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,393 @@
|
|
|
1
|
+
"""Multiple gradient colour/fill scales (R source: ``scale_multi.R``).
|
|
2
|
+
|
|
3
|
+
Ports ggh4x's :func:`scale_colour_multi` / :func:`scale_fill_multi`, which map
|
|
4
|
+
several *non-standard* colour/fill aesthetics (``fill1``, ``fill2``, ...) each to
|
|
5
|
+
its own :func:`ggplot2_py.continuous_scale` gradient. The constructors build a
|
|
6
|
+
:class:`~ggh4x.multiscale._multiscale_add.MultiScale` container that defers the
|
|
7
|
+
plot rewrite to ``+``-time (see :mod:`ggh4x.multiscale._multiscale_add`).
|
|
8
|
+
|
|
9
|
+
The R *listed-argument* convention is reproduced exactly by :func:`_pickvalue`:
|
|
10
|
+
an argument that is a Python ``list`` is interpreted *per aesthetic* (its ``i``-th
|
|
11
|
+
element belongs to the ``i``-th aesthetic, wrapping to the first element when the
|
|
12
|
+
index exceeds the list length); any other value (scalar, colour vector, tuple,
|
|
13
|
+
``ndarray``) is broadcast unchanged to every aesthetic.
|
|
14
|
+
|
|
15
|
+
Notes
|
|
16
|
+
-----
|
|
17
|
+
* ggplot2_py always runs the *new guide system*, so the ``trans`` extra argument
|
|
18
|
+
is unconditionally renamed to ``transform`` and the R ``scale_name`` argument is
|
|
19
|
+
never forwarded (it does not exist on :func:`ggplot2_py.continuous_scale`).
|
|
20
|
+
* All semantics were verified against a live R ``ggh4x`` session
|
|
21
|
+
(``distribute_scale_multi``, ``pickvalue``, the materialised ``GuideColourbar``
|
|
22
|
+
``available_aes`` and the default ``white``/``black`` gradient palette).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Dict, List
|
|
28
|
+
|
|
29
|
+
import scales as _scales
|
|
30
|
+
|
|
31
|
+
import ggplot2_py as _gg
|
|
32
|
+
from ggplot2_py import standardise_aes_names
|
|
33
|
+
from ggplot2_py.scale import continuous_scale
|
|
34
|
+
|
|
35
|
+
from .._cli import cli_abort
|
|
36
|
+
from ._multiscale_add import MultiScale
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"scale_fill_multi",
|
|
40
|
+
"scale_colour_multi",
|
|
41
|
+
"scale_color_multi",
|
|
42
|
+
"MultiScale",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Sentinel marking "argument not supplied" so the R ``missing()`` cascade for
|
|
46
|
+
# ``colours`` / ``colors`` can be reproduced faithfully.
|
|
47
|
+
_MISSING = object()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# pickvalue (scale_multi.R:174-183)
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
def _is_per_aesthetic_list(x: Any) -> bool:
|
|
54
|
+
"""Return ``True`` when *x* is a Python list used as a *per-aesthetic* container.
|
|
55
|
+
|
|
56
|
+
R distinguishes ``list(c("white","red"), c("black","blue"))`` (a *list* of
|
|
57
|
+
vectors -- per aesthetic) from ``c("white","black")`` (a *vector* -- broadcast)
|
|
58
|
+
via ``class(x)[[1]] == "list"``. Python collapses both onto ``list``, so the
|
|
59
|
+
faithful disambiguation is: a list is per-aesthetic iff at least one of its
|
|
60
|
+
elements is itself a non-string sequence (a nested vector), mirroring the
|
|
61
|
+
documented ``colours = [["white", "red"], ...]`` idiom. A flat list of scalars
|
|
62
|
+
(e.g. ``["white", "black"]``) is a bare colour vector and is broadcast.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
x : Any
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
bool
|
|
71
|
+
"""
|
|
72
|
+
if type(x) is not list:
|
|
73
|
+
return False
|
|
74
|
+
return any(
|
|
75
|
+
isinstance(el, (list, tuple)) or hasattr(el, "__len__") and not isinstance(el, (str, bytes))
|
|
76
|
+
for el in x
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _pickvalue(x: Any, i: int) -> Any:
|
|
81
|
+
"""Select the per-aesthetic value of *x* for aesthetic index *i*.
|
|
82
|
+
|
|
83
|
+
Port of R ``pickvalue`` (``scale_multi.R:174-183``). When *x* is a
|
|
84
|
+
*per-aesthetic* list (see :func:`_is_per_aesthetic_list`) the ``i``-th element
|
|
85
|
+
is returned, wrapping back to the first element when ``i`` exceeds the list
|
|
86
|
+
length. Any other value (a scalar, a bare colour vector / flat list, a
|
|
87
|
+
``tuple`` or an ``ndarray``) is *broadcast* and returned unchanged.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
x : Any
|
|
92
|
+
The raw argument value.
|
|
93
|
+
i : int
|
|
94
|
+
Zero-based aesthetic index.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
Any
|
|
99
|
+
``x[i]`` (with wraparound to ``x[0]``) when *x* is a per-aesthetic list;
|
|
100
|
+
otherwise *x* unchanged.
|
|
101
|
+
"""
|
|
102
|
+
if not _is_per_aesthetic_list(x):
|
|
103
|
+
return x
|
|
104
|
+
# R: i <- if (i > length(x)) 1 else i (1-based). Here i is 0-based, so the
|
|
105
|
+
# out-of-range index wraps to the first element.
|
|
106
|
+
if i >= len(x):
|
|
107
|
+
i = 0
|
|
108
|
+
return x[i]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# distribute_scale_multi (scale_multi.R:115-171)
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
def _distribute_scale_multi(
|
|
115
|
+
*,
|
|
116
|
+
aesthetics: List[str],
|
|
117
|
+
colours: Any,
|
|
118
|
+
values: Any,
|
|
119
|
+
na_value: Any,
|
|
120
|
+
guide: Any,
|
|
121
|
+
extra: Dict[str, Any],
|
|
122
|
+
) -> List[Any]:
|
|
123
|
+
"""Build one :func:`continuous_scale` per aesthetic, distributing arguments.
|
|
124
|
+
|
|
125
|
+
Port of R ``distribute_scale_multi`` (``scale_multi.R:115-171``). For every
|
|
126
|
+
aesthetic ``aesthetics[i]`` the listed arguments are picked with
|
|
127
|
+
:func:`_pickvalue`, the guide is materialised (so its ``available_aes`` is set
|
|
128
|
+
to the single non-standard aesthetic), and a gradient
|
|
129
|
+
:func:`continuous_scale` is constructed.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
aesthetics : list of str
|
|
134
|
+
Non-standard aesthetic names (e.g. ``["fill1", "fill2"]``).
|
|
135
|
+
colours : Any
|
|
136
|
+
Gradient colours (per-aesthetic ``list`` or broadcast vector).
|
|
137
|
+
values : Any
|
|
138
|
+
Gradient ``values`` positions (per-aesthetic ``list`` or broadcast).
|
|
139
|
+
na_value : Any
|
|
140
|
+
Colour for missing values (per-aesthetic ``list`` or broadcast).
|
|
141
|
+
guide : Any
|
|
142
|
+
Guide spec(s): ``"colourbar"``/``"colorbar"``/``"legend"`` strings or
|
|
143
|
+
:class:`ggplot2_py.guide.Guide` instances (per-aesthetic or broadcast).
|
|
144
|
+
extra : dict
|
|
145
|
+
Extra keyword arguments forwarded to :func:`continuous_scale`, each value
|
|
146
|
+
possibly a per-aesthetic ``list``.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
list of ggplot2_py.scale.ScaleContinuous
|
|
151
|
+
One continuous gradient scale per aesthetic.
|
|
152
|
+
"""
|
|
153
|
+
n = len(aesthetics)
|
|
154
|
+
|
|
155
|
+
# Extra args: per-aesthetic dict, with the new-guide-system trans->transform
|
|
156
|
+
# rename always applied (scale_multi.R:119-127).
|
|
157
|
+
extra_args: List[Dict[str, Any]] = []
|
|
158
|
+
for i in range(n):
|
|
159
|
+
picked: Dict[str, Any] = {k: _pickvalue(v, i) for k, v in extra.items()}
|
|
160
|
+
if "trans" in picked:
|
|
161
|
+
picked["transform"] = picked.pop("trans")
|
|
162
|
+
extra_args.append(picked)
|
|
163
|
+
|
|
164
|
+
# Interpret guides (scale_multi.R:130-152): materialise a string/Guide into a
|
|
165
|
+
# Guide instance whose ``available_aes`` is the single non-standard aesthetic.
|
|
166
|
+
guides: List[Any] = []
|
|
167
|
+
for i in range(n):
|
|
168
|
+
this_guide = _pickvalue(guide, i)
|
|
169
|
+
if isinstance(this_guide, str):
|
|
170
|
+
# standardise_aes_names('colourbar') == standardise_aes_names('colorbar')
|
|
171
|
+
# i.e. colour->color normalisation; only the bar/legend distinction
|
|
172
|
+
# matters here.
|
|
173
|
+
if this_guide in ("colourbar", "colorbar"):
|
|
174
|
+
this_guide = _gg.guide_colourbar()
|
|
175
|
+
elif this_guide == "legend":
|
|
176
|
+
this_guide = _gg.guide_legend()
|
|
177
|
+
if _is_guide_proto(this_guide):
|
|
178
|
+
# ggproto(NULL, old, available_aes = aes): clone the instance so the
|
|
179
|
+
# class default is shadowed without mutating the shared original.
|
|
180
|
+
cloned = _gg.ggproto(None, this_guide)
|
|
181
|
+
cloned._set(available_aes=[aesthetics[i]])
|
|
182
|
+
this_guide = cloned
|
|
183
|
+
else:
|
|
184
|
+
cli_abort(
|
|
185
|
+
"`ggh4x`'s author hasn't programmed this path yet. "
|
|
186
|
+
"Choose a legend or colourbar guide."
|
|
187
|
+
)
|
|
188
|
+
guides.append(this_guide)
|
|
189
|
+
|
|
190
|
+
# Interpret scales (scale_multi.R:155-169).
|
|
191
|
+
out: List[Any] = []
|
|
192
|
+
for i in range(n):
|
|
193
|
+
aes_i = aesthetics[i]
|
|
194
|
+
palette = _scales.pal_gradient_n(
|
|
195
|
+
colours=_pickvalue(colours, i),
|
|
196
|
+
values=_pickvalue(values, i),
|
|
197
|
+
)
|
|
198
|
+
kwargs: Dict[str, Any] = dict(extra_args[i])
|
|
199
|
+
sc = continuous_scale(
|
|
200
|
+
aesthetics=aes_i,
|
|
201
|
+
palette=palette,
|
|
202
|
+
na_value=_pickvalue(na_value, i),
|
|
203
|
+
guide=guides[i],
|
|
204
|
+
**kwargs,
|
|
205
|
+
)
|
|
206
|
+
out.append(sc)
|
|
207
|
+
return out
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _is_guide_proto(obj: Any) -> bool:
|
|
211
|
+
"""Return ``True`` when *obj* is a :class:`ggplot2_py.guide.Guide` instance."""
|
|
212
|
+
Guide = getattr(_gg, "Guide", None)
|
|
213
|
+
if Guide is None:
|
|
214
|
+
return False
|
|
215
|
+
try:
|
|
216
|
+
return isinstance(obj, Guide)
|
|
217
|
+
except TypeError:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
# Constructors
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
def _resolve_colours(colours: Any, colors: Any) -> Any:
|
|
225
|
+
"""Reproduce R's ``missing(colours)``/``missing(colors)`` default cascade.
|
|
226
|
+
|
|
227
|
+
Port of ``scale_multi.R:53-62``: prefer ``colours``; fall back to the
|
|
228
|
+
American ``colors``; default to ``["white", "black"]`` when neither is given.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
colours : Any
|
|
233
|
+
British-spelling argument, or :data:`_MISSING`.
|
|
234
|
+
colors : Any
|
|
235
|
+
American-spelling argument, or :data:`_MISSING`.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
Any
|
|
240
|
+
The resolved gradient colours.
|
|
241
|
+
"""
|
|
242
|
+
if colours is _MISSING:
|
|
243
|
+
if colors is _MISSING:
|
|
244
|
+
return ["white", "black"]
|
|
245
|
+
return colors
|
|
246
|
+
return colours
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def scale_fill_multi(
|
|
250
|
+
*,
|
|
251
|
+
colours: Any = _MISSING,
|
|
252
|
+
values: Any = None,
|
|
253
|
+
na_value: str = "transparent",
|
|
254
|
+
guide: Any = "colourbar",
|
|
255
|
+
aesthetics: Any = "fill",
|
|
256
|
+
colors: Any = _MISSING,
|
|
257
|
+
**kwargs: Any,
|
|
258
|
+
) -> MultiScale:
|
|
259
|
+
"""Map multiple non-standard fill aesthetics to multiple gradient scales.
|
|
260
|
+
|
|
261
|
+
Port of R ``scale_fill_multi`` (``scale_multi.R:48-76``). Distributes listed
|
|
262
|
+
arguments across one :func:`ggplot2_py.continuous_scale` gradient per
|
|
263
|
+
aesthetic and wraps the result in a :class:`MultiScale` container.
|
|
264
|
+
|
|
265
|
+
This should only be added to a plot **after** every layer it affects has been
|
|
266
|
+
added, since :class:`MultiScale` rewrites those layers' geoms at ``+``-time.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
colours : Any, optional
|
|
271
|
+
Gradient colours. A ``list`` is per-aesthetic (e.g.
|
|
272
|
+
``[["white", "red"], ["black", "blue"]]``); a bare vector is broadcast.
|
|
273
|
+
Defaults to ``["white", "black"]``.
|
|
274
|
+
values : Any, optional
|
|
275
|
+
Gradient ``values`` positions (per-aesthetic ``list`` or broadcast).
|
|
276
|
+
na_value : str, default ``"transparent"``
|
|
277
|
+
Colour for missing values (per-aesthetic ``list`` or broadcast).
|
|
278
|
+
guide : Any, default ``"colourbar"``
|
|
279
|
+
Guide spec(s): ``"colourbar"``/``"colorbar"``/``"legend"`` or
|
|
280
|
+
:class:`ggplot2_py.guide.Guide` instances (per-aesthetic or broadcast).
|
|
281
|
+
aesthetics : str or list of str, default ``"fill"``
|
|
282
|
+
Non-standard aesthetic name(s) to map.
|
|
283
|
+
colors : Any, optional
|
|
284
|
+
American-spelling alias for *colours*.
|
|
285
|
+
**kwargs : Any
|
|
286
|
+
Extra arguments forwarded to :func:`continuous_scale` (each may be a
|
|
287
|
+
per-aesthetic ``list``).
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
MultiScale
|
|
292
|
+
A deferred-mutation container of class ``MultiScale``.
|
|
293
|
+
"""
|
|
294
|
+
colours = _resolve_colours(colours, colors)
|
|
295
|
+
aes_list = [aesthetics] if isinstance(aesthetics, str) else list(aesthetics)
|
|
296
|
+
scales = _distribute_scale_multi(
|
|
297
|
+
aesthetics=aes_list,
|
|
298
|
+
colours=colours,
|
|
299
|
+
values=values,
|
|
300
|
+
na_value=na_value,
|
|
301
|
+
guide=guide,
|
|
302
|
+
extra=kwargs,
|
|
303
|
+
)
|
|
304
|
+
return MultiScale(
|
|
305
|
+
scales=scales,
|
|
306
|
+
aes=aes_list,
|
|
307
|
+
replaced_aes=standardise_aes_names(["fill"])[0],
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def scale_colour_multi(
|
|
312
|
+
*,
|
|
313
|
+
colours: Any = _MISSING,
|
|
314
|
+
values: Any = None,
|
|
315
|
+
na_value: str = "transparent",
|
|
316
|
+
guide: Any = "colourbar",
|
|
317
|
+
aesthetics: Any = "colour",
|
|
318
|
+
colors: Any = _MISSING,
|
|
319
|
+
**kwargs: Any,
|
|
320
|
+
) -> MultiScale:
|
|
321
|
+
"""Map multiple non-standard colour aesthetics to multiple gradient scales.
|
|
322
|
+
|
|
323
|
+
Port of R ``scale_colour_multi`` (``scale_multi.R:80-110``). Behaves exactly
|
|
324
|
+
like :func:`scale_fill_multi` but defaults ``aesthetics`` to ``"colour"`` and
|
|
325
|
+
sets ``replaced_aes`` to ``"colour"``.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
colours : Any, optional
|
|
330
|
+
Gradient colours (per-aesthetic ``list`` or broadcast vector). Defaults
|
|
331
|
+
to ``["white", "black"]``.
|
|
332
|
+
values : Any, optional
|
|
333
|
+
Gradient ``values`` positions (per-aesthetic ``list`` or broadcast).
|
|
334
|
+
na_value : str, default ``"transparent"``
|
|
335
|
+
Colour for missing values (per-aesthetic ``list`` or broadcast).
|
|
336
|
+
guide : Any, default ``"colourbar"``
|
|
337
|
+
Guide spec(s) (per-aesthetic or broadcast).
|
|
338
|
+
aesthetics : str or list of str, default ``"colour"``
|
|
339
|
+
Non-standard aesthetic name(s) to map.
|
|
340
|
+
colors : Any, optional
|
|
341
|
+
American-spelling alias for *colours*.
|
|
342
|
+
**kwargs : Any
|
|
343
|
+
Extra arguments forwarded to :func:`continuous_scale`.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
MultiScale
|
|
348
|
+
A deferred-mutation container of class ``MultiScale``.
|
|
349
|
+
"""
|
|
350
|
+
colours = _resolve_colours(colours, colors)
|
|
351
|
+
aes_list = [aesthetics] if isinstance(aesthetics, str) else list(aesthetics)
|
|
352
|
+
scales = _distribute_scale_multi(
|
|
353
|
+
aesthetics=aes_list,
|
|
354
|
+
colours=colours,
|
|
355
|
+
values=values,
|
|
356
|
+
na_value=na_value,
|
|
357
|
+
guide=guide,
|
|
358
|
+
extra=kwargs,
|
|
359
|
+
)
|
|
360
|
+
return MultiScale(
|
|
361
|
+
scales=scales,
|
|
362
|
+
aes=aes_list,
|
|
363
|
+
replaced_aes=standardise_aes_names(["colour"])[0],
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def scale_color_multi(
|
|
368
|
+
*,
|
|
369
|
+
colours: Any = _MISSING,
|
|
370
|
+
values: Any = None,
|
|
371
|
+
na_value: str = "transparent",
|
|
372
|
+
guide: Any = "colourbar",
|
|
373
|
+
aesthetics: Any = "colour",
|
|
374
|
+
colors: Any = _MISSING,
|
|
375
|
+
**kwargs: Any,
|
|
376
|
+
) -> MultiScale:
|
|
377
|
+
"""American-spelling alias for :func:`scale_colour_multi`.
|
|
378
|
+
|
|
379
|
+
See :func:`scale_colour_multi` for the full parameter description.
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
MultiScale
|
|
384
|
+
"""
|
|
385
|
+
return scale_colour_multi(
|
|
386
|
+
colours=colours,
|
|
387
|
+
values=values,
|
|
388
|
+
na_value=na_value,
|
|
389
|
+
guide=guide,
|
|
390
|
+
aesthetics=aesthetics,
|
|
391
|
+
colors=colors,
|
|
392
|
+
**kwargs,
|
|
393
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Per-panel scale and panel-size customisation for ggh4x facets.
|
|
2
|
+
|
|
3
|
+
This package ports four ggh4x R files, all implemented as **add-on objects**
|
|
4
|
+
(not new facet/geom bases): each is created by a constructor and consumed by an
|
|
5
|
+
``ggplot_add`` handler that rewrites the plot's live facet (or a layer's geom)
|
|
6
|
+
at ``+``-time, producing a runtime ggproto clone.
|
|
7
|
+
|
|
8
|
+
* :mod:`force_panelsize` (``force_panelsize.R``) -- :func:`force_panelsizes`:
|
|
9
|
+
force the panel row heights / column widths of any facet.
|
|
10
|
+
* :mod:`facetted_pos_scales` (``facetted_pos_scales.R``) --
|
|
11
|
+
:func:`facetted_pos_scales`: per-panel position scales.
|
|
12
|
+
* :mod:`scale_facet` (``scale_facet.R``) -- :func:`scale_x_facet` /
|
|
13
|
+
:func:`scale_y_facet`: a single per-panel position scale targeting panels by
|
|
14
|
+
predicate.
|
|
15
|
+
* :mod:`at_panel` (``at_panel.R``) -- :func:`at_panel`: constrain a layer to a
|
|
16
|
+
subset of panels.
|
|
17
|
+
|
|
18
|
+
Importing this package registers the ``ForcedSize`` / ``FacettedPosScales`` /
|
|
19
|
+
``ScaleFacet`` handlers on :func:`ggplot2_py.plot.update_ggplot` (via the
|
|
20
|
+
module imports), exactly as ``ggh4x.multiscale`` registers ``MultiScale``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from .at_panel import at_panel
|
|
26
|
+
from .facetted_pos_scales import (
|
|
27
|
+
FacettedPosScales,
|
|
28
|
+
check_facetted_scale,
|
|
29
|
+
facetted_pos_scales,
|
|
30
|
+
finish_data_individual,
|
|
31
|
+
init_scale,
|
|
32
|
+
init_scales_individual,
|
|
33
|
+
should_transform,
|
|
34
|
+
train_scales_individual,
|
|
35
|
+
validate_facetted_scale,
|
|
36
|
+
)
|
|
37
|
+
from .force_panelsize import ForcedSize, force_panelsizes, is_null_unit
|
|
38
|
+
from .scale_facet import ScaleFacet, scale_facet, scale_x_facet, scale_y_facet
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"force_panelsizes",
|
|
42
|
+
"ForcedSize",
|
|
43
|
+
"is_null_unit",
|
|
44
|
+
"facetted_pos_scales",
|
|
45
|
+
"FacettedPosScales",
|
|
46
|
+
"check_facetted_scale",
|
|
47
|
+
"validate_facetted_scale",
|
|
48
|
+
"init_scale",
|
|
49
|
+
"init_scales_individual",
|
|
50
|
+
"train_scales_individual",
|
|
51
|
+
"finish_data_individual",
|
|
52
|
+
"should_transform",
|
|
53
|
+
"scale_facet",
|
|
54
|
+
"scale_x_facet",
|
|
55
|
+
"scale_y_facet",
|
|
56
|
+
"ScaleFacet",
|
|
57
|
+
"at_panel",
|
|
58
|
+
]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Constrain a layer to specific panels (port of ggh4x ``R/at_panel.R``).
|
|
2
|
+
|
|
3
|
+
:func:`at_panel` clones a layer's geom so that, at draw time, the layer's data is
|
|
4
|
+
subset to only those panels for which a *predicate* (evaluated against the plot
|
|
5
|
+
layout) is ``True``. This makes panel-specific annotations possible.
|
|
6
|
+
|
|
7
|
+
NSE deviation
|
|
8
|
+
-------------
|
|
9
|
+
R captures ``expr`` via ``enquo`` and tidy-evaluates it against the plot layout.
|
|
10
|
+
Python has no NSE: ``expr`` is a *predicate* -- either a callable
|
|
11
|
+
``layout_df -> bool-array`` or a string evaluated with
|
|
12
|
+
:meth:`pandas.DataFrame.eval` over the layout columns (``PANEL`` / ``ROW`` /
|
|
13
|
+
``COL`` / ``SCALE_*`` + facet variables).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, List
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
from ggplot2_py import ggproto
|
|
24
|
+
from ggplot2_py.ggproto import ggproto_parent
|
|
25
|
+
from ggplot2_py.layer import Layer
|
|
26
|
+
|
|
27
|
+
from ggh4x._cli import cli_abort
|
|
28
|
+
|
|
29
|
+
__all__ = ["at_panel"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _eval_keep(expr: Any, panels: pd.DataFrame) -> np.ndarray:
|
|
33
|
+
"""Evaluate the panel predicate against the layout, returning a bool array.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
expr : callable or str
|
|
38
|
+
The panel predicate.
|
|
39
|
+
panels : pandas.DataFrame
|
|
40
|
+
The plot layout.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
numpy.ndarray
|
|
45
|
+
Boolean keep-mask, recycled to ``len(panels)``.
|
|
46
|
+
"""
|
|
47
|
+
if callable(expr):
|
|
48
|
+
res = expr(panels)
|
|
49
|
+
elif isinstance(expr, str):
|
|
50
|
+
res = panels.eval(expr, engine="python")
|
|
51
|
+
else:
|
|
52
|
+
res = expr
|
|
53
|
+
arr = np.asarray(res)
|
|
54
|
+
if arr.dtype != bool:
|
|
55
|
+
arr = arr.astype(bool)
|
|
56
|
+
n = len(panels)
|
|
57
|
+
if arr.ndim == 0:
|
|
58
|
+
arr = np.repeat(arr, n)
|
|
59
|
+
if len(arr) != n:
|
|
60
|
+
arr = np.resize(arr, n)
|
|
61
|
+
return arr
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def at_panel(layer: Any, expr: Any) -> Any:
|
|
65
|
+
"""Constrain a layer to the panels matching *expr*.
|
|
66
|
+
|
|
67
|
+
Faithful port of ggh4x's ``at_panel`` (``R/at_panel.R:43-82``). Clones the
|
|
68
|
+
layer's geom with a ``draw_layer`` override that evaluates *expr* against the
|
|
69
|
+
plot layout, subsets the layer data to the kept panels, then delegates to the
|
|
70
|
+
original geom's ``draw_layer``. A bare list of layers is handled by
|
|
71
|
+
recursing over its layer elements.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
layer : Layer or list of Layer
|
|
76
|
+
The layer (or bare list of layers) to constrain.
|
|
77
|
+
expr : callable or str
|
|
78
|
+
Panel predicate evaluated against the plot layout (see module docstring).
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
Layer or list
|
|
83
|
+
A layer clone whose geom only draws on matched panels (or the input list
|
|
84
|
+
with each layer element so cloned).
|
|
85
|
+
|
|
86
|
+
Raises
|
|
87
|
+
------
|
|
88
|
+
ValueError
|
|
89
|
+
When *expr* is missing, or *layer* is neither a layer nor a bare list of
|
|
90
|
+
layers.
|
|
91
|
+
"""
|
|
92
|
+
if expr is None:
|
|
93
|
+
cli_abort("`expr` must be an expression, it cannot be missing.")
|
|
94
|
+
|
|
95
|
+
if not isinstance(layer, Layer):
|
|
96
|
+
# Accept bare lists of layers (e.g. geom_sf()).
|
|
97
|
+
if isinstance(layer, list):
|
|
98
|
+
return [
|
|
99
|
+
at_panel(el, expr) if isinstance(el, Layer) else el
|
|
100
|
+
for el in layer
|
|
101
|
+
]
|
|
102
|
+
cli_abort(f"`layer` must be a layer, not {type(layer).__name__}.")
|
|
103
|
+
|
|
104
|
+
old_geom = layer.geom
|
|
105
|
+
|
|
106
|
+
def draw_layer(self: Any, data: Any, params: Any, layout: Any, coord: Any) -> List[Any]:
|
|
107
|
+
panels = layout.layout
|
|
108
|
+
keep = _eval_keep(expr, panels)
|
|
109
|
+
kept_panels = panels.loc[keep, "PANEL"]
|
|
110
|
+
|
|
111
|
+
data = data[data["PANEL"].isin(kept_panels)]
|
|
112
|
+
return ggproto_parent(old_geom, self).draw_layer(data, params, layout, coord)
|
|
113
|
+
|
|
114
|
+
new_geom = ggproto(None, old_geom, draw_layer=draw_layer)
|
|
115
|
+
return ggproto(None, layer, geom=new_geom)
|