ggh4x-python 0.3.1.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 (64) hide show
  1. ggh4x/__init__.py +140 -0
  2. ggh4x/_aimed_text_grob.py +432 -0
  3. ggh4x/_borrowed_ggplot2.py +273 -0
  4. ggh4x/_cli.py +84 -0
  5. ggh4x/_datasets.py +106 -0
  6. ggh4x/_download.py +111 -0
  7. ggh4x/_facet_helpers.py +313 -0
  8. ggh4x/_facet_utils.py +649 -0
  9. ggh4x/_gap_grobs.py +606 -0
  10. ggh4x/_registry.py +10 -0
  11. ggh4x/_rlang.py +93 -0
  12. ggh4x/_utils.py +150 -0
  13. ggh4x/_vctrs.py +233 -0
  14. ggh4x/conveniences.py +601 -0
  15. ggh4x/coord_axes_inside.py +380 -0
  16. ggh4x/element_part_rect.py +545 -0
  17. ggh4x/facet_grid2.py +1018 -0
  18. ggh4x/facet_manual.py +901 -0
  19. ggh4x/facet_nested.py +776 -0
  20. ggh4x/facet_nested_wrap.py +193 -0
  21. ggh4x/facet_wrap2.py +896 -0
  22. ggh4x/geom_box.py +536 -0
  23. ggh4x/geom_outline_point.py +444 -0
  24. ggh4x/geom_pointpath.py +259 -0
  25. ggh4x/geom_polygonraster.py +252 -0
  26. ggh4x/geom_rectrug.py +489 -0
  27. ggh4x/geom_text_aimed.py +279 -0
  28. ggh4x/guide_stringlegend.py +354 -0
  29. ggh4x/help_secondary.py +549 -0
  30. ggh4x/multiscale/__init__.py +51 -0
  31. ggh4x/multiscale/_multiscale_add.py +207 -0
  32. ggh4x/multiscale/scale_listed.py +167 -0
  33. ggh4x/multiscale/scale_manual.py +478 -0
  34. ggh4x/multiscale/scale_multi.py +393 -0
  35. ggh4x/panel_scales/__init__.py +58 -0
  36. ggh4x/panel_scales/at_panel.py +115 -0
  37. ggh4x/panel_scales/facetted_pos_scales.py +647 -0
  38. ggh4x/panel_scales/force_panelsize.py +411 -0
  39. ggh4x/panel_scales/scale_facet.py +222 -0
  40. ggh4x/position_disjoint_ranges.py +229 -0
  41. ggh4x/position_lineartrans.py +242 -0
  42. ggh4x/py.typed +0 -0
  43. ggh4x/resources/faithful.csv +273 -0
  44. ggh4x/resources/iris.csv +151 -0
  45. ggh4x/resources/mtcars.csv +33 -0
  46. ggh4x/resources/pressure.csv +20 -0
  47. ggh4x/resources/volcano.csv +87 -0
  48. ggh4x/save.py +255 -0
  49. ggh4x/stat_difference.py +388 -0
  50. ggh4x/stat_funxy.py +436 -0
  51. ggh4x/stat_rle.py +290 -0
  52. ggh4x/stat_rollingkernel.py +369 -0
  53. ggh4x/stat_theodensity.py +681 -0
  54. ggh4x/strip_nested.py +448 -0
  55. ggh4x/strip_split.py +687 -0
  56. ggh4x/strip_tag.py +636 -0
  57. ggh4x/strip_themed.py +232 -0
  58. ggh4x/strip_vanilla.py +1464 -0
  59. ggh4x/themes.py +31 -0
  60. ggh4x/themes_ggh4x.py +67 -0
  61. ggh4x_python-0.3.1.9000.dist-info/METADATA +40 -0
  62. ggh4x_python-0.3.1.9000.dist-info/RECORD +64 -0
  63. ggh4x_python-0.3.1.9000.dist-info/WHEEL +4 -0
  64. ggh4x_python-0.3.1.9000.dist-info/licenses/LICENSE +3 -0
