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
ggplot2_py/limits.py ADDED
@@ -0,0 +1,314 @@
1
+ """
2
+ Scale-limit shortcuts for ggplot2.
3
+
4
+ Provides ``xlim()``, ``ylim()``, ``lims()``, and ``expand_limits()`` for
5
+ quickly setting axis and aesthetic limits.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, List, Optional, Sequence, Union
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+
15
+ from ggplot2_py._compat import cli_abort
16
+
17
+ __all__ = [
18
+ "lims",
19
+ "xlim",
20
+ "ylim",
21
+ "expand_limits",
22
+ ]
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Internal helpers
27
+ # ---------------------------------------------------------------------------
28
+
29
+ def _make_scale(
30
+ scale_type: str,
31
+ var: str,
32
+ *,
33
+ limits: Any = None,
34
+ transform: str = "identity",
35
+ ) -> Any:
36
+ """Construct a scale via name look-up.
37
+
38
+ Parameters
39
+ ----------
40
+ scale_type : str
41
+ One of ``"continuous"``, ``"discrete"``, ``"date"``, ``"datetime"``.
42
+ var : str
43
+ Aesthetic variable name (e.g. ``"x"``, ``"y"``, ``"colour"``).
44
+ limits : object
45
+ The limits to pass to the scale constructor.
46
+ transform : str
47
+ Transform name (only meaningful for continuous scales).
48
+
49
+ Returns
50
+ -------
51
+ Scale
52
+ A configured Scale instance.
53
+ """
54
+ # Lazy imports to avoid circular dependency
55
+ import ggplot2_py.scales as _scales_mod
56
+
57
+ name = f"scale_{var}_{scale_type}"
58
+ scale_fn = getattr(_scales_mod, name, None)
59
+ if scale_fn is None:
60
+ cli_abort(f"Unknown scale function: {name}")
61
+
62
+ kwargs: dict = {"limits": limits}
63
+ if scale_type == "continuous":
64
+ kwargs["transform"] = transform
65
+
66
+ return scale_fn(**kwargs)
67
+
68
+
69
+ def _limits_numeric(lims: Sequence, var: str) -> Any:
70
+ """Create a continuous scale with numeric limits.
71
+
72
+ Parameters
73
+ ----------
74
+ lims : sequence of float
75
+ Two-element sequence ``[lower, upper]``. ``None``/``np.nan``
76
+ elements are treated as auto-computed.
77
+ var : str
78
+ Aesthetic name.
79
+
80
+ Returns
81
+ -------
82
+ Scale
83
+ """
84
+ if len(lims) != 2:
85
+ cli_abort(
86
+ f"`{var}` must be a two-element numeric sequence, "
87
+ f"got length {len(lims)}."
88
+ )
89
+ low, high = lims
90
+ # Determine transform
91
+ has_low = low is not None and not (isinstance(low, float) and np.isnan(low))
92
+ has_high = high is not None and not (isinstance(high, float) and np.isnan(high))
93
+ if has_low and has_high and low > high:
94
+ transform = "reverse"
95
+ else:
96
+ transform = "identity"
97
+ return _make_scale("continuous", var, limits=list(lims), transform=transform)
98
+
99
+
100
+ def _limits_character(lims: Sequence[str], var: str) -> Any:
101
+ """Create a discrete scale with character limits.
102
+
103
+ Parameters
104
+ ----------
105
+ lims : sequence of str
106
+ Factor / category levels.
107
+ var : str
108
+ Aesthetic name.
109
+
110
+ Returns
111
+ -------
112
+ Scale
113
+ """
114
+ return _make_scale("discrete", var, limits=list(lims))
115
+
116
+
117
+ def _limits_date(lims: Sequence, var: str) -> Any:
118
+ """Create a date scale with date limits.
119
+
120
+ Parameters
121
+ ----------
122
+ lims : sequence
123
+ Two-element date sequence.
124
+ var : str
125
+ Aesthetic name.
126
+
127
+ Returns
128
+ -------
129
+ Scale
130
+ """
131
+ if len(lims) != 2:
132
+ cli_abort(
133
+ f"`{var}` must be a two-element date sequence, "
134
+ f"got length {len(lims)}."
135
+ )
136
+ return _make_scale("date", var, limits=list(lims))
137
+
138
+
139
+ def _limits_dispatch(lims: Any, var: str) -> Any:
140
+ """Dispatch to the correct limit constructor based on *lims* type.
141
+
142
+ Parameters
143
+ ----------
144
+ lims : object
145
+ Limits specification.
146
+ var : str
147
+ Aesthetic name.
148
+
149
+ Returns
150
+ -------
151
+ Scale
152
+ """
153
+ if isinstance(lims, (list, tuple)):
154
+ if len(lims) == 0:
155
+ cli_abort(f"`{var}` limits must be non-empty.")
156
+ first = next((v for v in lims if v is not None), None)
157
+ if first is None or isinstance(first, (int, float, np.integer, np.floating)):
158
+ return _limits_numeric(lims, var)
159
+ elif isinstance(first, str):
160
+ return _limits_character(lims, var)
161
+ elif isinstance(first, (pd.Timestamp, np.datetime64)):
162
+ return _limits_date(lims, var)
163
+ else:
164
+ # Try numeric
165
+ return _limits_numeric(lims, var)
166
+ elif isinstance(lims, np.ndarray):
167
+ return _limits_numeric(list(lims), var)
168
+ else:
169
+ cli_abort(
170
+ f"Cannot create limits for `{var}` from type {type(lims).__name__}."
171
+ )
172
+
173
+
174
+ # ---------------------------------------------------------------------------
175
+ # Public API
176
+ # ---------------------------------------------------------------------------
177
+
178
+ def xlim(*args: Any) -> Any:
179
+ """Set x-axis limits.
180
+
181
+ Parameters
182
+ ----------
183
+ *args
184
+ Either a single two-element sequence or two scalar values
185
+ specifying the lower and upper bounds. ``None`` leaves the
186
+ corresponding limit to be computed from the data.
187
+
188
+ Returns
189
+ -------
190
+ Scale
191
+ A position scale with the specified limits.
192
+
193
+ Examples
194
+ --------
195
+ >>> xlim(0, 10)
196
+ >>> xlim(10, 0) # reversed
197
+ >>> xlim(None, 20) # auto lower bound
198
+ """
199
+ if len(args) == 1 and isinstance(args[0], (list, tuple, np.ndarray)):
200
+ values = list(args[0])
201
+ else:
202
+ values = list(args)
203
+ return _limits_dispatch(values, "x")
204
+
205
+
206
+ def ylim(*args: Any) -> Any:
207
+ """Set y-axis limits.
208
+
209
+ Parameters
210
+ ----------
211
+ *args
212
+ Either a single two-element sequence or two scalar values
213
+ specifying the lower and upper bounds.
214
+
215
+ Returns
216
+ -------
217
+ Scale
218
+ A position scale with the specified limits.
219
+
220
+ Examples
221
+ --------
222
+ >>> ylim(0, 50)
223
+ """
224
+ if len(args) == 1 and isinstance(args[0], (list, tuple, np.ndarray)):
225
+ values = list(args[0])
226
+ else:
227
+ values = list(args)
228
+ return _limits_dispatch(values, "y")
229
+
230
+
231
+ def lims(**kwargs: Any) -> List[Any]:
232
+ """Set limits for one or more aesthetics.
233
+
234
+ Parameters
235
+ ----------
236
+ **kwargs
237
+ Keyword arguments mapping aesthetic names to limit specifications.
238
+ Numeric limits create continuous scales; string limits create
239
+ discrete scales.
240
+
241
+ Returns
242
+ -------
243
+ list of Scale
244
+ A list of scale objects suitable for adding to a ggplot via ``+``.
245
+
246
+ Examples
247
+ --------
248
+ >>> lims(x=(0, 10), y=(0, 50), colour=["red", "blue"])
249
+ """
250
+ result: List[Any] = []
251
+ for var, lim in kwargs.items():
252
+ if isinstance(lim, (list, tuple)):
253
+ result.append(_limits_dispatch(lim, var))
254
+ else:
255
+ cli_abort(
256
+ f"Limits for `{var}` must be a list or tuple, "
257
+ f"got {type(lim).__name__}."
258
+ )
259
+ return result
260
+
261
+
262
+ def expand_limits(**kwargs: Any) -> Any:
263
+ """Expand plot limits to include specified values.
264
+
265
+ Creates a ``geom_blank`` layer with data containing the specified
266
+ values, which forces the scales to expand.
267
+
268
+ Parameters
269
+ ----------
270
+ **kwargs
271
+ Aesthetic-name = value pairs. Each value (or list of values)
272
+ will be included in the scale training for the corresponding
273
+ aesthetic.
274
+
275
+ Returns
276
+ -------
277
+ Layer
278
+ A ``geom_blank`` layer with the specified data.
279
+
280
+ Examples
281
+ --------
282
+ >>> expand_limits(x=0, y=[0, 100])
283
+ """
284
+ # Build a DataFrame from the kwargs, repeating shorter vectors
285
+ data: dict = {}
286
+ for k, v in kwargs.items():
287
+ if isinstance(v, (list, tuple, np.ndarray)):
288
+ data[k] = list(v)
289
+ elif isinstance(v, pd.Series):
290
+ data[k] = list(v)
291
+ else:
292
+ data[k] = [v]
293
+
294
+ # Repeat vectors up to the max length
295
+ if data:
296
+ max_len = max(len(v) for v in data.values())
297
+ for k, v in data.items():
298
+ if len(v) < max_len:
299
+ # Repeat cyclically
300
+ reps = (max_len // len(v)) + 1
301
+ data[k] = (v * reps)[:max_len]
302
+
303
+ df = pd.DataFrame(data)
304
+
305
+ # Lazy imports
306
+ from ggplot2_py.aes import aes, Mapping
307
+
308
+ # Build a mapping from the column names
309
+ mapping = aes(**{k: k for k in df.columns})
310
+
311
+ # Use geom_blank
312
+ from ggplot2_py.geom import geom_blank
313
+
314
+ return geom_blank(mapping=mapping, data=df, inherit_aes=False)