ggplot2-python 4.0.2.9000__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. ggplot2_py/__init__.py +852 -0
  2. ggplot2_py/_compat.py +475 -0
  3. ggplot2_py/_plugins.py +129 -0
  4. ggplot2_py/_utils.py +544 -0
  5. ggplot2_py/aes.py +586 -0
  6. ggplot2_py/annotation.py +540 -0
  7. ggplot2_py/coord.py +2108 -0
  8. ggplot2_py/coords/__init__.py +49 -0
  9. ggplot2_py/datasets.py +265 -0
  10. ggplot2_py/draw_key.py +454 -0
  11. ggplot2_py/facet.py +1456 -0
  12. ggplot2_py/fortify.py +95 -0
  13. ggplot2_py/geom.py +4516 -0
  14. ggplot2_py/geoms/__init__.py +12 -0
  15. ggplot2_py/ggproto.py +279 -0
  16. ggplot2_py/guide.py +2925 -0
  17. ggplot2_py/guide_axis.py +615 -0
  18. ggplot2_py/guide_colourbar.py +657 -0
  19. ggplot2_py/guide_legend.py +1061 -0
  20. ggplot2_py/guides/__init__.py +8 -0
  21. ggplot2_py/labeller.py +296 -0
  22. ggplot2_py/labels.py +309 -0
  23. ggplot2_py/layer.py +954 -0
  24. ggplot2_py/layout.py +754 -0
  25. ggplot2_py/limits.py +314 -0
  26. ggplot2_py/plot.py +1401 -0
  27. ggplot2_py/plot_render.py +866 -0
  28. ggplot2_py/position.py +1269 -0
  29. ggplot2_py/protocols.py +171 -0
  30. ggplot2_py/py.typed +0 -0
  31. ggplot2_py/qplot.py +233 -0
  32. ggplot2_py/resources/diamonds.csv +53941 -0
  33. ggplot2_py/resources/economics.csv +575 -0
  34. ggplot2_py/resources/economics_long.csv +2871 -0
  35. ggplot2_py/resources/faithfuld.csv +5626 -0
  36. ggplot2_py/resources/luv_colours.csv +658 -0
  37. ggplot2_py/resources/midwest.csv +438 -0
  38. ggplot2_py/resources/mpg.csv +235 -0
  39. ggplot2_py/resources/msleep.csv +84 -0
  40. ggplot2_py/resources/presidential.csv +13 -0
  41. ggplot2_py/resources/seals.csv +1156 -0
  42. ggplot2_py/resources/txhousing.csv +8603 -0
  43. ggplot2_py/save.py +316 -0
  44. ggplot2_py/scale.py +2727 -0
  45. ggplot2_py/scales/__init__.py +4252 -0
  46. ggplot2_py/stat.py +6071 -0
  47. ggplot2_py/stats/__init__.py +9 -0
  48. ggplot2_py/theme.py +490 -0
  49. ggplot2_py/theme_defaults.py +1350 -0
  50. ggplot2_py/theme_elements.py +2052 -0
  51. ggplot2_python-4.0.2.9000.dist-info/METADATA +179 -0
  52. ggplot2_python-4.0.2.9000.dist-info/RECORD +54 -0
  53. ggplot2_python-4.0.2.9000.dist-info/WHEEL +4 -0
  54. ggplot2_python-4.0.2.9000.dist-info/licenses/LICENSE +3 -0