ggh4x/geom_rectrug.py ADDED
@@ -0,0 +1,489 @@
1
+ """Rectangular rugs in the margins (port of ggh4x ``geom_rectrug.R``).
2
+
3
+ Where rug plots show 1-D data as ticks in the plot margins,
4
+ ``geom_rectmargin()`` and ``geom_tilemargin()`` draw *rectangles* in the
5
+ margins. They are convenient for one-dimensional, ranged annotations on a 2-D
6
+ plot. ``geom_rectmargin()`` is parameterised by ``xmin``/``xmax`` and/or
7
+ ``ymin``/``ymax``; ``geom_tilemargin()`` is parameterised by centre ``x``/``y``
8
+ and ``width``/``height``.
9
+
10
+ R source: ``ggh4x/R/geom_rectrug.R``.
11
+
12
+ Notes
13
+ -----
14
+ * :meth:`GeomRectMargin.draw_panel` validates that ``length`` is a grid unit,
15
+ coord-transforms the data, remaps ``sides`` via ``chartr("tblr", "rlbt")``
16
+ under :class:`ggplot2_py.CoordFlip`, and builds a thin rectangle band on each
17
+ requested side using only the ``min`` of the ``rug_length`` band.
18
+ * :class:`GeomTileMargin` inherits :meth:`GeomRectMargin.draw_panel` unchanged
19
+ and only overrides :meth:`setup_data` (centre+size -> min/max, with
20
+ ``width``/``height`` falling back to ``resolution(x/y, zero=False)``) and the
21
+ default aesthetics.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Any, Optional
27
+
28
+ import numpy as np
29
+ import pandas as pd
30
+
31
+ from ggplot2_py import CoordFlip
32
+ from ggplot2_py.geom import (
33
+ GeomRug,
34
+ FromTheme,
35
+ Gpar,
36
+ GList,
37
+ GTree,
38
+ Mapping,
39
+ PT,
40
+ _coord_transform,
41
+ _mix_ink_paper,
42
+ draw_key_polygon,
43
+ rect_grob,
44
+ scales_alpha,
45
+ )
46
+ from ggplot2_py._utils import resolution
47
+ from grid_py import Unit, is_unit
48
+
49
+ from ._cli import cli_abort
50
+
51
+ __all__ = [
52
+ "geom_rectmargin",
53
+ "geom_tilemargin",
54
+ "GeomRectMargin",
55
+ "GeomTileMargin",
56
+ ]
57
+
58
+
59
+ def _alpha_na(colour: Any, alpha: Any) -> Any:
60
+ """Apply alpha to a colour spec, passing ``NA`` (``None``/``NaN``) through.
61
+
62
+ Port of R's ``scales::alpha(colour, alpha)`` ``NA``-handling: an ``NA``
63
+ colour stays ``NA`` (drawn as no-paint) instead of erroring. Scalars and
64
+ arrays are both supported; ``None`` entries inside an array are preserved.
65
+
66
+ Parameters
67
+ ----------
68
+ colour : Any
69
+ A colour scalar, array (possibly with ``None``/``NaN`` entries) or
70
+ ``None``.
71
+ alpha : Any
72
+ Alpha scalar or array.
73
+
74
+ Returns
75
+ -------
76
+ Any
77
+ The alpha-applied colour(s), with ``NA`` preserved.
78
+ """
79
+ if colour is None:
80
+ return None
81
+ arr = np.atleast_1d(np.asarray(colour, dtype=object))
82
+ if arr.shape == (1,) and (arr[0] is None or (isinstance(arr[0], float) and np.isnan(arr[0]))):
83
+ return None
84
+ is_na = np.array(
85
+ [c is None or (isinstance(c, float) and np.isnan(c)) for c in arr]
86
+ )
87
+ if not is_na.any():
88
+ return scales_alpha(colour, alpha)
89
+ if is_na.all():
90
+ return None
91
+ # Mixed: apply alpha only to the valid colours, keep None elsewhere.
92
+ out = np.empty(arr.shape, dtype=object)
93
+ valid_idx = np.flatnonzero(~is_na)
94
+ alpha_arr = np.atleast_1d(np.asarray(alpha, dtype=object))
95
+ applied = scales_alpha(
96
+ np.array([arr[i] for i in valid_idx], dtype=object),
97
+ np.array(
98
+ [alpha_arr[i % len(alpha_arr)] for i in valid_idx], dtype=object
99
+ )
100
+ if alpha is not None
101
+ else None,
102
+ )
103
+ out[:] = None
104
+ out[valid_idx] = applied
105
+ return out
106
+
107
+
108
+ class GeomRectMargin(GeomRug):
109
+ """Rectangular rug geom parameterised by ``(x/y)min``/``(x/y)max``.
110
+
111
+ Subclass of :class:`ggplot2_py.GeomRug` ported from R ``GeomRectMargin``
112
+ (``geom_rectrug.R:189-280``). It completely replaces the rug's
113
+ ``draw_panel``/``default_aes``/``draw_key`` and only inherits the missing
114
+ value / setup plumbing.
115
+ """
116
+
117
+ optional_aes = ("x", "y", "xmin", "xmax", "ymin", "ymax")
118
+
119
+ # R geom_rectrug.R:272-278.
120
+ default_aes: Mapping = Mapping(
121
+ colour=FromTheme("colour", fallback=lambda g: None),
122
+ fill=FromTheme("fill", fallback=_mix_ink_paper(0.35)),
123
+ linewidth=FromTheme("borderwidth"),
124
+ linetype=FromTheme("bordertype"),
125
+ alpha=None,
126
+ )
127
+
128
+ draw_key = draw_key_polygon
129
+
130
+ def draw_panel(
131
+ self,
132
+ data: pd.DataFrame,
133
+ panel_params: Any,
134
+ coord: Any,
135
+ sides: str = "bl",
136
+ outside: bool = False,
137
+ length: Any = None,
138
+ linejoin: str = "mitre",
139
+ **params: Any,
140
+ ) -> Any:
141
+ """Build the margin rectangles for one panel.
142
+
143
+ Port of R ``GeomRectMargin$draw_panel`` (``geom_rectrug.R:191-270``).
144
+
145
+ Parameters
146
+ ----------
147
+ data : pandas.DataFrame
148
+ Layer data (``xmin``/``xmax`` and/or ``ymin``/``ymax``).
149
+ panel_params : Any
150
+ Panel scales / ranges.
151
+ coord : Any
152
+ Active coordinate system.
153
+ sides : str, default ``"bl"``
154
+ Which margins to draw on (any of ``"trbl"``).
155
+ outside : bool, default ``False``
156
+ Whether to move the rectangles outside the plot area.
157
+ length : grid_py.Unit, optional
158
+ Rectangle thickness (defaults to ``Unit(0.03, "npc")``).
159
+ linejoin : str, default ``"mitre"``
160
+ Line join style.
161
+ **params : Any
162
+ Ignored extra parameters.
163
+
164
+ Returns
165
+ -------
166
+ grid_py.GTree
167
+ A tree of the per-side rectangle grobs.
168
+
169
+ Raises
170
+ ------
171
+ TypeError
172
+ If ``length`` is not a grid unit.
173
+ """
174
+ if length is None:
175
+ length = Unit(0.03, "npc")
176
+ if not is_unit(length):
177
+ cli_abort(
178
+ "The `length` argument must be a `unit` object.",
179
+ error_class=TypeError,
180
+ )
181
+
182
+ coords = _coord_transform(coord, data, panel_params)
183
+ if isinstance(coord, CoordFlip):
184
+ sides = sides.translate(str.maketrans("tblr", "rlbt"))
185
+
186
+ if not outside:
187
+ rug_min = length
188
+ else:
189
+ rug_min = -1 * length
190
+
191
+ colour = coords["colour"].to_numpy() if "colour" in coords.columns else None
192
+ fill = coords["fill"].to_numpy() if "fill" in coords.columns else "grey35"
193
+ alpha = coords["alpha"].to_numpy() if "alpha" in coords.columns else None
194
+ linetype = coords["linetype"].to_numpy() if "linetype" in coords.columns else 1
195
+ linewidth = (
196
+ coords["linewidth"].to_numpy(dtype="float64")
197
+ if "linewidth" in coords.columns
198
+ else np.full(len(coords), 0.5)
199
+ )
200
+
201
+ gp = Gpar(
202
+ col=_alpha_na(colour, alpha),
203
+ fill=_alpha_na(fill, alpha),
204
+ linejoin=linejoin,
205
+ lty=linetype,
206
+ lwd=linewidth * PT,
207
+ lineend="round" if linejoin == "round" else "square",
208
+ )
209
+
210
+ rugs = []
211
+ have_x = "xmin" in coords.columns and "xmax" in coords.columns
212
+ have_y = "ymin" in coords.columns and "ymax" in coords.columns
213
+
214
+ if have_x:
215
+ xmin = coords["xmin"].to_numpy(dtype="float64")
216
+ xmax = coords["xmax"].to_numpy(dtype="float64")
217
+ xwidth = xmax - xmin
218
+ if "b" in sides:
219
+ rugs.append(
220
+ rect_grob(
221
+ x=Unit(xmin, "native"),
222
+ y=Unit(0.0, "npc"),
223
+ width=Unit(xwidth, "native"),
224
+ height=rug_min,
225
+ just=("left", "bottom"),
226
+ gp=gp,
227
+ )
228
+ )
229
+ if "t" in sides:
230
+ rugs.append(
231
+ rect_grob(
232
+ x=Unit(xmin, "native"),
233
+ y=Unit(1.0, "npc"),
234
+ width=Unit(xwidth, "native"),
235
+ height=rug_min,
236
+ just=("left", "top"),
237
+ gp=gp,
238
+ )
239
+ )
240
+
241
+ if have_y:
242
+ ymin = coords["ymin"].to_numpy(dtype="float64")
243
+ ymax = coords["ymax"].to_numpy(dtype="float64")
244
+ yheight = ymax - ymin
245
+ if "l" in sides:
246
+ rugs.append(
247
+ rect_grob(
248
+ x=Unit(0.0, "npc"),
249
+ y=Unit(ymax, "native"),
250
+ width=rug_min,
251
+ height=Unit(yheight, "native"),
252
+ just=("left", "top"),
253
+ gp=gp,
254
+ )
255
+ )
256
+ if "r" in sides:
257
+ rugs.append(
258
+ rect_grob(
259
+ x=Unit(1.0, "npc"),
260
+ y=Unit(ymax, "native"),
261
+ width=rug_min,
262
+ height=Unit(yheight, "native"),
263
+ just=("right", "top"),
264
+ gp=gp,
265
+ )
266
+ )
267
+
268
+ return GTree(children=GList(*rugs))
269
+
270
+
271
+ class GeomTileMargin(GeomRectMargin):
272
+ """Rectangular rug geom parameterised by centre + size.
273
+
274
+ Subclass of :class:`GeomRectMargin` ported from R ``GeomTileMargin``
275
+ (``geom_rectrug.R:286-309``). Inherits :meth:`GeomRectMargin.draw_panel`
276
+ unchanged.
277
+ """
278
+
279
+ extra_params = ("na_rm",)
280
+
281
+ # R geom_rectrug.R:299-307.
282
+ default_aes: Mapping = Mapping(
283
+ fill=FromTheme("fill", fallback=_mix_ink_paper(0.2)),
284
+ colour=FromTheme("colour", fallback=lambda g: None),
285
+ linewidth=FromTheme("borderwidth", fallback=lambda g: g.borderwidth / 5),
286
+ linetype=FromTheme("bordertype"),
287
+ alpha=None,
288
+ width=None,
289
+ height=None,
290
+ )
291
+
292
+ def setup_data(self, data: pd.DataFrame, params: dict) -> pd.DataFrame:
293
+ """Derive ``xmin``/``xmax``/``ymin``/``ymax`` from centre + size.
294
+
295
+ Port of R ``GeomTileMargin$setup_data`` (``geom_rectrug.R:290-297``).
296
+ ``width``/``height`` fall back to ``params`` then
297
+ ``resolution(x/y, zero=False)``.
298
+
299
+ Parameters
300
+ ----------
301
+ data : pandas.DataFrame
302
+ Layer data with centre ``x``/``y`` and optional ``width``/
303
+ ``height``.
304
+ params : dict
305
+ Layer parameters.
306
+
307
+ Returns
308
+ -------
309
+ pandas.DataFrame
310
+ Data with the four corner columns set and ``width``/``height``
311
+ dropped.
312
+ """
313
+ data = data.copy()
314
+
315
+ def _resolved(col: str, axis: str) -> Optional[np.ndarray]:
316
+ if col in data.columns and not data[col].isna().all():
317
+ return data[col].to_numpy(dtype="float64")
318
+ pval = params.get(col)
319
+ if pval is not None:
320
+ return np.full(len(data), float(pval))
321
+ if axis in data.columns:
322
+ return np.full(len(data), resolution(data[axis].to_numpy(dtype="float64"), zero=False))
323
+ return None
324
+
325
+ if "x" in data.columns:
326
+ width = _resolved("width", "x")
327
+ x = data["x"].to_numpy(dtype="float64")
328
+ data["xmin"] = x - width / 2
329
+ data["xmax"] = x + width / 2
330
+ if "y" in data.columns:
331
+ height = _resolved("height", "y")
332
+ y = data["y"].to_numpy(dtype="float64")
333
+ data["ymin"] = y - height / 2
334
+ data["ymax"] = y + height / 2
335
+
336
+ for drop in ("width", "height"):
337
+ if drop in data.columns:
338
+ data = data.drop(columns=drop)
339
+ return data
340
+
341
+ draw_key = draw_key_polygon
342
+
343
+
344
+ def geom_rectmargin(
345
+ mapping: Optional[Mapping] = None,
346
+ data: Any = None,
347
+ stat: str = "identity",
348
+ position: str = "identity",
349
+ outside: bool = False,
350
+ sides: str = "bl",
351
+ length: Any = None,
352
+ linejoin: str = "mitre",
353
+ na_rm: bool = False,
354
+ show_legend: Any = None,
355
+ inherit_aes: bool = True,
356
+ **kwargs: Any,
357
+ ) -> Any:
358
+ """Create a rectangular margin-rug layer parameterised by min/max.
359
+
360
+ Port of R ``geom_rectmargin()`` (``geom_rectrug.R:117-147``).
361
+
362
+ Parameters
363
+ ----------
364
+ mapping : Mapping, optional
365
+ Aesthetic mapping created by :func:`ggplot2_py.aes`.
366
+ data : Any, optional
367
+ Layer data.
368
+ stat : str, default ``"identity"``
369
+ Statistical transformation.
370
+ position : str, default ``"identity"``
371
+ Position adjustment.
372
+ outside : bool, default ``False``
373
+ Whether to move the rectangles outside the plot area.
374
+ sides : str, default ``"bl"``
375
+ Which margins to draw on (any of ``"trbl"``).
376
+ length : grid_py.Unit, optional
377
+ Rectangle thickness (defaults to ``Unit(0.03, "npc")``).
378
+ linejoin : str, default ``"mitre"``
379
+ Line join style.
380
+ na_rm : bool, default ``False``
381
+ If ``True``, silently remove missing values.
382
+ show_legend : bool or None, default ``None``
383
+ Whether to show a legend for this layer.
384
+ inherit_aes : bool, default ``True``
385
+ Whether to inherit the plot's default aesthetics.
386
+ **kwargs : Any
387
+ Additional aesthetic parameters passed to the layer.
388
+
389
+ Returns
390
+ -------
391
+ ggplot2_py.Layer
392
+ A layer object that can be added to a plot.
393
+ """
394
+ from ggplot2_py.layer import layer
395
+
396
+ if length is None:
397
+ length = Unit(0.03, "npc")
398
+
399
+ return layer(
400
+ data=data,
401
+ mapping=mapping,
402
+ stat=stat,
403
+ geom=GeomRectMargin,
404
+ position=position,
405
+ show_legend=show_legend,
406
+ inherit_aes=inherit_aes,
407
+ params={
408
+ "outside": outside,
409
+ "sides": sides,
410
+ "length": length,
411
+ "linejoin": linejoin,
412
+ "na_rm": na_rm,
413
+ **kwargs,
414
+ },
415
+ )
416
+
417
+
418
+ def geom_tilemargin(
419
+ mapping: Optional[Mapping] = None,
420
+ data: Any = None,
421
+ stat: str = "identity",
422
+ position: str = "identity",
423
+ outside: bool = False,
424
+ sides: str = "bl",
425
+ length: Any = None,
426
+ linejoin: str = "mitre",
427
+ na_rm: bool = False,
428
+ show_legend: Any = None,
429
+ inherit_aes: bool = True,
430
+ **kwargs: Any,
431
+ ) -> Any:
432
+ """Create a rectangular margin-rug layer parameterised by centre + size.
433
+
434
+ Port of R ``geom_tilemargin()`` (``geom_rectrug.R:151-181``).
435
+
436
+ Parameters
437
+ ----------
438
+ mapping : Mapping, optional
439
+ Aesthetic mapping created by :func:`ggplot2_py.aes`.
440
+ data : Any, optional
441
+ Layer data.
442
+ stat : str, default ``"identity"``
443
+ Statistical transformation.
444
+ position : str, default ``"identity"``
445
+ Position adjustment.
446
+ outside : bool, default ``False``
447
+ Whether to move the rectangles outside the plot area.
448
+ sides : str, default ``"bl"``
449
+ Which margins to draw on (any of ``"trbl"``).
450
+ length : grid_py.Unit, optional
451
+ Rectangle thickness (defaults to ``Unit(0.03, "npc")``).
452
+ linejoin : str, default ``"mitre"``
453
+ Line join style.
454
+ na_rm : bool, default ``False``
455
+ If ``True``, silently remove missing values.
456
+ show_legend : bool or None, default ``None``
457
+ Whether to show a legend for this layer.
458
+ inherit_aes : bool, default ``True``
459
+ Whether to inherit the plot's default aesthetics.
460
+ **kwargs : Any
461
+ Additional aesthetic parameters passed to the layer.
462
+
463
+ Returns
464
+ -------
465
+ ggplot2_py.Layer
466
+ A layer object that can be added to a plot.
467
+ """
468
+ from ggplot2_py.layer import layer
469
+
470
+ if length is None:
471
+ length = Unit(0.03, "npc")
472
+
473
+ return layer(
474
+ data=data,
475
+ mapping=mapping,
476
+ stat=stat,
477
+ geom=GeomTileMargin,
478
+ position=position,
479
+ show_legend=show_legend,
480
+ inherit_aes=inherit_aes,
481
+ params={
482
+ "outside": outside,
483
+ "sides": sides,
484
+ "length": length,
485
+ "linejoin": linejoin,
486
+ "na_rm": na_rm,
487
+ **kwargs,
488
+ },
489
+ )