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,478 @@
|
|
|
1
|
+
"""Manual hybrid discrete/continuous position scales (R source: ``scale_manual.R``).
|
|
2
|
+
|
|
3
|
+
Ports ggh4x's :func:`scale_x_manual` / :func:`scale_y_manual` and the
|
|
4
|
+
:class:`ScaleManualPosition` ggproto. These behave like discrete position scales
|
|
5
|
+
(accepting discrete data and limits) but place each discrete level at an arbitrary
|
|
6
|
+
*continuous* coordinate, which needn't be equally spaced.
|
|
7
|
+
|
|
8
|
+
The load-bearing trick (per the R comment at ``scale_manual.R:169``) is that the
|
|
9
|
+
scale's continuous range (:attr:`range_c`) is trained with the scale expansion
|
|
10
|
+
**already baked in** at :meth:`ScaleManualPosition.train` time — this is what lets
|
|
11
|
+
discrete labels land at the requested continuous positions.
|
|
12
|
+
|
|
13
|
+
The :func:`sep_discrete` helper (a function factory that maps separator-delimited
|
|
14
|
+
grouped labels to numeric positions) lives in :mod:`ggh4x.conveniences` and is
|
|
15
|
+
re-exported here for convenience.
|
|
16
|
+
|
|
17
|
+
All semantics were verified against a live R ``ggh4x`` session
|
|
18
|
+
(``ScaleManualPosition$train``/``$map`` ``range_c`` and mapped values for plain,
|
|
19
|
+
named, ``c_limits`` and ``sep_discrete`` value vectors).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any, List, Optional, Sequence
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
import scales as _scales
|
|
29
|
+
|
|
30
|
+
from ggplot2_py._compat import waiver, is_waiver
|
|
31
|
+
from ggplot2_py.scale import (
|
|
32
|
+
ScaleDiscretePosition,
|
|
33
|
+
discrete_scale,
|
|
34
|
+
expansion,
|
|
35
|
+
mapped_discrete,
|
|
36
|
+
_is_discrete,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
from .._cli import cli_abort, cli_warn
|
|
40
|
+
from ..conveniences import sep_discrete
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"scale_x_manual",
|
|
44
|
+
"scale_y_manual",
|
|
45
|
+
"ScaleManualPosition",
|
|
46
|
+
"sep_discrete",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# helpers
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
def _is_discrete_input(x: Any) -> bool:
|
|
54
|
+
"""Return ``True`` for discrete data (R ``is.discrete``: factor/char/logical).
|
|
55
|
+
|
|
56
|
+
Port of ``scale_manual.R:259-261``. Delegates to ggplot2_py's
|
|
57
|
+
:func:`_is_discrete` which covers pandas Categoricals/object Series, string and
|
|
58
|
+
boolean ``ndarray``\\ s, and ``str``/``bool`` scalars and sequences.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
x : Any
|
|
63
|
+
Candidate data.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
bool
|
|
68
|
+
"""
|
|
69
|
+
return _is_discrete(x)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _values_names(values: Any) -> Optional[List[str]]:
|
|
73
|
+
"""Return the names of a named ``values`` mapping, or ``None`` if unnamed.
|
|
74
|
+
|
|
75
|
+
A dict-like ``values`` (the Python idiom for R's named numeric vector) carries
|
|
76
|
+
names as its keys; anything else is unnamed.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
values : Any
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
list of str or None
|
|
85
|
+
"""
|
|
86
|
+
if isinstance(values, dict):
|
|
87
|
+
return [str(k) for k in values.keys()]
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _values_numeric(values: Any) -> np.ndarray:
|
|
92
|
+
"""Return the numeric magnitudes of ``values`` (dict values or the vector)."""
|
|
93
|
+
if isinstance(values, dict):
|
|
94
|
+
return np.asarray(list(values.values()), dtype=float)
|
|
95
|
+
return np.asarray(values, dtype=float)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# ScaleManualPosition ggproto class (scale_manual.R:155-206)
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
class ScaleManualPosition(ScaleDiscretePosition):
|
|
102
|
+
"""Position scale placing discrete levels at arbitrary continuous coordinates.
|
|
103
|
+
|
|
104
|
+
Subclass of :class:`ggplot2_py.scale.ScaleDiscretePosition` ported from R
|
|
105
|
+
``ScaleManualPosition`` (``scale_manual.R:155-206``). Overrides
|
|
106
|
+
:meth:`train` and :meth:`map` entirely.
|
|
107
|
+
|
|
108
|
+
Attributes
|
|
109
|
+
----------
|
|
110
|
+
c_limits : numpy.ndarray or None
|
|
111
|
+
Optional length-2 continuous-limit override (``NaN`` entries fall back to
|
|
112
|
+
the data-derived range), set by :func:`_scale_position_manual`.
|
|
113
|
+
range_c : scales.ContinuousRange
|
|
114
|
+
The continuous range, trained with the expansion already applied at
|
|
115
|
+
:meth:`train` time.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
c_limits: Any = None
|
|
119
|
+
|
|
120
|
+
def train(self, x: Any) -> None:
|
|
121
|
+
"""Train the continuous range, baking in the scale expansion.
|
|
122
|
+
|
|
123
|
+
Port of R ``ScaleManualPosition$train`` (``scale_manual.R:158-174``).
|
|
124
|
+
For discrete *x* the discrete range is trained and the value range is the
|
|
125
|
+
range of the palette over the limits; for continuous *x* the value range
|
|
126
|
+
is ``range(x)``. Any non-``NaN`` :attr:`c_limits` overrides the
|
|
127
|
+
corresponding bound. The expansion (``self.expand`` or
|
|
128
|
+
``expansion(add=0.6)``) is then applied and the expanded bounds trained
|
|
129
|
+
into :attr:`range_c`.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
x : array-like
|
|
134
|
+
Layer data for this position aesthetic.
|
|
135
|
+
"""
|
|
136
|
+
if _is_discrete_input(x):
|
|
137
|
+
self.range.train(
|
|
138
|
+
x, drop=self.drop, na_rm=not self.na_translate
|
|
139
|
+
)
|
|
140
|
+
pal = self.palette(self.get_limits())
|
|
141
|
+
# A named palette (R named numeric vector) is a dict here; range() in R
|
|
142
|
+
# operates on the magnitudes regardless of names.
|
|
143
|
+
pal_arr = _values_numeric(pal)
|
|
144
|
+
rng = np.array([np.nanmin(pal_arr), np.nanmax(pal_arr)], dtype=float)
|
|
145
|
+
else:
|
|
146
|
+
x_arr = np.asarray(x, dtype=float)
|
|
147
|
+
rng = np.array([np.nanmin(x_arr), np.nanmax(x_arr)], dtype=float)
|
|
148
|
+
|
|
149
|
+
# c_limits override (scale_manual.R:166-168): NA-aware ifelse.
|
|
150
|
+
if self.c_limits is not None:
|
|
151
|
+
c_lim = np.asarray(self.c_limits, dtype=float)
|
|
152
|
+
rng = np.where(np.isnan(c_lim), rng, c_lim)
|
|
153
|
+
|
|
154
|
+
# Hack for scale expansion (scale_manual.R:169-173). expansion() returns
|
|
155
|
+
# [mul_lo, add_lo, mul_hi, add_hi]; expand_range(range, mul, add).
|
|
156
|
+
expand = self.expand if not is_waiver(self.expand) else expansion(add=0.6)
|
|
157
|
+
expand = np.asarray(expand, dtype=float)
|
|
158
|
+
lower = _scales.expand_range(rng, expand[0], expand[1])[0]
|
|
159
|
+
upper = _scales.expand_range(rng, expand[2], expand[3])[1]
|
|
160
|
+
self.range_c.train(np.array([lower, upper], dtype=float))
|
|
161
|
+
|
|
162
|
+
def map(self, x: Any, limits: Optional[Any] = None) -> Any:
|
|
163
|
+
"""Map discrete *x* to continuous positions via the manual palette.
|
|
164
|
+
|
|
165
|
+
Port of R ``ScaleManualPosition$map`` (``scale_manual.R:176-205``). For
|
|
166
|
+
discrete *x* the palette is resolved (with the
|
|
167
|
+
``n_breaks_cache``/``palette_cache`` cache), honouring a *named* palette
|
|
168
|
+
(match by name, blanking names absent from *limits*) or a positional match
|
|
169
|
+
against *limits*; missing values are filled with :attr:`na_value` when
|
|
170
|
+
:attr:`na_translate`. Continuous *x* passes straight through. The result
|
|
171
|
+
is always wrapped as a :func:`mapped_discrete` sentinel.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
x : array-like
|
|
176
|
+
Data to map.
|
|
177
|
+
limits : array-like, optional
|
|
178
|
+
Scale limits. Defaults to :meth:`get_limits`.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
ggplot2_py.scale._MappedDiscrete
|
|
183
|
+
The mapped continuous positions.
|
|
184
|
+
"""
|
|
185
|
+
if limits is None:
|
|
186
|
+
limits = self.get_limits()
|
|
187
|
+
|
|
188
|
+
if _is_discrete_input(x):
|
|
189
|
+
limits_list = list(limits) if limits is not None else []
|
|
190
|
+
n = sum(
|
|
191
|
+
1
|
|
192
|
+
for v in limits_list
|
|
193
|
+
if not (v is None or (isinstance(v, float) and np.isnan(v)))
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if self.n_breaks_cache is not None and self.n_breaks_cache == n:
|
|
197
|
+
pal = self.palette_cache
|
|
198
|
+
else:
|
|
199
|
+
if self.n_breaks_cache is not None:
|
|
200
|
+
cli_warn("Cached palette does not match requested.")
|
|
201
|
+
pal = self.palette(limits)
|
|
202
|
+
self.palette_cache = pal
|
|
203
|
+
self.n_breaks_cache = n
|
|
204
|
+
|
|
205
|
+
pal_names = _values_names(pal)
|
|
206
|
+
limits_str = [str(v) for v in limits_list]
|
|
207
|
+
x_str = [str(v) for v in np.asarray(x)]
|
|
208
|
+
|
|
209
|
+
if pal_names is not None:
|
|
210
|
+
# Named-palette branch (scale_manual.R:190-194).
|
|
211
|
+
pal_vals = list(_values_numeric(pal))
|
|
212
|
+
# Blank entries whose names are not in the limits.
|
|
213
|
+
pal_vals = [
|
|
214
|
+
(np.nan if pal_names[i] not in limits_str else pal_vals[i])
|
|
215
|
+
for i in range(len(pal_names))
|
|
216
|
+
]
|
|
217
|
+
name_to_val = {pal_names[i]: pal_vals[i] for i in range(len(pal_names))}
|
|
218
|
+
pal_match = np.array(
|
|
219
|
+
[name_to_val.get(v, np.nan) for v in x_str], dtype=float
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
# Positional branch (scale_manual.R:196): pal[match(x, limits)].
|
|
223
|
+
pal_vals = np.asarray(pal, dtype=float)
|
|
224
|
+
idx = {v: i for i, v in enumerate(limits_str)}
|
|
225
|
+
pal_match = np.array(
|
|
226
|
+
[
|
|
227
|
+
pal_vals[idx[v]] if v in idx and idx[v] < len(pal_vals) else np.nan
|
|
228
|
+
for v in x_str
|
|
229
|
+
],
|
|
230
|
+
dtype=float,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if self.na_translate:
|
|
234
|
+
x_is_na = np.array(
|
|
235
|
+
[
|
|
236
|
+
v is None or (isinstance(v, float) and np.isnan(v)) or v == "nan"
|
|
237
|
+
for v in x_str
|
|
238
|
+
]
|
|
239
|
+
)
|
|
240
|
+
na_fill = float(self.na_value) if _is_number(self.na_value) else np.nan
|
|
241
|
+
fill_mask = x_is_na | np.isnan(pal_match)
|
|
242
|
+
pal_match = np.where(fill_mask, na_fill, pal_match)
|
|
243
|
+
|
|
244
|
+
x = pal_match
|
|
245
|
+
|
|
246
|
+
return mapped_discrete(np.asarray(x, dtype=float))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _is_number(v: Any) -> bool:
|
|
250
|
+
"""Return ``True`` for a real numeric scalar (used for ``na_value`` coercion)."""
|
|
251
|
+
return isinstance(v, (int, float, np.integer, np.floating)) and not isinstance(v, bool)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
# internal constructor (scale_manual.R:82-151)
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
def _scale_position_manual(
|
|
258
|
+
aesthetics: Sequence[str],
|
|
259
|
+
values: Any = None,
|
|
260
|
+
*,
|
|
261
|
+
limits: Any = None,
|
|
262
|
+
c_limits: Any = None,
|
|
263
|
+
breaks: Any = None,
|
|
264
|
+
expand: Any = None,
|
|
265
|
+
guide: Any = None,
|
|
266
|
+
position: str = "bottom",
|
|
267
|
+
**kwargs: Any,
|
|
268
|
+
) -> ScaleManualPosition:
|
|
269
|
+
"""Build a :class:`ScaleManualPosition` (R ``scale_position_manual``).
|
|
270
|
+
|
|
271
|
+
Port of ``scale_manual.R:82-151``.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
aesthetics : sequence of str
|
|
276
|
+
The position aesthetics (e.g. ``["x", "xmin", "xmax", "xend"]``).
|
|
277
|
+
values : callable or numeric or dict
|
|
278
|
+
A palette function ``limits -> numeric``, or a numeric vector (optionally
|
|
279
|
+
named via a ``dict``) of positions parallel to the unique values.
|
|
280
|
+
limits : array-like or callable, optional
|
|
281
|
+
Scale limits. Defaults to intersecting the data with the ``values`` names
|
|
282
|
+
when *values* is named.
|
|
283
|
+
c_limits : array-like or None, optional
|
|
284
|
+
``None`` to use the value range, or a length-2 numeric (``NaN`` entries
|
|
285
|
+
fall back to the value range) for custom continuous limits.
|
|
286
|
+
breaks : array-like, optional
|
|
287
|
+
Breaks; when *values* is an unnamed vector these also name it.
|
|
288
|
+
expand : array-like or Waiver, optional
|
|
289
|
+
Scale expansion.
|
|
290
|
+
guide : Any, optional
|
|
291
|
+
Guide spec.
|
|
292
|
+
position : str, default ``"bottom"``
|
|
293
|
+
Axis position.
|
|
294
|
+
**kwargs : Any
|
|
295
|
+
Extra arguments forwarded to :func:`ggplot2_py.discrete_scale`.
|
|
296
|
+
|
|
297
|
+
Returns
|
|
298
|
+
-------
|
|
299
|
+
ScaleManualPosition
|
|
300
|
+
|
|
301
|
+
Raises
|
|
302
|
+
------
|
|
303
|
+
ValueError
|
|
304
|
+
If *values* is neither a function nor numeric, or if *c_limits* is not
|
|
305
|
+
``None`` or a length-2 numeric vector.
|
|
306
|
+
"""
|
|
307
|
+
if breaks is None:
|
|
308
|
+
breaks = waiver()
|
|
309
|
+
if expand is None:
|
|
310
|
+
expand = waiver()
|
|
311
|
+
if guide is None:
|
|
312
|
+
guide = waiver()
|
|
313
|
+
|
|
314
|
+
if not callable(values):
|
|
315
|
+
# Validate is.numeric before extracting magnitudes (scale_manual.R:96-101).
|
|
316
|
+
if not (isinstance(values, dict) or _is_numeric_vector(values)):
|
|
317
|
+
cli_abort("The `values` argument must be `numeric`.", TypeError)
|
|
318
|
+
|
|
319
|
+
names = _values_names(values)
|
|
320
|
+
# If limits is None and values has names -> intersect-with-names limits.
|
|
321
|
+
if limits is None and names is not None:
|
|
322
|
+
names_set = list(names)
|
|
323
|
+
|
|
324
|
+
def _limits_fn(x: Any, _names: List[str] = names_set) -> List[str]:
|
|
325
|
+
# intersect(x, names(values)) %||% character()
|
|
326
|
+
xs = [str(v) for v in x]
|
|
327
|
+
return [v for v in xs if v in _names]
|
|
328
|
+
|
|
329
|
+
limits = _limits_fn
|
|
330
|
+
|
|
331
|
+
# Unnamed vector + breaks given -> name the values by breaks.
|
|
332
|
+
if (
|
|
333
|
+
names is None
|
|
334
|
+
and not is_waiver(breaks)
|
|
335
|
+
and breaks is not None
|
|
336
|
+
and not callable(breaks)
|
|
337
|
+
):
|
|
338
|
+
brks = list(breaks)
|
|
339
|
+
vals = list(_values_numeric(values))
|
|
340
|
+
if len(brks) <= len(vals):
|
|
341
|
+
values = {str(brks[i]): vals[i] for i in range(len(brks))}
|
|
342
|
+
else:
|
|
343
|
+
values = {str(brks[i]): vals[i] for i in range(len(vals))}
|
|
344
|
+
|
|
345
|
+
# Build the palette (scale_manual.R:118-126).
|
|
346
|
+
def _pal(lims: Any, _values: Any = values) -> np.ndarray:
|
|
347
|
+
vals = _values_numeric(_values)
|
|
348
|
+
lims_list = list(lims) if lims is not None else []
|
|
349
|
+
if len(lims_list) > len(vals):
|
|
350
|
+
cli_abort(
|
|
351
|
+
f"Insufficient values in manual scale. {len(lims_list)} needed "
|
|
352
|
+
f"but {len(vals)} provided."
|
|
353
|
+
)
|
|
354
|
+
out = vals[: len(lims_list)]
|
|
355
|
+
names2 = _values_names(_values)
|
|
356
|
+
if names2 is not None:
|
|
357
|
+
# Carry the names so map()'s named-palette branch fires.
|
|
358
|
+
return {names2[i]: out[i] for i in range(len(out))}
|
|
359
|
+
return out
|
|
360
|
+
|
|
361
|
+
pal = _pal
|
|
362
|
+
else:
|
|
363
|
+
pal = values
|
|
364
|
+
|
|
365
|
+
# Validate c_limits (scale_manual.R:131-136).
|
|
366
|
+
if c_limits is not None:
|
|
367
|
+
c_arr = np.asarray(c_limits, dtype=float)
|
|
368
|
+
if c_arr.ndim != 1 or c_arr.size != 2:
|
|
369
|
+
cli_abort(
|
|
370
|
+
"The `c_limits` argument must either be `None` or a `numeric` "
|
|
371
|
+
"vector of length 2."
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
sc = discrete_scale(
|
|
375
|
+
list(aesthetics),
|
|
376
|
+
pal,
|
|
377
|
+
limits=limits,
|
|
378
|
+
expand=expand,
|
|
379
|
+
guide=guide,
|
|
380
|
+
position=position,
|
|
381
|
+
super_class=ScaleManualPosition,
|
|
382
|
+
**kwargs,
|
|
383
|
+
)
|
|
384
|
+
sc.range_c = _scales.ContinuousRange()
|
|
385
|
+
sc.c_limits = c_limits
|
|
386
|
+
return sc
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _is_numeric_vector(values: Any) -> bool:
|
|
390
|
+
"""Return ``True`` when *values* is a numeric vector (R ``is.numeric``)."""
|
|
391
|
+
if isinstance(values, (int, float, np.integer, np.floating)) and not isinstance(values, bool):
|
|
392
|
+
return True
|
|
393
|
+
if isinstance(values, (list, tuple, np.ndarray)):
|
|
394
|
+
if len(values) == 0:
|
|
395
|
+
return True
|
|
396
|
+
try:
|
|
397
|
+
np.asarray(values, dtype=float)
|
|
398
|
+
return True
|
|
399
|
+
except (TypeError, ValueError):
|
|
400
|
+
return False
|
|
401
|
+
return False
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
# ---------------------------------------------------------------------------
|
|
405
|
+
# external constructors (scale_manual.R:48-78)
|
|
406
|
+
# ---------------------------------------------------------------------------
|
|
407
|
+
def scale_x_manual(
|
|
408
|
+
values: Any,
|
|
409
|
+
c_limits: Any = None,
|
|
410
|
+
position: str = "bottom",
|
|
411
|
+
**kwargs: Any,
|
|
412
|
+
) -> ScaleManualPosition:
|
|
413
|
+
"""A hybrid discrete/continuous manual position scale for ``x``.
|
|
414
|
+
|
|
415
|
+
Port of R ``scale_x_manual`` (``scale_manual.R:48-61``). Accepts discrete
|
|
416
|
+
input like a discrete scale but maps each level to an arbitrary continuous
|
|
417
|
+
coordinate.
|
|
418
|
+
|
|
419
|
+
Parameters
|
|
420
|
+
----------
|
|
421
|
+
values : callable or numeric or dict
|
|
422
|
+
A numeric vector with the same length as the unique values (optionally
|
|
423
|
+
named via a ``dict``), or a function accepting the limits and returning a
|
|
424
|
+
parallel numeric vector (see :func:`sep_discrete`).
|
|
425
|
+
c_limits : array-like or None, default ``None``
|
|
426
|
+
``None`` to use the value range as the continuous limits, or a length-2
|
|
427
|
+
numeric (``NaN`` entries fall back to the value range) for custom limits.
|
|
428
|
+
position : str, default ``"bottom"``
|
|
429
|
+
Axis position.
|
|
430
|
+
**kwargs : Any
|
|
431
|
+
Extra arguments forwarded to :func:`ggplot2_py.discrete_scale`.
|
|
432
|
+
|
|
433
|
+
Returns
|
|
434
|
+
-------
|
|
435
|
+
ScaleManualPosition
|
|
436
|
+
"""
|
|
437
|
+
return _scale_position_manual(
|
|
438
|
+
["x", "xmin", "xmax", "xend"],
|
|
439
|
+
values=values,
|
|
440
|
+
c_limits=c_limits,
|
|
441
|
+
position=position,
|
|
442
|
+
**kwargs,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def scale_y_manual(
|
|
447
|
+
values: Any,
|
|
448
|
+
c_limits: Any = None,
|
|
449
|
+
position: str = "left",
|
|
450
|
+
**kwargs: Any,
|
|
451
|
+
) -> ScaleManualPosition:
|
|
452
|
+
"""A hybrid discrete/continuous manual position scale for ``y``.
|
|
453
|
+
|
|
454
|
+
Port of R ``scale_y_manual`` (``scale_manual.R:65-78``). Behaves like
|
|
455
|
+
:func:`scale_x_manual` but for the ``y`` aesthetic.
|
|
456
|
+
|
|
457
|
+
Parameters
|
|
458
|
+
----------
|
|
459
|
+
values : callable or numeric or dict
|
|
460
|
+
Positions for the unique values, or a function mapping limits to positions.
|
|
461
|
+
c_limits : array-like or None, default ``None``
|
|
462
|
+
Continuous-limit override (see :func:`scale_x_manual`).
|
|
463
|
+
position : str, default ``"left"``
|
|
464
|
+
Axis position.
|
|
465
|
+
**kwargs : Any
|
|
466
|
+
Extra arguments forwarded to :func:`ggplot2_py.discrete_scale`.
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
ScaleManualPosition
|
|
471
|
+
"""
|
|
472
|
+
return _scale_position_manual(
|
|
473
|
+
["y", "ymin", "ymax", "yend"],
|
|
474
|
+
values=values,
|
|
475
|
+
c_limits=c_limits,
|
|
476
|
+
position=position,
|
|
477
|
+
**kwargs,
|
|
478
|
+
)
|