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,540 @@
1
+ """
2
+ Annotation helpers for ggplot2 plots.
3
+
4
+ Provides ``annotate()``, ``annotation_custom()``, ``annotation_raster()``,
5
+ ``annotation_logticks()``, and map-related annotation stubs.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, List, Optional, Sequence, Union
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+
15
+ from ggplot2_py._compat import Waiver, is_waiver, waiver, cli_abort, cli_warn
16
+ from ggplot2_py._utils import compact
17
+
18
+ __all__ = [
19
+ "annotate",
20
+ "annotation_custom",
21
+ "annotation_raster",
22
+ "annotation_logticks",
23
+ "annotation_map",
24
+ "annotation_borders",
25
+ "borders",
26
+ ]
27
+
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # annotate()
31
+ # ---------------------------------------------------------------------------
32
+
33
+ def annotate(
34
+ geom: str,
35
+ x: Any = None,
36
+ y: Any = None,
37
+ xmin: Any = None,
38
+ xmax: Any = None,
39
+ ymin: Any = None,
40
+ ymax: Any = None,
41
+ xend: Any = None,
42
+ yend: Any = None,
43
+ *,
44
+ na_rm: bool = False,
45
+ **kwargs: Any,
46
+ ) -> Any:
47
+ """Create an annotation layer.
48
+
49
+ Unlike a typical geom, the aesthetics of an annotation layer are
50
+ *not* mapped from variables in a data frame -- they are passed as
51
+ constant vectors. This is useful for adding small annotations such as
52
+ text labels, rectangles, or line segments.
53
+
54
+ Parameters
55
+ ----------
56
+ geom : str
57
+ Name of the geom to use (e.g. ``"text"``, ``"rect"``, ``"segment"``).
58
+ x, y, xmin, xmax, ymin, ymax, xend, yend : scalar or array-like, optional
59
+ Positional aesthetics. At least one must be specified.
60
+ na_rm : bool, optional
61
+ If ``True``, silently remove missing values.
62
+ **kwargs
63
+ Additional aesthetic values or geom parameters.
64
+
65
+ Returns
66
+ -------
67
+ Layer
68
+ A layer suitable for adding to a ggplot via ``+``.
69
+
70
+ Raises
71
+ ------
72
+ ValueError
73
+ If *geom* is one of the reference-line geoms (``"abline"``,
74
+ ``"hline"``, ``"vline"``).
75
+
76
+ Examples
77
+ --------
78
+ >>> annotate("text", x=4, y=25, label="Some text")
79
+ >>> annotate("rect", xmin=3, xmax=4.2, ymin=12, ymax=21, alpha=0.2)
80
+ """
81
+ if geom in ("abline", "hline", "vline"):
82
+ cli_warn(
83
+ f"`geom` must not be '{geom}'. "
84
+ f"Please use `geom_{geom}()` directly instead."
85
+ )
86
+
87
+ # Build position dict (only non-None entries)
88
+ position = compact({
89
+ "x": x,
90
+ "xmin": xmin,
91
+ "xmax": xmax,
92
+ "xend": xend,
93
+ "y": y,
94
+ "ymin": ymin,
95
+ "ymax": ymax,
96
+ "yend": yend,
97
+ })
98
+
99
+ aesthetics: Dict[str, Any] = dict(position)
100
+ aesthetics.update(kwargs)
101
+
102
+ # Check compatible lengths
103
+ lengths = {}
104
+ for k, v in aesthetics.items():
105
+ if isinstance(v, (list, tuple, np.ndarray, pd.Series)):
106
+ lengths[k] = len(v)
107
+ else:
108
+ lengths[k] = 1
109
+
110
+ unique_lengths = set(lengths.values())
111
+ if len(unique_lengths) > 1:
112
+ unique_lengths.discard(1)
113
+ if len(unique_lengths) > 1:
114
+ bad = {k: l for k, l in lengths.items() if l != 1}
115
+ details = ", ".join(f"{k} ({l})" for k, l in bad.items())
116
+ cli_abort(f"Unequal parameter lengths: {details}")
117
+
118
+ n = max(lengths.values()) if lengths else 1
119
+
120
+ # Build data from position aesthetics
121
+ data_dict: Dict[str, Any] = {}
122
+ for k, v in position.items():
123
+ if isinstance(v, (list, tuple, np.ndarray, pd.Series)):
124
+ data_dict[k] = list(v)
125
+ else:
126
+ data_dict[k] = [v] * n
127
+ data = pd.DataFrame(data_dict)
128
+
129
+ # Separate params (non-position kwargs)
130
+ params: Dict[str, Any] = {"na_rm": na_rm}
131
+ for k, v in kwargs.items():
132
+ if k not in ("position", "stat"):
133
+ params[k] = v
134
+ else:
135
+ cli_warn(f"`annotate()` cannot accept the `{k}` argument.")
136
+
137
+ # Lazy imports
138
+ from ggplot2_py.aes import aes, Mapping
139
+ from ggplot2_py.layer import layer
140
+ from ggplot2_py.stat import StatIdentity
141
+ from ggplot2_py.position import PositionIdentity
142
+
143
+ # Build mapping from data columns
144
+ mapping = aes(**{k: k for k in data.columns})
145
+
146
+ return layer(
147
+ geom=geom,
148
+ params=params,
149
+ stat=StatIdentity,
150
+ position=PositionIdentity,
151
+ data=data,
152
+ mapping=mapping,
153
+ inherit_aes=False,
154
+ show_legend=False,
155
+ )
156
+
157
+
158
+ # ---------------------------------------------------------------------------
159
+ # annotation_custom()
160
+ # ---------------------------------------------------------------------------
161
+
162
+ def annotation_custom(
163
+ grob: Any,
164
+ xmin: float = -np.inf,
165
+ xmax: float = np.inf,
166
+ ymin: float = -np.inf,
167
+ ymax: float = np.inf,
168
+ ) -> Any:
169
+ """Add an arbitrary grob as a plot annotation.
170
+
171
+ The annotation is placed using data coordinates and will not affect
172
+ the scale limits.
173
+
174
+ Parameters
175
+ ----------
176
+ grob : grob
177
+ A grid grob to display.
178
+ xmin, xmax : float
179
+ Horizontal extent in data coordinates. ``-inf``/``inf`` spans
180
+ the full panel.
181
+ ymin, ymax : float
182
+ Vertical extent in data coordinates.
183
+
184
+ Returns
185
+ -------
186
+ Layer
187
+ An annotation layer.
188
+ """
189
+ from ggplot2_py.layer import layer
190
+ from ggplot2_py.stat import StatIdentity
191
+ from ggplot2_py.position import PositionIdentity
192
+ from ggplot2_py.geom import Geom
193
+
194
+ # Minimal GeomCustomAnn implementation
195
+ class GeomCustomAnn(Geom):
196
+ """Internal geom for annotation_custom."""
197
+
198
+ _class_name = "GeomCustomAnn"
199
+ extra_params = []
200
+
201
+ @staticmethod
202
+ def handle_na(data: Any, params: Any) -> Any:
203
+ return data
204
+
205
+ @staticmethod
206
+ def draw_panel(data: Any, panel_params: Any, coord: Any,
207
+ grob: Any = None, xmin: float = -np.inf,
208
+ xmax: float = np.inf, ymin: float = -np.inf,
209
+ ymax: float = np.inf, **kwargs: Any) -> Any:
210
+ from grid_py import Viewport, edit_grob
211
+
212
+ # Transform annotation coordinates
213
+ adf = pd.DataFrame({
214
+ "xmin": [xmin], "xmax": [xmax],
215
+ "ymin": [ymin], "ymax": [ymax],
216
+ })
217
+ adf = coord.transform(adf, panel_params)
218
+ x_range = [float(adf["xmin"].iloc[0]), float(adf["xmax"].iloc[0])]
219
+ y_range = [float(adf["ymin"].iloc[0]), float(adf["ymax"].iloc[0])]
220
+ vp = Viewport(
221
+ x=sum(x_range) / 2,
222
+ y=sum(y_range) / 2,
223
+ width=abs(x_range[1] - x_range[0]),
224
+ height=abs(y_range[1] - y_range[0]),
225
+ just="center",
226
+ )
227
+ return edit_grob(grob, vp=vp)
228
+
229
+ dummy = pd.DataFrame({"x": [0], "y": [0]})
230
+
231
+ return layer(
232
+ data=dummy,
233
+ stat=StatIdentity,
234
+ position=PositionIdentity,
235
+ geom=GeomCustomAnn,
236
+ inherit_aes=False,
237
+ params={
238
+ "grob": grob,
239
+ "xmin": xmin,
240
+ "xmax": xmax,
241
+ "ymin": ymin,
242
+ "ymax": ymax,
243
+ },
244
+ )
245
+
246
+
247
+ # ---------------------------------------------------------------------------
248
+ # annotation_raster()
249
+ # ---------------------------------------------------------------------------
250
+
251
+ def annotation_raster(
252
+ raster: Any,
253
+ xmin: float,
254
+ xmax: float,
255
+ ymin: float,
256
+ ymax: float,
257
+ interpolate: bool = False,
258
+ ) -> Any:
259
+ """Add a raster image as a plot annotation.
260
+
261
+ Parameters
262
+ ----------
263
+ raster : array-like
264
+ Raster object to display (e.g. a NumPy array or PIL Image).
265
+ xmin, xmax : float
266
+ Horizontal extent in data coordinates.
267
+ ymin, ymax : float
268
+ Vertical extent in data coordinates.
269
+ interpolate : bool, optional
270
+ If ``True``, interpolate the raster linearly.
271
+
272
+ Returns
273
+ -------
274
+ Layer
275
+ An annotation layer.
276
+ """
277
+ from ggplot2_py.layer import layer
278
+ from ggplot2_py.stat import StatIdentity
279
+ from ggplot2_py.position import PositionIdentity
280
+ from ggplot2_py.geom import Geom
281
+
282
+ class GeomRasterAnn(Geom):
283
+ """Internal geom for annotation_raster."""
284
+
285
+ _class_name = "GeomRasterAnn"
286
+ extra_params = []
287
+
288
+ @staticmethod
289
+ def handle_na(data: Any, params: Any) -> Any:
290
+ return data
291
+
292
+ @staticmethod
293
+ def draw_panel(data: Any, panel_params: Any, coord: Any,
294
+ raster: Any = None, xmin: float = 0,
295
+ xmax: float = 1, ymin: float = 0,
296
+ ymax: float = 1, interpolate: bool = False,
297
+ **kwargs: Any) -> Any:
298
+ from grid_py import raster_grob
299
+
300
+ adf = pd.DataFrame({
301
+ "xmin": [xmin], "xmax": [xmax],
302
+ "ymin": [ymin], "ymax": [ymax],
303
+ })
304
+ adf = coord.transform(adf, panel_params)
305
+ x0 = float(adf["xmin"].iloc[0])
306
+ y0 = float(adf["ymin"].iloc[0])
307
+ w = float(adf["xmax"].iloc[0]) - x0
308
+ h = float(adf["ymax"].iloc[0]) - y0
309
+ return raster_grob(
310
+ raster, x0, y0, w, h,
311
+ default_units="native",
312
+ just=("left", "bottom"),
313
+ interpolate=interpolate,
314
+ )
315
+
316
+ dummy = pd.DataFrame({"x": [0], "y": [0]})
317
+
318
+ return layer(
319
+ data=dummy,
320
+ mapping=None,
321
+ stat=StatIdentity,
322
+ position=PositionIdentity,
323
+ geom=GeomRasterAnn,
324
+ inherit_aes=False,
325
+ params={
326
+ "raster": raster,
327
+ "xmin": xmin,
328
+ "xmax": xmax,
329
+ "ymin": ymin,
330
+ "ymax": ymax,
331
+ "interpolate": interpolate,
332
+ },
333
+ )
334
+
335
+
336
+ # ---------------------------------------------------------------------------
337
+ # annotation_logticks()
338
+ # ---------------------------------------------------------------------------
339
+
340
+ def annotation_logticks(
341
+ base: float = 10,
342
+ sides: str = "bl",
343
+ outside: bool = False,
344
+ scaled: bool = True,
345
+ short: Any = None,
346
+ mid: Any = None,
347
+ long: Any = None,
348
+ colour: str = "black",
349
+ linewidth: float = 0.5,
350
+ linetype: Union[str, int] = 1,
351
+ alpha: float = 1.0,
352
+ color: Optional[str] = None,
353
+ **kwargs: Any,
354
+ ) -> Any:
355
+ """Add log-scale tick marks to a plot.
356
+
357
+ This annotation is superseded by ``guide_axis_logticks()``.
358
+
359
+ Parameters
360
+ ----------
361
+ base : float, optional
362
+ Base of the logarithm (default 10).
363
+ sides : str, optional
364
+ Which sides to draw ticks on. A string containing any of
365
+ ``"t"`` (top), ``"r"`` (right), ``"b"`` (bottom), ``"l"`` (left).
366
+ outside : bool, optional
367
+ If ``True``, draw ticks outside the plot area.
368
+ scaled : bool, optional
369
+ ``True`` if data is already log-transformed.
370
+ short, mid, long : Unit-like, optional
371
+ Lengths for the three levels of tick marks.
372
+ colour : str, optional
373
+ Tick colour.
374
+ linewidth : float, optional
375
+ Tick line width in mm.
376
+ linetype : str or int, optional
377
+ Tick linetype.
378
+ alpha : float, optional
379
+ Tick transparency.
380
+ color : str, optional
381
+ Alias for *colour*.
382
+ **kwargs
383
+ Additional parameters.
384
+
385
+ Returns
386
+ -------
387
+ Layer
388
+ A log-tick annotation layer.
389
+ """
390
+ if color is not None:
391
+ colour = color
392
+
393
+ from grid_py import Unit
394
+
395
+ if short is None:
396
+ short = Unit(0.1, "cm")
397
+ if mid is None:
398
+ mid = Unit(0.2, "cm")
399
+ if long is None:
400
+ long = Unit(0.3, "cm")
401
+
402
+ from ggplot2_py.layer import layer
403
+ from ggplot2_py.stat import StatIdentity
404
+ from ggplot2_py.position import PositionIdentity
405
+ from ggplot2_py.geom import Geom
406
+
407
+ class GeomLogticks(Geom):
408
+ """Internal geom for annotation_logticks."""
409
+
410
+ _class_name = "GeomLogticks"
411
+ extra_params = []
412
+
413
+ @staticmethod
414
+ def handle_na(data: Any, params: Any) -> Any:
415
+ return data
416
+
417
+ @staticmethod
418
+ def draw_panel(data: Any, panel_params: Any, coord: Any,
419
+ **params: Any) -> Any:
420
+ from grid_py import GTree, GList, segments_grob, Unit as U
421
+
422
+ # Stub: return an empty gTree
423
+ return GTree(children=GList())
424
+
425
+ dummy = pd.DataFrame({"x": [0], "y": [0]})
426
+
427
+ return layer(
428
+ data=dummy,
429
+ mapping=None,
430
+ stat=StatIdentity,
431
+ position=PositionIdentity,
432
+ geom=GeomLogticks,
433
+ show_legend=False,
434
+ inherit_aes=False,
435
+ params={
436
+ "base": base,
437
+ "sides": sides,
438
+ "outside": outside,
439
+ "scaled": scaled,
440
+ "short": short,
441
+ "mid": mid,
442
+ "long": long,
443
+ "colour": colour,
444
+ "linewidth": linewidth,
445
+ "linetype": linetype,
446
+ "alpha": alpha,
447
+ **kwargs,
448
+ },
449
+ )
450
+
451
+
452
+ # ---------------------------------------------------------------------------
453
+ # Stubs for map-based annotations
454
+ # ---------------------------------------------------------------------------
455
+
456
+ def annotation_map(map_data: Any, **kwargs: Any) -> Any:
457
+ """Add a map layer as an annotation (stub).
458
+
459
+ Parameters
460
+ ----------
461
+ map_data : DataFrame
462
+ Map data with ``long``, ``lat``, ``group`` columns.
463
+ **kwargs
464
+ Additional aesthetic parameters.
465
+
466
+ Returns
467
+ -------
468
+ Layer
469
+ An annotation layer (currently a stub that raises
470
+ ``NotImplementedError``).
471
+ """
472
+ raise NotImplementedError(
473
+ "annotation_map() is not yet implemented in the Python port."
474
+ )
475
+
476
+
477
+ def annotation_borders(
478
+ database: str = "world",
479
+ regions: str = ".",
480
+ fill: Optional[str] = None,
481
+ colour: str = "grey50",
482
+ xlim: Optional[Any] = None,
483
+ ylim: Optional[Any] = None,
484
+ **kwargs: Any,
485
+ ) -> Any:
486
+ """Add map border annotations (stub).
487
+
488
+ Parameters
489
+ ----------
490
+ database : str
491
+ Map database name.
492
+ regions : str
493
+ Regions to include.
494
+ fill : str or None
495
+ Fill colour.
496
+ colour : str
497
+ Border colour.
498
+ xlim, ylim : optional
499
+ Coordinate limits.
500
+
501
+ Returns
502
+ -------
503
+ Layer
504
+ """
505
+ raise NotImplementedError(
506
+ "annotation_borders() is not yet implemented in the Python port."
507
+ )
508
+
509
+
510
+ def borders(
511
+ database: str = "world",
512
+ regions: str = ".",
513
+ fill: Optional[str] = None,
514
+ colour: str = "grey50",
515
+ xlim: Optional[Any] = None,
516
+ ylim: Optional[Any] = None,
517
+ **kwargs: Any,
518
+ ) -> Any:
519
+ """Add map borders as a layer (stub).
520
+
521
+ Parameters
522
+ ----------
523
+ database : str
524
+ Map database name.
525
+ regions : str
526
+ Regions to include.
527
+ fill : str or None
528
+ Fill colour.
529
+ colour : str
530
+ Border colour.
531
+ xlim, ylim : optional
532
+ Coordinate limits.
533
+
534
+ Returns
535
+ -------
536
+ Layer
537
+ """
538
+ raise NotImplementedError(
539
+ "borders() is not yet implemented in the Python port."
540
+ )