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.
- ggh4x/__init__.py +140 -0
- ggh4x/_aimed_text_grob.py +432 -0
- ggh4x/_borrowed_ggplot2.py +273 -0
- ggh4x/_cli.py +84 -0
- ggh4x/_datasets.py +106 -0
- ggh4x/_download.py +111 -0
- ggh4x/_facet_helpers.py +313 -0
- ggh4x/_facet_utils.py +649 -0
- ggh4x/_gap_grobs.py +606 -0
- ggh4x/_registry.py +10 -0
- ggh4x/_rlang.py +93 -0
- ggh4x/_utils.py +150 -0
- ggh4x/_vctrs.py +233 -0
- ggh4x/conveniences.py +601 -0
- ggh4x/coord_axes_inside.py +380 -0
- ggh4x/element_part_rect.py +545 -0
- ggh4x/facet_grid2.py +1018 -0
- ggh4x/facet_manual.py +901 -0
- ggh4x/facet_nested.py +776 -0
- ggh4x/facet_nested_wrap.py +193 -0
- ggh4x/facet_wrap2.py +896 -0
- ggh4x/geom_box.py +536 -0
- ggh4x/geom_outline_point.py +444 -0
- ggh4x/geom_pointpath.py +259 -0
- ggh4x/geom_polygonraster.py +252 -0
- ggh4x/geom_rectrug.py +489 -0
- ggh4x/geom_text_aimed.py +279 -0
- ggh4x/guide_stringlegend.py +354 -0
- ggh4x/help_secondary.py +549 -0
- ggh4x/multiscale/__init__.py +51 -0
- ggh4x/multiscale/_multiscale_add.py +207 -0
- ggh4x/multiscale/scale_listed.py +167 -0
- ggh4x/multiscale/scale_manual.py +478 -0
- ggh4x/multiscale/scale_multi.py +393 -0
- ggh4x/panel_scales/__init__.py +58 -0
- ggh4x/panel_scales/at_panel.py +115 -0
- ggh4x/panel_scales/facetted_pos_scales.py +647 -0
- ggh4x/panel_scales/force_panelsize.py +411 -0
- ggh4x/panel_scales/scale_facet.py +222 -0
- ggh4x/position_disjoint_ranges.py +229 -0
- ggh4x/position_lineartrans.py +242 -0
- ggh4x/py.typed +0 -0
- ggh4x/resources/faithful.csv +273 -0
- ggh4x/resources/iris.csv +151 -0
- ggh4x/resources/mtcars.csv +33 -0
- ggh4x/resources/pressure.csv +20 -0
- ggh4x/resources/volcano.csv +87 -0
- ggh4x/save.py +255 -0
- ggh4x/stat_difference.py +388 -0
- ggh4x/stat_funxy.py +436 -0
- ggh4x/stat_rle.py +290 -0
- ggh4x/stat_rollingkernel.py +369 -0
- ggh4x/stat_theodensity.py +681 -0
- ggh4x/strip_nested.py +448 -0
- ggh4x/strip_split.py +687 -0
- ggh4x/strip_tag.py +636 -0
- ggh4x/strip_themed.py +232 -0
- ggh4x/strip_vanilla.py +1464 -0
- ggh4x/themes.py +31 -0
- ggh4x/themes_ggh4x.py +67 -0
- ggh4x_python-0.3.1.9000.dist-info/METADATA +40 -0
- ggh4x_python-0.3.1.9000.dist-info/RECORD +64 -0
- ggh4x_python-0.3.1.9000.dist-info/WHEEL +4 -0
- ggh4x_python-0.3.1.9000.dist-info/licenses/LICENSE +3 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
"""Partial rectangle theme element.
|
|
2
|
+
|
|
3
|
+
Port of ggh4x R source ``element_part_rect.R``.
|
|
4
|
+
|
|
5
|
+
The :func:`element_part_rect` factory draws individual sides of a rectangle as a
|
|
6
|
+
theme element, substituting :func:`ggplot2_py.theme_elements.element_rect`. A
|
|
7
|
+
``side`` string built from the letters ``"t"`` (top), ``"l"`` (left), ``"b"``
|
|
8
|
+
(bottom) and ``"r"`` (right) selects which borders are drawn.
|
|
9
|
+
|
|
10
|
+
Components ported from the R file
|
|
11
|
+
---------------------------------
|
|
12
|
+
* :class:`ElementPartRect` -- subclass of ``ElementRect`` carrying a ``side`` slot
|
|
13
|
+
(the R S3 class vector ``c("element_part_rect", "element_rect", "element")``).
|
|
14
|
+
* :func:`element_part_rect` -- polymorphic factory (R L29-78). All four sides ->
|
|
15
|
+
plain ``element_rect``; no sides -> ``element_rect(colour=NA)``; otherwise an
|
|
16
|
+
``ElementPartRect``.
|
|
17
|
+
* :func:`_grob_from_part_rect` -- the ``element_grob.element_part_rect`` S3 method
|
|
18
|
+
(R L82-109): merges caller graphical parameters over the element's own and
|
|
19
|
+
delegates to :func:`part_rect_grob`.
|
|
20
|
+
* :func:`part_rect_grob` -- ``partrectGrob`` (R L111-243): a grob tree of a
|
|
21
|
+
fill-only rectangle plus a segments grob drawing the requested sides.
|
|
22
|
+
* :func:`element_grob` -- a ggh4x-local wrapper around
|
|
23
|
+
``ggplot2_py.theme_elements.element_grob`` that intercepts ``ElementPartRect``
|
|
24
|
+
(which is-a ``ElementRect``) *before* the upstream ``ElementRect`` branch.
|
|
25
|
+
|
|
26
|
+
Notes
|
|
27
|
+
-----
|
|
28
|
+
``ggplot2_py.theme_elements.element_grob`` is a hard ``isinstance`` chain rather
|
|
29
|
+
than R-style S3 dispatch, so the wrapper here adds the missing
|
|
30
|
+
``ElementPartRect`` branch. Importing this module also monkeypatches the
|
|
31
|
+
upstream ``element_grob`` so any code path that dispatches an ``ElementPartRect``
|
|
32
|
+
through the original function still renders partial borders.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import re
|
|
38
|
+
from typing import Any, List, Optional, Union
|
|
39
|
+
|
|
40
|
+
from grid_py import (
|
|
41
|
+
Gpar,
|
|
42
|
+
Unit,
|
|
43
|
+
grob_tree,
|
|
44
|
+
is_unit,
|
|
45
|
+
rect_grob,
|
|
46
|
+
segments_grob,
|
|
47
|
+
unit_c,
|
|
48
|
+
unit_rep,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
import ggplot2_py.theme_elements as _te
|
|
52
|
+
from ggplot2_py._compat import NA, is_na
|
|
53
|
+
from ggplot2_py.theme_elements import ElementRect, element_rect
|
|
54
|
+
from ggplot2_py.theme_elements import _PT # 72.27 / 25.4 == R's .pt
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"ElementPartRect",
|
|
58
|
+
"element_part_rect",
|
|
59
|
+
"part_rect_grob",
|
|
60
|
+
"element_grob",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Class
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ElementPartRect(ElementRect):
|
|
70
|
+
"""Theme element drawing partial rectangle borders.
|
|
71
|
+
|
|
72
|
+
Subclasses :class:`ggplot2_py.theme_elements.ElementRect` so existing theme
|
|
73
|
+
inheritance (``combine_elements`` / ``merge_element``) treats it like a rect
|
|
74
|
+
for ``strip.background`` / ``panel.background``. Adds a ``side`` slot
|
|
75
|
+
selecting which borders to draw.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
fill : str or None
|
|
80
|
+
Fill colour.
|
|
81
|
+
colour : str or None
|
|
82
|
+
Border colour.
|
|
83
|
+
linewidth : float or None
|
|
84
|
+
Border width in millimetres.
|
|
85
|
+
linetype : int, str, or None
|
|
86
|
+
Border line type.
|
|
87
|
+
inherit_blank : bool
|
|
88
|
+
Whether to inherit ``element_blank`` from parents.
|
|
89
|
+
side : str
|
|
90
|
+
Any combination of ``"t"``, ``"l"``, ``"b"``, ``"r"`` selecting the
|
|
91
|
+
top, left, bottom and right borders respectively.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
fill: Optional[str] = None,
|
|
97
|
+
colour: Optional[str] = None,
|
|
98
|
+
linewidth: Optional[float] = None,
|
|
99
|
+
linetype: Optional[Union[int, str]] = None,
|
|
100
|
+
inherit_blank: bool = False,
|
|
101
|
+
side: str = "tlbr",
|
|
102
|
+
) -> None:
|
|
103
|
+
super().__init__(
|
|
104
|
+
fill=fill,
|
|
105
|
+
colour=colour,
|
|
106
|
+
linewidth=linewidth,
|
|
107
|
+
linetype=linetype,
|
|
108
|
+
inherit_blank=inherit_blank,
|
|
109
|
+
)
|
|
110
|
+
self.side = side
|
|
111
|
+
|
|
112
|
+
def __repr__(self) -> str: # pragma: no cover - cosmetic
|
|
113
|
+
parts = []
|
|
114
|
+
for attr in ("fill", "colour", "linewidth", "linetype", "side", "inherit_blank"):
|
|
115
|
+
val = getattr(self, attr)
|
|
116
|
+
if val is not None and val is not False:
|
|
117
|
+
parts.append(f"{attr}={val!r}")
|
|
118
|
+
return f"element_part_rect({', '.join(parts)})"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Factory (R L29-78)
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def element_part_rect(
|
|
127
|
+
side: str = "tlbr",
|
|
128
|
+
fill: Optional[str] = None,
|
|
129
|
+
colour: Optional[str] = None,
|
|
130
|
+
linewidth: Optional[float] = None,
|
|
131
|
+
linetype: Optional[Union[int, str]] = None,
|
|
132
|
+
color: Optional[str] = None,
|
|
133
|
+
inherit_blank: bool = False,
|
|
134
|
+
) -> ElementRect:
|
|
135
|
+
"""Construct a partial-rectangle theme element.
|
|
136
|
+
|
|
137
|
+
Polymorphic factory mirroring R ``element_part_rect`` (L29-78):
|
|
138
|
+
|
|
139
|
+
* If ``side`` contains all of ``t``, ``l``, ``b`` and ``r`` the element
|
|
140
|
+
simplifies to a plain :func:`ggplot2_py.theme_elements.element_rect`.
|
|
141
|
+
* If ``side`` contains none of those letters it simplifies to
|
|
142
|
+
``element_rect(colour=NA, ...)`` (a borderless rectangle).
|
|
143
|
+
* Otherwise an :class:`ElementPartRect` is returned.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
side : str
|
|
148
|
+
Any combination of ``"t"``, ``"l"``, ``"b"``, ``"r"``. Including all
|
|
149
|
+
or none of these letters defaults to a regular ``element_rect()``.
|
|
150
|
+
fill : str or None
|
|
151
|
+
Fill colour.
|
|
152
|
+
colour : str or None
|
|
153
|
+
Border colour.
|
|
154
|
+
linewidth : float or None
|
|
155
|
+
Line/border size in millimetres.
|
|
156
|
+
linetype : int, str, or None
|
|
157
|
+
Line type: an integer (0--8), a name, or a hex dash string.
|
|
158
|
+
color : str or None
|
|
159
|
+
American-spelling alias for ``colour``; overrides ``colour`` when given.
|
|
160
|
+
inherit_blank : bool
|
|
161
|
+
Whether to inherit ``element_blank`` from parents.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
ElementRect or ElementPartRect
|
|
166
|
+
A plain ``ElementRect`` for the all-sides / no-sides cases, otherwise
|
|
167
|
+
an ``ElementPartRect``.
|
|
168
|
+
"""
|
|
169
|
+
if color is not None:
|
|
170
|
+
colour = color
|
|
171
|
+
|
|
172
|
+
# R: grepl("(?=.*t)(?=.*l)(?=.*r)(?=.*b)", side, perl = TRUE)
|
|
173
|
+
all_sides = re.search(r"(?=.*t)(?=.*l)(?=.*r)(?=.*b)", side) is not None
|
|
174
|
+
if all_sides:
|
|
175
|
+
# Simplifies to regular rectangle.
|
|
176
|
+
return element_rect(
|
|
177
|
+
fill=fill,
|
|
178
|
+
colour=colour,
|
|
179
|
+
linewidth=linewidth,
|
|
180
|
+
linetype=linetype,
|
|
181
|
+
inherit_blank=inherit_blank,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# R: !grepl("t|l|r|b", side, perl = TRUE)
|
|
185
|
+
no_sides = re.search(r"[tlrb]", side) is None
|
|
186
|
+
if no_sides:
|
|
187
|
+
# Also simplifies to a regular rectangle, but with no colour.
|
|
188
|
+
return element_rect(
|
|
189
|
+
fill=fill,
|
|
190
|
+
colour=NA,
|
|
191
|
+
linewidth=linewidth,
|
|
192
|
+
linetype=linetype,
|
|
193
|
+
inherit_blank=inherit_blank,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return ElementPartRect(
|
|
197
|
+
fill=fill,
|
|
198
|
+
colour=colour,
|
|
199
|
+
linewidth=linewidth,
|
|
200
|
+
linetype=linetype,
|
|
201
|
+
inherit_blank=inherit_blank,
|
|
202
|
+
side=side,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# gpar helpers
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _gpar_from_fields(
|
|
212
|
+
lwd: Any = None,
|
|
213
|
+
col: Any = None,
|
|
214
|
+
fill: Any = None,
|
|
215
|
+
lty: Any = None,
|
|
216
|
+
) -> dict:
|
|
217
|
+
"""Build a gpar-parameter dict applying R ``gpar(...)`` NULL-dropping.
|
|
218
|
+
|
|
219
|
+
R ``gpar(lwd = NULL, col = colour, ...)`` silently drops any ``NULL``
|
|
220
|
+
argument. ``NA`` is retained (e.g. ``gpar(col = NA)``) and forwarded as
|
|
221
|
+
grid_py's ``None`` NA sentinel. This helper returns a plain ``dict`` so
|
|
222
|
+
callers can further merge/strip fields before constructing a
|
|
223
|
+
:class:`grid_py.Gpar`.
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
lwd, col, fill, lty : Any
|
|
228
|
+
Candidate graphical-parameter values. ``None`` mirrors R ``NULL``
|
|
229
|
+
(drop the field); :data:`ggplot2_py._compat.NA` maps to grid_py's
|
|
230
|
+
``None`` NA sentinel (retain the field, draw nothing).
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
dict
|
|
235
|
+
Parameter dict with ``None`` (NULL) entries omitted. ``NA`` entries
|
|
236
|
+
are stored as Python ``None`` (grid_py NA sentinel).
|
|
237
|
+
"""
|
|
238
|
+
out: dict = {}
|
|
239
|
+
for key, value in (("lwd", lwd), ("col", col), ("fill", fill), ("lty", lty)):
|
|
240
|
+
if value is None:
|
|
241
|
+
continue # R NULL -> drop entirely
|
|
242
|
+
if is_na(value):
|
|
243
|
+
out[key] = None # R NA -> grid_py NA sentinel
|
|
244
|
+
else:
|
|
245
|
+
out[key] = value
|
|
246
|
+
return out
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _strip_field(params: dict, field: str) -> Gpar:
|
|
250
|
+
"""Return a :class:`grid_py.Gpar` with ``field`` forced to the NA sentinel.
|
|
251
|
+
|
|
252
|
+
Mirrors R ``do.call(gpar, within(gp, col <- NA))`` (and the ``fill`` twin):
|
|
253
|
+
the named field is *set* to ``NA`` (kept, value NA) while every other field
|
|
254
|
+
is preserved unchanged.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
params : dict
|
|
259
|
+
Source parameter dict (already NULL-dropped).
|
|
260
|
+
field : str
|
|
261
|
+
Field to force to ``NA`` -- ``"col"`` or ``"fill"``.
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
grid_py.Gpar
|
|
266
|
+
The graphical parameters with ``field`` set to grid_py's ``None`` NA
|
|
267
|
+
sentinel (renders nothing for that aesthetic).
|
|
268
|
+
"""
|
|
269
|
+
merged = dict(params)
|
|
270
|
+
merged[field] = None # grid_py NA sentinel (== R NA)
|
|
271
|
+
return Gpar(**merged)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
# element_grob.element_part_rect (R L82-109)
|
|
276
|
+
# ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _grob_from_part_rect(
|
|
280
|
+
element: ElementPartRect,
|
|
281
|
+
x: Any = 0.5,
|
|
282
|
+
y: Any = 0.5,
|
|
283
|
+
width: Any = 1,
|
|
284
|
+
height: Any = 1,
|
|
285
|
+
fill: Optional[str] = None,
|
|
286
|
+
colour: Optional[str] = None,
|
|
287
|
+
linewidth: Optional[float] = None,
|
|
288
|
+
linetype: Optional[Union[int, str]] = None,
|
|
289
|
+
**kwargs: Any,
|
|
290
|
+
) -> Any:
|
|
291
|
+
"""Render an :class:`ElementPartRect` as a grob.
|
|
292
|
+
|
|
293
|
+
Port of R ``element_grob.element_part_rect`` (L82-109). Builds a caller
|
|
294
|
+
graphical-parameter set and an element graphical-parameter set, then lets
|
|
295
|
+
the caller values override the element values field-by-field before calling
|
|
296
|
+
:func:`part_rect_grob`.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
element : ElementPartRect
|
|
301
|
+
The element being rendered (supplies ``side`` plus default
|
|
302
|
+
fill/colour/linewidth/linetype).
|
|
303
|
+
x, y : Unit or numeric
|
|
304
|
+
Rectangle centre. Default ``0.5`` npc.
|
|
305
|
+
width, height : Unit or numeric
|
|
306
|
+
Rectangle size. Default ``1`` npc.
|
|
307
|
+
fill : str or None
|
|
308
|
+
Caller fill override (millimetre-independent).
|
|
309
|
+
colour : str or None
|
|
310
|
+
Caller border-colour override.
|
|
311
|
+
linewidth : float or None
|
|
312
|
+
Caller border width in millimetres (converted to ``lwd`` via ``_PT``).
|
|
313
|
+
linetype : int, str, or None
|
|
314
|
+
Caller line-type override.
|
|
315
|
+
**kwargs
|
|
316
|
+
Forwarded to :func:`part_rect_grob` (e.g. ``name``, ``vp``,
|
|
317
|
+
``default_units``).
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
grid_py.GTree
|
|
322
|
+
A grob tree drawing the requested borders.
|
|
323
|
+
"""
|
|
324
|
+
# R: caller gp = gpar(lwd = linewidth * .pt or NULL, col, fill, lty)
|
|
325
|
+
caller = _gpar_from_fields(
|
|
326
|
+
lwd=(linewidth * _PT) if linewidth is not None else None,
|
|
327
|
+
col=colour,
|
|
328
|
+
fill=fill,
|
|
329
|
+
lty=linetype,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# R: element_gp = gpar(lwd = element$linewidth * .pt or NULL, ...)
|
|
333
|
+
el_lw = element.linewidth
|
|
334
|
+
element_gp = _gpar_from_fields(
|
|
335
|
+
lwd=(el_lw * _PT) if el_lw is not None else None,
|
|
336
|
+
col=element.colour,
|
|
337
|
+
fill=element.fill,
|
|
338
|
+
lty=element.linetype,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# R: for (i in names(gp)) element_gp[[i]] <- gp[[i]] (caller overrides)
|
|
342
|
+
for key, value in caller.items():
|
|
343
|
+
element_gp[key] = value
|
|
344
|
+
|
|
345
|
+
return part_rect_grob(
|
|
346
|
+
x,
|
|
347
|
+
y,
|
|
348
|
+
width,
|
|
349
|
+
height,
|
|
350
|
+
gp=element_gp,
|
|
351
|
+
sides=element.side,
|
|
352
|
+
**kwargs,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# ---------------------------------------------------------------------------
|
|
357
|
+
# partrectGrob (R L111-243)
|
|
358
|
+
# ---------------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _as_unit(value: Any, default_units: str) -> Unit:
|
|
362
|
+
"""Coerce *value* to a :class:`grid_py.Unit` (R ``if (!is.unit(x)) unit(...)``)."""
|
|
363
|
+
if is_unit(value):
|
|
364
|
+
return value
|
|
365
|
+
return Unit(value, default_units)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Side geometry: factors of (x, width) for x0/x1 and (y, height) for y0/y1.
|
|
369
|
+
# Each tuple is (wx0, wx1, hy0, hy1) where the endpoint is
|
|
370
|
+
# x0 = x + wx0 * width, x1 = x + wx1 * width,
|
|
371
|
+
# y0 = y + hy0 * height, y1 = y + hy1 * height.
|
|
372
|
+
# Order of evaluation is fixed t -> b -> l -> r regardless of the input string
|
|
373
|
+
# (mirrors R's sequence of grepl blocks, L144-230).
|
|
374
|
+
_SIDE_SPECS = (
|
|
375
|
+
("t", (-0.5, 0.5, 0.5, 0.5)),
|
|
376
|
+
("b", (-0.5, 0.5, -0.5, -0.5)),
|
|
377
|
+
("l", (-0.5, -0.5, -0.5, 0.5)),
|
|
378
|
+
("r", (0.5, 0.5, -0.5, 0.5)),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def part_rect_grob(
|
|
383
|
+
x: Any = None,
|
|
384
|
+
y: Any = None,
|
|
385
|
+
width: Any = None,
|
|
386
|
+
height: Any = None,
|
|
387
|
+
default_units: str = "npc",
|
|
388
|
+
name: Optional[str] = None,
|
|
389
|
+
gp: Optional[Union[Gpar, dict]] = None,
|
|
390
|
+
vp: Optional[Any] = None,
|
|
391
|
+
sides: str = "tlbr",
|
|
392
|
+
) -> Any:
|
|
393
|
+
"""Build a grob tree drawing selected rectangle borders.
|
|
394
|
+
|
|
395
|
+
Port of R ``partrectGrob`` (L111-243). Produces a :class:`grid_py.GTree`
|
|
396
|
+
containing (1) a fill-only rectangle (border stripped) drawn first, and (2)
|
|
397
|
+
a segments grob (fill stripped) drawing only the requested sides, drawn on
|
|
398
|
+
top.
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
x, y : Unit or numeric, optional
|
|
403
|
+
Rectangle centre. Defaults to ``0.5`` npc.
|
|
404
|
+
width, height : Unit or numeric, optional
|
|
405
|
+
Rectangle size. Defaults to ``1`` npc.
|
|
406
|
+
default_units : str
|
|
407
|
+
Unit type applied to bare numerics. Default ``"npc"``.
|
|
408
|
+
name : str or None
|
|
409
|
+
Name for the returned grob tree.
|
|
410
|
+
gp : grid_py.Gpar or dict or None
|
|
411
|
+
Graphical parameters. A dict is accepted and treated like R's
|
|
412
|
+
unclassed gpar list.
|
|
413
|
+
vp : object or None
|
|
414
|
+
Optional viewport (applied to the children and the tree, matching R).
|
|
415
|
+
sides : str
|
|
416
|
+
Any combination of ``"t"``, ``"l"``, ``"b"``, ``"r"`` selecting which
|
|
417
|
+
borders to draw.
|
|
418
|
+
|
|
419
|
+
Returns
|
|
420
|
+
-------
|
|
421
|
+
grid_py.GTree
|
|
422
|
+
``grob_tree(fillgrob, sidegrob, name=name, vp=vp)``.
|
|
423
|
+
"""
|
|
424
|
+
if x is None:
|
|
425
|
+
x = Unit(0.5, "npc")
|
|
426
|
+
if y is None:
|
|
427
|
+
y = Unit(0.5, "npc")
|
|
428
|
+
if width is None:
|
|
429
|
+
width = Unit(1, "npc")
|
|
430
|
+
if height is None:
|
|
431
|
+
height = Unit(1, "npc")
|
|
432
|
+
|
|
433
|
+
x = _as_unit(x, default_units)
|
|
434
|
+
y = _as_unit(y, default_units)
|
|
435
|
+
width = _as_unit(width, default_units)
|
|
436
|
+
height = _as_unit(height, default_units)
|
|
437
|
+
|
|
438
|
+
# R: gp <- unclass(gp) -- normalise to a plain param dict.
|
|
439
|
+
if gp is None:
|
|
440
|
+
params: dict = {}
|
|
441
|
+
elif isinstance(gp, Gpar):
|
|
442
|
+
params = gp.params
|
|
443
|
+
elif isinstance(gp, dict):
|
|
444
|
+
params = dict(gp)
|
|
445
|
+
else: # pragma: no cover - defensive
|
|
446
|
+
raise TypeError(f"gp must be a Gpar, dict, or None, got {type(gp)!r}")
|
|
447
|
+
|
|
448
|
+
# R: rectfill = rectGrob(..., gp = do.call(gpar, within(gp, col <- NA)))
|
|
449
|
+
rectfill = rect_grob(
|
|
450
|
+
x=x,
|
|
451
|
+
y=y,
|
|
452
|
+
width=width,
|
|
453
|
+
height=height,
|
|
454
|
+
default_units=default_units,
|
|
455
|
+
name="fillgrob",
|
|
456
|
+
vp=vp,
|
|
457
|
+
gp=_strip_field(params, "col"),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# n = max(length(x), length(width), length(y), length(height))
|
|
461
|
+
n = max(len(x), len(width), len(y), len(height))
|
|
462
|
+
|
|
463
|
+
x0: Optional[Unit] = None
|
|
464
|
+
x1: Optional[Unit] = None
|
|
465
|
+
y0: Optional[Unit] = None
|
|
466
|
+
y1: Optional[Unit] = None
|
|
467
|
+
|
|
468
|
+
for letter, (wx0, wx1, hy0, hy1) in _SIDE_SPECS:
|
|
469
|
+
# R: grepl("(?=.*t)", sides, perl = TRUE) etc. -- letter present?
|
|
470
|
+
if letter not in sides:
|
|
471
|
+
continue
|
|
472
|
+
seg_x0 = unit_rep(x + width * wx0, length_out=n)
|
|
473
|
+
seg_x1 = unit_rep(x + width * wx1, length_out=n)
|
|
474
|
+
seg_y0 = unit_rep(y + height * hy0, length_out=n)
|
|
475
|
+
seg_y1 = unit_rep(y + height * hy1, length_out=n)
|
|
476
|
+
if x0 is None:
|
|
477
|
+
x0, x1, y0, y1 = seg_x0, seg_x1, seg_y0, seg_y1
|
|
478
|
+
else:
|
|
479
|
+
x0 = unit_c(x0, seg_x0)
|
|
480
|
+
x1 = unit_c(x1, seg_x1)
|
|
481
|
+
y0 = unit_c(y0, seg_y0)
|
|
482
|
+
y1 = unit_c(y1, seg_y1)
|
|
483
|
+
|
|
484
|
+
# R: sidegrob = segmentsGrob(..., gp = do.call(gpar, within(gp, fill <- NA)))
|
|
485
|
+
sidegrob = segments_grob(
|
|
486
|
+
x0=x0,
|
|
487
|
+
y0=y0,
|
|
488
|
+
x1=x1,
|
|
489
|
+
y1=y1,
|
|
490
|
+
name="sidegrob",
|
|
491
|
+
gp=_strip_field(params, "fill"),
|
|
492
|
+
vp=vp,
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
# R: grobTree(rectfill, sidegrob, name = name, vp = vp) -- fill first.
|
|
496
|
+
return grob_tree(rectfill, sidegrob, name=name, vp=vp)
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
# ---------------------------------------------------------------------------
|
|
500
|
+
# element_grob dispatch wrapper
|
|
501
|
+
# ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
# Capture the upstream (possibly already-patched) implementation exactly once.
|
|
504
|
+
_gg_element_grob = getattr(_te, "_ggh4x_orig_element_grob", _te.element_grob)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def element_grob(element: Any, **kwargs: Any) -> Any:
|
|
508
|
+
"""Dispatch a theme element to its grob, handling :class:`ElementPartRect`.
|
|
509
|
+
|
|
510
|
+
``ggplot2_py.theme_elements.element_grob`` is a hard ``isinstance`` chain,
|
|
511
|
+
not S3 dispatch. Because :class:`ElementPartRect` is-a ``ElementRect``, the
|
|
512
|
+
upstream ``ElementRect`` branch would wrongly draw a full rectangle, so this
|
|
513
|
+
wrapper intercepts ``ElementPartRect`` *first* and otherwise defers to the
|
|
514
|
+
upstream function.
|
|
515
|
+
|
|
516
|
+
Parameters
|
|
517
|
+
----------
|
|
518
|
+
element : Element
|
|
519
|
+
Any theme element.
|
|
520
|
+
**kwargs
|
|
521
|
+
Forwarded to the appropriate renderer.
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
Grob
|
|
526
|
+
A grid grob.
|
|
527
|
+
"""
|
|
528
|
+
if isinstance(element, ElementPartRect):
|
|
529
|
+
return _grob_from_part_rect(element, **kwargs)
|
|
530
|
+
return _gg_element_grob(element, **kwargs)
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _install_element_grob_patch() -> None:
|
|
534
|
+
"""Monkeypatch ``ggplot2_py.theme_elements.element_grob`` with the wrapper.
|
|
535
|
+
|
|
536
|
+
Stores the original under ``_ggh4x_orig_element_grob`` so re-imports stay
|
|
537
|
+
idempotent, then replaces the module attribute so any code dispatching an
|
|
538
|
+
``ElementPartRect`` through the upstream function renders partial borders.
|
|
539
|
+
"""
|
|
540
|
+
if getattr(_te, "_ggh4x_orig_element_grob", None) is None:
|
|
541
|
+
_te._ggh4x_orig_element_grob = _te.element_grob # type: ignore[attr-defined]
|
|
542
|
+
_te.element_grob = element_grob # type: ignore[assignment]
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
_install_element_grob_patch()
|