@@ -0,0 +1,9 @@
1
+ """
2
+ Stats subpackage for ggplot2.
3
+
4
+ Re-exports all stat classes and constructor functions from
5
+ :mod:`ggplot2_py.stat`.
6
+ """
7
+
8
+ from ggplot2_py.stat import * # noqa: F401,F403
9
+ from ggplot2_py.stat import __all__ # noqa: F401
ggplot2_py/theme.py ADDED
@@ -0,0 +1,490 @@
1
+ """
2
+ Theme creation and management for ggplot2.
3
+
4
+ Provides the ``Theme`` class, the ``theme()`` constructor, global theme
5
+ state functions (``theme_get``, ``theme_set``, etc.), and the ``+`` /
6
+ ``%+replace%`` operators for combining themes.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import copy
12
+ from typing import Any, Dict, Optional
13
+
14
+ from ggplot2_py._compat import cli_abort, cli_warn
15
+ from ggplot2_py.theme_elements import (
16
+ Element,
17
+ ElementBlank,
18
+ is_theme_element,
19
+ merge_element,
20
+ combine_elements,
21
+ _ggplot_global,
22
+ get_element_tree,
23
+ calc_element,
24
+ )
25
+
26
+ __all__ = [
27
+ "Theme",
28
+ "theme",
29
+ "is_theme",
30
+ "complete_theme",
31
+ "add_theme",
32
+ "theme_get",
33
+ "theme_set",
34
+ "theme_update",
35
+ "theme_replace",
36
+ "set_theme",
37
+ "get_theme",
38
+ "reset_theme_settings",
39
+ "update_theme",
40
+ "replace_theme",
41
+ ]
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Theme class
46
+ # ---------------------------------------------------------------------------
47
+
48
+ class Theme:
49
+ """A ggplot2 theme object.
50
+
51
+ Internally a ``Theme`` is a dictionary-like container mapping element
52
+ names (strings) to element objects (``Element`` subclasses, ``Unit``,
53
+ scalars, etc.). It supports ``+`` to merge themes and item access via
54
+ ``[]`` or ``.get()``.
55
+
56
+ Parameters
57
+ ----------
58
+ elements : dict
59
+ Mapping of element names to values.
60
+ complete : bool
61
+ ``True`` for complete themes (e.g. ``theme_grey()``).
62
+ validate : bool
63
+ Whether to validate elements against the element tree.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ elements: Optional[Dict[str, Any]] = None,
69
+ complete: bool = False,
70
+ validate: bool = True,
71
+ ) -> None:
72
+ self._elements: Dict[str, Any] = dict(elements) if elements else {}
73
+ self.complete = complete
74
+ self.validate = validate
75
+
76
+ # -- dict-like access ---------------------------------------------------
77
+
78
+ def __getitem__(self, key: str) -> Any:
79
+ return self._elements[key]
80
+
81
+ def __setitem__(self, key: str, value: Any) -> None:
82
+ self._elements[key] = value
83
+
84
+ def __contains__(self, key: str) -> bool:
85
+ return key in self._elements
86
+
87
+ def __iter__(self):
88
+ return iter(self._elements)
89
+
90
+ def __len__(self) -> int:
91
+ return len(self._elements)
92
+
93
+ def get(self, key: str, default: Any = None) -> Any:
94
+ """Return the element for *key*, or *default* if absent.
95
+
96
+ Parameters
97
+ ----------
98
+ key : str
99
+ Element name.
100
+ default : Any
101
+ Fallback value.
102
+
103
+ Returns
104
+ -------
105
+ Any
106
+ """
107
+ return self._elements.get(key, default)
108
+
109
+ def keys(self):
110
+ return self._elements.keys()
111
+
112
+ def values(self):
113
+ return self._elements.values()
114
+
115
+ def items(self):
116
+ return self._elements.items()
117
+
118
+ def names(self):
119
+ """Return element names (R-compatible alias for ``keys``)."""
120
+ return list(self._elements.keys())
121
+
122
+ def update(self, other: Dict[str, Any]) -> None:
123
+ """Update elements in-place from *other*.
124
+
125
+ Parameters
126
+ ----------
127
+ other : dict
128
+ Mapping of element names to values.
129
+ """
130
+ self._elements.update(other)
131
+
132
+ def copy(self) -> "Theme":
133
+ """Return a shallow copy of this theme.
134
+
135
+ Returns
136
+ -------
137
+ Theme
138
+ """
139
+ return Theme(
140
+ elements=dict(self._elements),
141
+ complete=self.complete,
142
+ validate=self.validate,
143
+ )
144
+
145
+ # -- operators ----------------------------------------------------------
146
+
147
+ def __add__(self, other: Any) -> "Theme":
148
+ """Merge another theme into this one (``self + other``).
149
+
150
+ Properties in *other* override those in *self*; ``None`` properties
151
+ in *other* are filled from *self* (element-level merge).
152
+
153
+ Parameters
154
+ ----------
155
+ other : Theme or None
156
+ Theme to merge.
157
+
158
+ Returns
159
+ -------
160
+ Theme
161
+ """
162
+ if other is None:
163
+ return self.copy()
164
+ if not isinstance(other, Theme):
165
+ cli_abort(
166
+ f"Cannot add {type(other).__name__} to a Theme object."
167
+ )
168
+ return add_theme(self, other)
169
+
170
+ def __radd__(self, other: Any) -> "Theme":
171
+ """Support ``None + theme``."""
172
+ if other is None or other == 0:
173
+ return self.copy()
174
+ if isinstance(other, Theme):
175
+ return add_theme(other, self)
176
+ return NotImplemented
177
+
178
+ def __repr__(self) -> str:
179
+ n = len(self._elements)
180
+ tag = "complete " if self.complete else ""
181
+ return f"<Theme ({tag}{n} elements)>"
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # theme() constructor
186
+ # ---------------------------------------------------------------------------
187
+
188
+ def theme(complete: bool = False, validate: bool = True, **kwargs: Any) -> Theme:
189
+ """Create a ``Theme`` object.
190
+
191
+ Parameters
192
+ ----------
193
+ complete : bool
194
+ If ``True``, marks this as a complete theme (like ``theme_grey()``).
195
+ Complete themes set ``inherit_blank=True`` on all elements.
196
+ validate : bool
197
+ If ``True``, element values are checked against the element tree.
198
+ **kwargs
199
+ Named theme elements (e.g. ``line=element_line(...)``).
200
+
201
+ Returns
202
+ -------
203
+ Theme
204
+ A new theme object.
205
+
206
+ Examples
207
+ --------
208
+ >>> from ggplot2_py.theme_elements import element_text, element_rect, rel
209
+ >>> t = theme(plot_title=element_text(size=rel(1.2), hjust=0))
210
+ """
211
+ # Normalise dots to dashes (Python kwargs use underscores but theme
212
+ # element names use dots in R). We accept both forms.
213
+ elements: Dict[str, Any] = {}
214
+ for key, value in kwargs.items():
215
+ # Convert underscores to dots so that ``axis_text_x`` -> ``axis.text.x``
216
+ canonical = key.replace("_", ".")
217
+ # But preserve 'inherit.blank' as-is (it's an element property, not
218
+ # a theme key). Actually theme keys never contain "inherit.blank"
219
+ # so this is fine.
220
+ elements[canonical] = value
221
+
222
+ # If complete, set inherit_blank = True on all elements
223
+ if complete:
224
+ for key, el in elements.items():
225
+ if isinstance(el, Element) and hasattr(el, "inherit_blank"):
226
+ el.inherit_blank = True
227
+
228
+ return Theme(elements=elements, complete=complete, validate=validate)
229
+
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # is_theme
233
+ # ---------------------------------------------------------------------------
234
+
235
+ def is_theme(x: Any) -> bool:
236
+ """Test whether *x* is a ``Theme`` object.
237
+
238
+ Parameters
239
+ ----------
240
+ x : Any
241
+ Object to test.
242
+
243
+ Returns
244
+ -------
245
+ bool
246
+ """
247
+ return isinstance(x, Theme)
248
+
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # add_theme — the engine behind ``+``
252
+ # ---------------------------------------------------------------------------
253
+
254
+ def add_theme(t1: Theme, t2: Theme) -> Theme:
255
+ """Merge theme *t2* into *t1*.
256
+
257
+ Parameters
258
+ ----------
259
+ t1 : Theme
260
+ The base theme (may also be a plain dict for the initial plot theme).
261
+ t2 : Theme
262
+ The theme to add (its elements override *t1*'s).
263
+
264
+ Returns
265
+ -------
266
+ Theme
267
+ A new theme with merged elements.
268
+ """
269
+ if t2 is None:
270
+ if isinstance(t1, dict):
271
+ return Theme(t1)
272
+ return t1.copy()
273
+
274
+ # If t2 is complete, it replaces t1 entirely
275
+ if t2.complete:
276
+ return t2.copy()
277
+
278
+ if t1 is None:
279
+ return t2.copy()
280
+
281
+ # If t1 is a plain dict (e.g. initial plot theme), wrap it in a Theme
282
+ # so it has .validate, .copy(), .keys() etc. Mirrors R's
283
+ # ``if (!is_theme(t1) && is.list(t1)) t1 <- theme(!!!t1)``
284
+ if isinstance(t1, dict) and not isinstance(t1, Theme):
285
+ t1 = Theme(t1)
286
+
287
+ result = t1.copy()
288
+ for item in t2.keys():
289
+ try:
290
+ old_val = result.get(item)
291
+ new_val = t2[item]
292
+ merged = merge_element(new_val, old_val)
293
+ result[item] = merged
294
+ except Exception as exc:
295
+ cli_warn(f"Problem merging theme element '{item}': {exc}")
296
+ result[item] = t2[item]
297
+
298
+ result.validate = t1.validate and t2.validate
299
+ return result
300
+
301
+
302
+ def theme_replace_op(e1: Theme, e2: Theme) -> Theme:
303
+ """The ``%+replace%`` operator: replace elements wholesale.
304
+
305
+ Unlike ``+``, this does not merge element-level properties.
306
+ Missing elements in *e2* result in ``None`` in the output.
307
+
308
+ Parameters
309
+ ----------
310
+ e1 : Theme
311
+ The base theme.
312
+ e2 : Theme
313
+ The replacement theme.
314
+
315
+ Returns
316
+ -------
317
+ Theme
318
+ """
319
+ if not isinstance(e1, Theme) or not isinstance(e2, Theme):
320
+ cli_abort("%+replace% requires two Theme objects.")
321
+ result = e1.copy()
322
+ for key in e2.keys():
323
+ result[key] = e2[key]
324
+ return result
325
+
326
+
327
+ # ---------------------------------------------------------------------------
328
+ # complete_theme
329
+ # ---------------------------------------------------------------------------
330
+
331
+ def complete_theme(
332
+ theme_obj: Optional[Theme] = None,
333
+ default: Optional[Theme] = None,
334
+ ) -> Theme:
335
+ """Complete a theme so every element is fully resolved.
336
+
337
+ Parameters
338
+ ----------
339
+ theme_obj : Theme or None
340
+ An incomplete theme to complete.
341
+ default : Theme or None
342
+ A complete theme to fill in missing elements. Falls back to the
343
+ current global theme.
344
+
345
+ Returns
346
+ -------
347
+ Theme
348
+ A fully resolved theme.
349
+ """
350
+ if default is None:
351
+ default = get_theme()
352
+ if default is None:
353
+ # No global theme set yet; return what we have
354
+ if theme_obj is None:
355
+ return Theme(complete=True, validate=False)
356
+ result = theme_obj.copy()
357
+ result.complete = True
358
+ result.validate = False
359
+ return result
360
+
361
+ if theme_obj is None:
362
+ result = default.copy()
363
+ elif theme_obj.complete:
364
+ # For complete themes, only fill missing elements
365
+ result = theme_obj.copy()
366
+ for key in default.keys():
367
+ if key not in result:
368
+ result[key] = default[key]
369
+ else:
370
+ result = default + theme_obj
371
+
372
+ # Fill from global default as last resort
373
+ global_default = _ggplot_global.theme_default
374
+ if global_default is not None:
375
+ for key in global_default.keys():
376
+ if key not in result:
377
+ result[key] = global_default[key]
378
+
379
+ result.complete = True
380
+ result.validate = False
381
+ return result
382
+
383
+
384
+ # ---------------------------------------------------------------------------
385
+ # Global theme state
386
+ # ---------------------------------------------------------------------------
387
+
388
+ def get_theme() -> Optional[Theme]:
389
+ """Return the currently active global theme.
390
+
391
+ Returns
392
+ -------
393
+ Theme or None
394
+ """
395
+ return _ggplot_global.theme_current
396
+
397
+
398
+ def set_theme(new: Optional[Theme] = None) -> Optional[Theme]:
399
+ """Set the global theme, returning the previous one.
400
+
401
+ Parameters
402
+ ----------
403
+ new : Theme or None
404
+ The theme to make active. If ``None``, resets to the default.
405
+
406
+ Returns
407
+ -------
408
+ Theme or None
409
+ The previously active theme.
410
+ """
411
+ if new is None:
412
+ new = _ggplot_global.theme_default
413
+ if new is not None and not isinstance(new, Theme):
414
+ cli_abort("set_theme() requires a Theme object.")
415
+ old = _ggplot_global.theme_current
416
+ _ggplot_global.theme_current = new
417
+ return old
418
+
419
+
420
+ def update_theme(**kwargs: Any) -> Optional[Theme]:
421
+ """Update the current global theme in-place.
422
+
423
+ Parameters
424
+ ----------
425
+ **kwargs
426
+ Theme element overrides (passed to ``theme()``).
427
+
428
+ Returns
429
+ -------
430
+ Theme or None
431
+ The previously active theme.
432
+ """
433
+ current = get_theme()
434
+ new_theme = theme(**kwargs)
435
+ if current is not None:
436
+ return set_theme(current + new_theme)
437
+ return set_theme(new_theme)
438
+
439
+
440
+ def replace_theme(**kwargs: Any) -> Optional[Theme]:
441
+ """Replace elements in the current global theme.
442
+
443
+ Unlike ``update_theme``, this replaces elements wholesale
444
+ (no element-level merging).
445
+
446
+ Parameters
447
+ ----------
448
+ **kwargs
449
+ Theme element overrides.
450
+
451
+ Returns
452
+ -------
453
+ Theme or None
454
+ The previously active theme.
455
+ """
456
+ current = get_theme()
457
+ new_theme = theme(**kwargs)
458
+ if current is not None:
459
+ return set_theme(theme_replace_op(current, new_theme))
460
+ return set_theme(new_theme)
461
+
462
+
463
+ def reset_theme_settings(reset_current: bool = True) -> None:
464
+ """Reset the global theme state to built-in defaults.
465
+
466
+ Parameters
467
+ ----------
468
+ reset_current : bool
469
+ If ``True`` (default), also reset the currently active theme.
470
+ """
471
+ from ggplot2_py.theme_elements import reset_theme_settings as _reset_tree
472
+
473
+ _reset_tree(reset_current=False)
474
+ # Lazy import to avoid circular dependency at module load time
475
+ try:
476
+ from ggplot2_py.theme_defaults import theme_grey
477
+
478
+ _ggplot_global.theme_default = theme_grey()
479
+ if reset_current:
480
+ _ggplot_global.theme_current = _ggplot_global.theme_default
481
+ except ImportError:
482
+ # theme_defaults may not be available yet during initial import
483
+ pass
484
+
485
+
486
+ # R-compatible aliases
487
+ theme_get = get_theme
488
+ theme_set = set_theme
489
+ theme_update = update_theme
490
+ theme_replace = replace_theme