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,207 @@
|
|
|
1
|
+
"""The :class:`MultiScale` container and its ``ggplot_add`` handler.
|
|
2
|
+
|
|
3
|
+
R source: ``scale_listed.R:135-203`` (the shared ``ggplot_add.MultiScale`` S3
|
|
4
|
+
method) plus the ``structure(..., class = "MultiScale")`` tagged container built
|
|
5
|
+
by :func:`scale_multi.scale_fill_multi` / :func:`scale_listed.scale_listed`.
|
|
6
|
+
|
|
7
|
+
A :class:`MultiScale` is **not** a :class:`ggplot2_py.scale.Scale` or a ggproto;
|
|
8
|
+
it is a small deferred-mutation container. Its handler, registered on
|
|
9
|
+
:func:`ggplot2_py.plot.update_ggplot` via :func:`functools.singledispatch`,
|
|
10
|
+
runs at ``+``-time and:
|
|
11
|
+
|
|
12
|
+
1. adds each carried scale to ``plot.scales`` (``ScalesList.add``); and
|
|
13
|
+
2. rewrites every affected layer's *geom* so that the standard aesthetic
|
|
14
|
+
(``fill``/``colour``) it understands is exposed under the non-standard
|
|
15
|
+
aesthetic name (``fill1``/``spec``/...).
|
|
16
|
+
|
|
17
|
+
The geom rewrite is the **same machinery** as ggnewscale's ``bump_aes_layer``
|
|
18
|
+
geom branch — clone the geom, install ``handle_na``/``draw_key`` column-rename
|
|
19
|
+
shims (using the ``__func__``-capture + ``object.__setattr__`` install to dodge
|
|
20
|
+
GGProto's auto-bind arity bug) and rewrite the geom's aes-name slots — but in the
|
|
21
|
+
*opposite direction*: ``handle_na``/``draw_key`` rename ``new_aes -> replaced_aes``
|
|
22
|
+
on the data columns (so the inner geom sees the standard name), while
|
|
23
|
+
``default_aes`` / ``non_missing_aes`` / ``optional_aes`` / ``required_aes`` rename
|
|
24
|
+
``replaced_aes -> new_aes`` on the slot names. Unlike ggnewscale, the layer's
|
|
25
|
+
*stat* is left untouched.
|
|
26
|
+
|
|
27
|
+
Importing this module has the side effect of registering the handler on
|
|
28
|
+
``update_ggplot``; :mod:`ggh4x.multiscale` imports it for that side effect.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from typing import Any, List
|
|
35
|
+
|
|
36
|
+
import ggplot2_py as _gg
|
|
37
|
+
from ggplot2_py.plot import update_ggplot
|
|
38
|
+
|
|
39
|
+
# Reuse ggnewscale's verified bumping primitives (clone-with-renamed-geom).
|
|
40
|
+
from ggnewscale._bump import (
|
|
41
|
+
_rename_columns,
|
|
42
|
+
_safe_aes_slot,
|
|
43
|
+
_safe_default_aes,
|
|
44
|
+
)
|
|
45
|
+
from ggnewscale._change_name import change_name
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"MultiScale",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
# MultiScale container
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
@dataclass
|
|
56
|
+
class MultiScale:
|
|
57
|
+
"""Deferred container of non-standard scales for one *replaced* aesthetic.
|
|
58
|
+
|
|
59
|
+
Port of R's ``structure(list(scales=, aes=, replaced_aes=), class="MultiScale")``
|
|
60
|
+
(``scale_multi.R:72-75`` / ``scale_listed.R:127-130``). It is intentionally a
|
|
61
|
+
distinct class (not a ``dict``/``list``) so :func:`functools.singledispatch`
|
|
62
|
+
resolves it to :func:`_add_multiscale` rather than to the list/Mapping
|
|
63
|
+
handlers.
|
|
64
|
+
|
|
65
|
+
Attributes
|
|
66
|
+
----------
|
|
67
|
+
scales : list of ggplot2_py.scale.Scale
|
|
68
|
+
The scale objects to add to the plot.
|
|
69
|
+
aes : list of str
|
|
70
|
+
Non-standard aesthetic names (e.g. ``["fill1", "fill2"]``).
|
|
71
|
+
replaced_aes : str
|
|
72
|
+
The standard aesthetic those scales replace (``"fill"`` / ``"colour"``).
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
scales: List[Any] = field(default_factory=list)
|
|
76
|
+
aes: List[str] = field(default_factory=list)
|
|
77
|
+
replaced_aes: str = ""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Per-layer geom rewrite
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
def _rewrite_layer_geom(layer: Any, new_aes: List[str], replaced_aes: str) -> Any:
|
|
84
|
+
"""Clone *layer*'s geom so *replaced_aes* is exposed as *new_aes*.
|
|
85
|
+
|
|
86
|
+
Port of the per-layer rewrite in R ``ggplot_add.MultiScale``
|
|
87
|
+
(``scale_listed.R:150-201``), mirroring ggnewscale's ``bump_aes_layer`` geom
|
|
88
|
+
branch but renaming in the opposite direction and leaving the stat untouched.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
layer : ggplot2_py.layer.Layer
|
|
93
|
+
The layer whose geom is rewritten (mutated in place, matching R's
|
|
94
|
+
``lay$geom <- new_geom``).
|
|
95
|
+
new_aes : list of str
|
|
96
|
+
The non-standard aesthetic name(s) used by this layer's mapping.
|
|
97
|
+
replaced_aes : str
|
|
98
|
+
The standard aesthetic name those replace.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
ggplot2_py.layer.Layer
|
|
103
|
+
The same *layer*, with its geom replaced.
|
|
104
|
+
"""
|
|
105
|
+
old_geom = layer.geom
|
|
106
|
+
|
|
107
|
+
# handle_na wrapper: rename data columns new_aes -> replaced_aes, then
|
|
108
|
+
# delegate to the *original* geom's handle_na (scale_listed.R:157-163). R
|
|
109
|
+
# captures ``old_geom$handle_na`` as a closure, so the delegate runs against
|
|
110
|
+
# the original geom whose ``required_aes``/``non_missing_aes`` still carry the
|
|
111
|
+
# standard names that the renamed data now matches. ``old_geom.handle_na`` is
|
|
112
|
+
# a bound method of *old_geom*, so calling it with ``(data, params)`` keeps
|
|
113
|
+
# ``self`` pointing at the original geom (not the renamed clone).
|
|
114
|
+
old_handle_na = getattr(old_geom, "handle_na", None)
|
|
115
|
+
new_geom_kwargs: dict = {}
|
|
116
|
+
if old_handle_na is not None:
|
|
117
|
+
|
|
118
|
+
def _new_handle_na(self: Any, data: Any, params: Any, _fn: Any = old_handle_na) -> Any:
|
|
119
|
+
renamed = _rename_columns(data, {a: replaced_aes for a in new_aes})
|
|
120
|
+
return _fn(renamed, params)
|
|
121
|
+
|
|
122
|
+
new_geom_kwargs["handle_na"] = _new_handle_na
|
|
123
|
+
|
|
124
|
+
new_geom = _gg.ggproto(
|
|
125
|
+
f"New{'_'.join(new_aes)}{type(old_geom).__name__}",
|
|
126
|
+
old_geom,
|
|
127
|
+
**new_geom_kwargs,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Slot name-rewrites: replaced_aes -> new_aes (scale_listed.R:179-195).
|
|
131
|
+
# When a layer maps several non-standard aesthetics, R's gsub replaces the
|
|
132
|
+
# standard slot name with *all* of them; the first match wins for downstream
|
|
133
|
+
# lookups, so we use new_aes[0] as ggnewscale does for the single-aes case.
|
|
134
|
+
target_aes = new_aes[0]
|
|
135
|
+
new_geom.default_aes = change_name(
|
|
136
|
+
_safe_default_aes(new_geom), replaced_aes, target_aes
|
|
137
|
+
)
|
|
138
|
+
new_geom.non_missing_aes = change_name(
|
|
139
|
+
_safe_aes_slot(new_geom, "non_missing_aes"), replaced_aes, target_aes
|
|
140
|
+
)
|
|
141
|
+
new_geom.optional_aes = change_name(
|
|
142
|
+
_safe_aes_slot(new_geom, "optional_aes"), replaced_aes, target_aes
|
|
143
|
+
)
|
|
144
|
+
new_geom.required_aes = change_name(
|
|
145
|
+
_safe_aes_slot(new_geom, "required_aes"), replaced_aes, target_aes
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# draw_key wrapper: rename data columns new_aes -> replaced_aes, then delegate
|
|
149
|
+
# (scale_listed.R:165-171). Capture __func__ and install via
|
|
150
|
+
# object.__setattr__ to bypass GGProto auto-bind (arity dodge).
|
|
151
|
+
old_draw_key_attr = getattr(new_geom, "draw_key", None)
|
|
152
|
+
if old_draw_key_attr is not None:
|
|
153
|
+
old_draw_key_fn = getattr(old_draw_key_attr, "__func__", old_draw_key_attr)
|
|
154
|
+
|
|
155
|
+
def _new_draw_key(data: Any, params: Any, size: Any = None, _fn: Any = old_draw_key_fn) -> Any:
|
|
156
|
+
renamed = _rename_columns(data, {a: replaced_aes for a in new_aes})
|
|
157
|
+
return _fn(renamed, params, size)
|
|
158
|
+
|
|
159
|
+
object.__setattr__(new_geom, "draw_key", _new_draw_key)
|
|
160
|
+
|
|
161
|
+
layer.geom = new_geom
|
|
162
|
+
return layer
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
# ggplot_add.MultiScale (scale_listed.R:142-203)
|
|
167
|
+
# ---------------------------------------------------------------------------
|
|
168
|
+
@update_ggplot.register(MultiScale)
|
|
169
|
+
def _add_multiscale(obj: MultiScale, plot: Any, object_name: str = "") -> Any:
|
|
170
|
+
"""Add a :class:`MultiScale` to *plot* (R ``ggplot_add.MultiScale``).
|
|
171
|
+
|
|
172
|
+
Adds every carried scale to ``plot.scales`` then rewrites each layer that maps
|
|
173
|
+
one of the container's non-standard aesthetics so its geom exposes the
|
|
174
|
+
standard ``replaced_aes`` under that non-standard name.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
obj : MultiScale
|
|
179
|
+
The container to add.
|
|
180
|
+
plot : ggplot2_py.plot.GGPlot
|
|
181
|
+
The plot to mutate.
|
|
182
|
+
object_name : str, optional
|
|
183
|
+
Unused (kept for the ``update_ggplot`` dispatch signature).
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
ggplot2_py.plot.GGPlot
|
|
188
|
+
The mutated plot.
|
|
189
|
+
"""
|
|
190
|
+
# 1. Add scales (scale_listed.R:143-145).
|
|
191
|
+
for sc in obj.scales:
|
|
192
|
+
plot.scales.add(sc)
|
|
193
|
+
|
|
194
|
+
replaced_aes = obj.replaced_aes
|
|
195
|
+
|
|
196
|
+
# 2. Rewrite each affected layer (scale_listed.R:150-201).
|
|
197
|
+
new_layers: List[Any] = []
|
|
198
|
+
for lay in plot.layers:
|
|
199
|
+
mapping_keys = list(lay.mapping.keys()) if lay.mapping is not None else []
|
|
200
|
+
if not any(k in obj.aes for k in mapping_keys):
|
|
201
|
+
new_layers.append(lay)
|
|
202
|
+
continue
|
|
203
|
+
new_aes = [a for a in obj.aes if a in mapping_keys]
|
|
204
|
+
new_layers.append(_rewrite_layer_geom(lay, new_aes, replaced_aes))
|
|
205
|
+
|
|
206
|
+
plot.layers = new_layers
|
|
207
|
+
return plot
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Distribute a list of non-standard-aesthetic scales (R source: ``scale_listed.R``).
|
|
2
|
+
|
|
3
|
+
Ports ggh4x's :func:`scale_listed`, which takes a user-supplied list of ready-made
|
|
4
|
+
discrete scales (each bound to a single *non-standard* aesthetic) together with a
|
|
5
|
+
parallel ``replaces`` vector naming the *standard* aesthetic each one substitutes,
|
|
6
|
+
validates them, materialises their guides (setting ``available_aes`` so the legend
|
|
7
|
+
system matches the non-standard aesthetic) and groups them by ``replaces`` into one
|
|
8
|
+
:class:`~ggh4x.multiscale._multiscale_add.MultiScale` per distinct standard
|
|
9
|
+
aesthetic.
|
|
10
|
+
|
|
11
|
+
The grouping mirrors R's ``split(scalelist, replaces)``: groups are emitted in the
|
|
12
|
+
sorted order of the distinct ``replaces`` names (R orders ``split`` by factor
|
|
13
|
+
levels, i.e. alphabetically for a character vector). ``scale_listed`` returns the
|
|
14
|
+
*list* of :class:`MultiScale` objects; the per-layer geom rewrite happens later in
|
|
15
|
+
:func:`ggh4x.multiscale._multiscale_add._add_multiscale`.
|
|
16
|
+
|
|
17
|
+
All semantics were verified against a live R ``ggh4x`` session (validity checks,
|
|
18
|
+
the ``any``-union vs assign ``available_aes`` logic, and the alphabetical
|
|
19
|
+
``split`` ordering).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any, List
|
|
25
|
+
|
|
26
|
+
import ggplot2_py as _gg
|
|
27
|
+
from ggplot2_py import standardise_aes_names
|
|
28
|
+
|
|
29
|
+
from .._cli import cli_abort
|
|
30
|
+
from ._multiscale_add import MultiScale
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"scale_listed",
|
|
34
|
+
"ALL_AESTHETICS",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Verbatim port of R ``.all_aesthetics`` (scale_listed.R:205-211).
|
|
39
|
+
ALL_AESTHETICS = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"adj", "alpha", "angle", "bg", "cex", "col", "color", "colour", "fg",
|
|
42
|
+
"fill", "group", "hjust", "label", "linetype", "lower", "lty", "lwd",
|
|
43
|
+
"max", "middle", "min", "pch", "radius", "sample", "shape", "size",
|
|
44
|
+
"srt", "upper", "vjust", "weight", "width", "x", "xend", "xmax", "xmin",
|
|
45
|
+
"xintercept", "y", "yend", "ymax", "ymin", "yintercept", "z",
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_guide_proto(obj: Any) -> bool:
|
|
51
|
+
"""Return ``True`` when *obj* is a :class:`ggplot2_py.guide.Guide` instance."""
|
|
52
|
+
Guide = getattr(_gg, "Guide", None)
|
|
53
|
+
if Guide is None:
|
|
54
|
+
return False
|
|
55
|
+
try:
|
|
56
|
+
return isinstance(obj, Guide)
|
|
57
|
+
except TypeError:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def scale_listed(scalelist: List[Any], replaces: Any = None) -> List[MultiScale]:
|
|
62
|
+
"""Distribute a list of non-standard-aesthetic scales across the plot.
|
|
63
|
+
|
|
64
|
+
Port of R ``scale_listed`` (``scale_listed.R:56-133``). Validates the scale
|
|
65
|
+
list, materialises each scale's guide with the correct ``available_aes``, and
|
|
66
|
+
groups the scales by the standard aesthetic each replaces into one
|
|
67
|
+
:class:`MultiScale` per distinct ``replaces`` value.
|
|
68
|
+
|
|
69
|
+
This should only be added to a plot **after** every layer the non-standard
|
|
70
|
+
aesthetics affect has been added, since :class:`MultiScale` rewrites those
|
|
71
|
+
layers' geoms at ``+``-time.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
scalelist : list of ggplot2_py.scale.Scale
|
|
76
|
+
Scales each created with a single non-standard ``aesthetics`` argument.
|
|
77
|
+
replaces : sequence of str
|
|
78
|
+
Parallel to *scalelist*; the standard aesthetic (typically ``"colour"``
|
|
79
|
+
or ``"fill"``) each scale replaces.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
list of MultiScale
|
|
84
|
+
One :class:`MultiScale` per distinct ``replaces`` value, in the sorted
|
|
85
|
+
order of the distinct ``replaces`` names.
|
|
86
|
+
|
|
87
|
+
Raises
|
|
88
|
+
------
|
|
89
|
+
ValueError
|
|
90
|
+
If *replaces* is not parallel to *scalelist*, contains an invalid
|
|
91
|
+
aesthetic, if a list element is not a :class:`ggplot2_py.scale.Scale`, or
|
|
92
|
+
if any scale does not have exactly one aesthetic.
|
|
93
|
+
"""
|
|
94
|
+
if replaces is None:
|
|
95
|
+
replaces = []
|
|
96
|
+
replaces = list(replaces)
|
|
97
|
+
|
|
98
|
+
# Check replaces validity (scale_listed.R:58-69).
|
|
99
|
+
if len(scalelist) != len(replaces):
|
|
100
|
+
cli_abort(
|
|
101
|
+
"The `replaces` argument must be parallel to and of the same length "
|
|
102
|
+
"as the `scalelist` argument."
|
|
103
|
+
)
|
|
104
|
+
replaces = standardise_aes_names(replaces)
|
|
105
|
+
if not all(r in ALL_AESTHETICS for r in replaces):
|
|
106
|
+
cli_abort(
|
|
107
|
+
"The aesthetics in the `replaces` argument must be valid aesthetics."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Check scalelist validity (scale_listed.R:71-82).
|
|
111
|
+
Scale = getattr(_gg, "Scale", None)
|
|
112
|
+
if not all(Scale is not None and isinstance(s, Scale) for s in scalelist):
|
|
113
|
+
cli_abort(
|
|
114
|
+
"The `scalelist` argument must have valid `Scale` objects as "
|
|
115
|
+
"list-elements."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Check scale aesthetics (scale_listed.R:85-98).
|
|
119
|
+
aes_per_scale = [list(s.aesthetics or []) for s in scalelist]
|
|
120
|
+
if any(len(a) > 1 for a in aes_per_scale):
|
|
121
|
+
cli_abort("`scale_listed()` can only accept 1 aesthetic per scale.")
|
|
122
|
+
aesthetics: List[str] = [a for sub in aes_per_scale for a in sub]
|
|
123
|
+
if len(aesthetics) != len(replaces):
|
|
124
|
+
cli_abort(
|
|
125
|
+
"Every scale in the `scalelist` argument must have set valid "
|
|
126
|
+
"aesthetics."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Interpret guides (scale_listed.R:101-121).
|
|
130
|
+
for scale in scalelist:
|
|
131
|
+
guide = scale.guide
|
|
132
|
+
if guide == "none" or guide is False:
|
|
133
|
+
continue
|
|
134
|
+
if not _is_guide_proto(guide):
|
|
135
|
+
# match.fun(paste0("guide_", guide))()
|
|
136
|
+
guide_fn = getattr(_gg, f"guide_{guide}", None)
|
|
137
|
+
if not callable(guide_fn):
|
|
138
|
+
cli_abort(
|
|
139
|
+
f"`scale_listed()` cannot find a guide constructor for "
|
|
140
|
+
f"`{guide}`."
|
|
141
|
+
)
|
|
142
|
+
guide = guide_fn()
|
|
143
|
+
if _is_guide_proto(guide):
|
|
144
|
+
# ggproto(NULL, old): clone so the original guide is untouched.
|
|
145
|
+
guide = _gg.ggproto(None, guide)
|
|
146
|
+
avail = list(getattr(guide, "available_aes", []) or [])
|
|
147
|
+
if "any" not in avail:
|
|
148
|
+
guide.available_aes = list(scale.aesthetics)
|
|
149
|
+
else:
|
|
150
|
+
guide.available_aes = avail + list(scale.aesthetics)
|
|
151
|
+
scale.guide = guide
|
|
152
|
+
|
|
153
|
+
# Split by replaced aes (scale_listed.R:123-132). R's ``split`` keys are the
|
|
154
|
+
# distinct ``replaces`` names ordered as factor levels (alphabetical for a
|
|
155
|
+
# character vector).
|
|
156
|
+
distinct = sorted(set(replaces))
|
|
157
|
+
out: List[MultiScale] = []
|
|
158
|
+
for aes_name in distinct:
|
|
159
|
+
idx = [i for i, r in enumerate(replaces) if r == aes_name]
|
|
160
|
+
out.append(
|
|
161
|
+
MultiScale(
|
|
162
|
+
scales=[scalelist[i] for i in idx],
|
|
163
|
+
aes=[aesthetics[i] for i in idx],
|
|
164
|
+
replaced_aes=aes_name,
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
return out
|