ggplot2-python 4.0.2.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.
- ggplot2_py/__init__.py +852 -0
- ggplot2_py/_compat.py +475 -0
- ggplot2_py/_plugins.py +129 -0
- ggplot2_py/_utils.py +544 -0
- ggplot2_py/aes.py +586 -0
- ggplot2_py/annotation.py +540 -0
- ggplot2_py/coord.py +2108 -0
- ggplot2_py/coords/__init__.py +49 -0
- ggplot2_py/datasets.py +265 -0
- ggplot2_py/draw_key.py +454 -0
- ggplot2_py/facet.py +1456 -0
- ggplot2_py/fortify.py +95 -0
- ggplot2_py/geom.py +4516 -0
- ggplot2_py/geoms/__init__.py +12 -0
- ggplot2_py/ggproto.py +279 -0
- ggplot2_py/guide.py +2925 -0
- ggplot2_py/guide_axis.py +615 -0
- ggplot2_py/guide_colourbar.py +657 -0
- ggplot2_py/guide_legend.py +1061 -0
- ggplot2_py/guides/__init__.py +8 -0
- ggplot2_py/labeller.py +296 -0
- ggplot2_py/labels.py +309 -0
- ggplot2_py/layer.py +954 -0
- ggplot2_py/layout.py +754 -0
- ggplot2_py/limits.py +314 -0
- ggplot2_py/plot.py +1401 -0
- ggplot2_py/plot_render.py +866 -0
- ggplot2_py/position.py +1269 -0
- ggplot2_py/protocols.py +171 -0
- ggplot2_py/py.typed +0 -0
- ggplot2_py/qplot.py +233 -0
- ggplot2_py/resources/diamonds.csv +53941 -0
- ggplot2_py/resources/economics.csv +575 -0
- ggplot2_py/resources/economics_long.csv +2871 -0
- ggplot2_py/resources/faithfuld.csv +5626 -0
- ggplot2_py/resources/luv_colours.csv +658 -0
- ggplot2_py/resources/midwest.csv +438 -0
- ggplot2_py/resources/mpg.csv +235 -0
- ggplot2_py/resources/msleep.csv +84 -0
- ggplot2_py/resources/presidential.csv +13 -0
- ggplot2_py/resources/seals.csv +1156 -0
- ggplot2_py/resources/txhousing.csv +8603 -0
- ggplot2_py/save.py +316 -0
- ggplot2_py/scale.py +2727 -0
- ggplot2_py/scales/__init__.py +4252 -0
- ggplot2_py/stat.py +6071 -0
- ggplot2_py/stats/__init__.py +9 -0
- ggplot2_py/theme.py +490 -0
- ggplot2_py/theme_defaults.py +1350 -0
- ggplot2_py/theme_elements.py +2052 -0
- ggplot2_python-4.0.2.9000.dist-info/METADATA +179 -0
- ggplot2_python-4.0.2.9000.dist-info/RECORD +54 -0
- ggplot2_python-4.0.2.9000.dist-info/WHEEL +4 -0
- ggplot2_python-4.0.2.9000.dist-info/licenses/LICENSE +3 -0
ggplot2_py/labeller.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Facet labeller functions — faithful port of R's facet-labeller.R.
|
|
3
|
+
|
|
4
|
+
Labeller functions format the strip labels of facet grids and wraps.
|
|
5
|
+
They accept a dict of ``{variable_name: [values...]}`` and return
|
|
6
|
+
a list of formatted label strings.
|
|
7
|
+
|
|
8
|
+
R references
|
|
9
|
+
------------
|
|
10
|
+
* ``ggplot2/R/facet-labeller.R`` — label_value, label_both, etc.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import textwrap
|
|
16
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"label_value",
|
|
20
|
+
"label_both",
|
|
21
|
+
"label_context",
|
|
22
|
+
"label_parsed",
|
|
23
|
+
"label_wrap_gen",
|
|
24
|
+
"as_labeller",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
# label_value
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
def label_value(
|
|
33
|
+
labels: Dict[str, List[str]],
|
|
34
|
+
multi_line: bool = True,
|
|
35
|
+
) -> List[str]:
|
|
36
|
+
"""Return only the factor values.
|
|
37
|
+
|
|
38
|
+
This is the default labeller in ggplot2.
|
|
39
|
+
|
|
40
|
+
Mirrors ``label_value`` in R (facet-labeller.R:105-112).
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
labels : dict
|
|
45
|
+
``{variable_name: [value_per_panel...]}``.
|
|
46
|
+
multi_line : bool
|
|
47
|
+
If ``True`` (default), return one line per variable.
|
|
48
|
+
If ``False``, collapse all variables into one line.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
list of str
|
|
53
|
+
Formatted labels, one per panel.
|
|
54
|
+
|
|
55
|
+
Examples
|
|
56
|
+
--------
|
|
57
|
+
>>> label_value({"drv": ["4", "f", "r"]})
|
|
58
|
+
["4", "f", "r"]
|
|
59
|
+
>>> label_value({"drv": ["4", "f"], "cyl": ["4", "6"]}, multi_line=False)
|
|
60
|
+
["4, 4", "f, 6"]
|
|
61
|
+
"""
|
|
62
|
+
var_names = list(labels.keys())
|
|
63
|
+
if not var_names:
|
|
64
|
+
return []
|
|
65
|
+
|
|
66
|
+
n_panels = len(next(iter(labels.values())))
|
|
67
|
+
|
|
68
|
+
if multi_line and len(var_names) == 1:
|
|
69
|
+
return [str(v) for v in labels[var_names[0]]]
|
|
70
|
+
|
|
71
|
+
if multi_line:
|
|
72
|
+
# Return one label per variable per panel — for multi-line strips,
|
|
73
|
+
# join with newline
|
|
74
|
+
result = []
|
|
75
|
+
for i in range(n_panels):
|
|
76
|
+
parts = [str(labels[v][i]) for v in var_names]
|
|
77
|
+
result.append("\n".join(parts))
|
|
78
|
+
return result
|
|
79
|
+
else:
|
|
80
|
+
# Single line: collapse all variables
|
|
81
|
+
result = []
|
|
82
|
+
for i in range(n_panels):
|
|
83
|
+
parts = [str(labels[v][i]) for v in var_names]
|
|
84
|
+
result.append(", ".join(parts))
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# label_both
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
def label_both(
|
|
93
|
+
labels: Dict[str, List[str]],
|
|
94
|
+
multi_line: bool = True,
|
|
95
|
+
sep: str = ": ",
|
|
96
|
+
) -> List[str]:
|
|
97
|
+
"""Return ``"variable: value"`` labels.
|
|
98
|
+
|
|
99
|
+
Mirrors ``label_both`` in R (facet-labeller.R:119-142).
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
labels : dict
|
|
104
|
+
``{variable_name: [value_per_panel...]}``.
|
|
105
|
+
multi_line : bool
|
|
106
|
+
If ``True``, one line per variable; if ``False``, collapse.
|
|
107
|
+
sep : str
|
|
108
|
+
Separator between variable name and value.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
list of str
|
|
113
|
+
Formatted labels.
|
|
114
|
+
|
|
115
|
+
Examples
|
|
116
|
+
--------
|
|
117
|
+
>>> label_both({"drv": ["4", "f", "r"]})
|
|
118
|
+
["drv: 4", "drv: f", "drv: r"]
|
|
119
|
+
"""
|
|
120
|
+
var_names = list(labels.keys())
|
|
121
|
+
if not var_names:
|
|
122
|
+
return []
|
|
123
|
+
|
|
124
|
+
n_panels = len(next(iter(labels.values())))
|
|
125
|
+
|
|
126
|
+
if multi_line:
|
|
127
|
+
result = []
|
|
128
|
+
for i in range(n_panels):
|
|
129
|
+
parts = [f"{v}{sep}{labels[v][i]}" for v in var_names]
|
|
130
|
+
result.append("\n".join(parts))
|
|
131
|
+
return result
|
|
132
|
+
else:
|
|
133
|
+
result = []
|
|
134
|
+
for i in range(n_panels):
|
|
135
|
+
var_part = ", ".join(var_names)
|
|
136
|
+
val_part = ", ".join(str(labels[v][i]) for v in var_names)
|
|
137
|
+
result.append(f"{var_part}{sep}{val_part}")
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# label_context
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
def label_context(
|
|
146
|
+
labels: Dict[str, List[str]],
|
|
147
|
+
multi_line: bool = True,
|
|
148
|
+
sep: str = ": ",
|
|
149
|
+
) -> List[str]:
|
|
150
|
+
"""Context-dependent labeller.
|
|
151
|
+
|
|
152
|
+
Uses ``label_value`` for single-variable faceting, ``label_both``
|
|
153
|
+
when multiple variables are involved.
|
|
154
|
+
|
|
155
|
+
Mirrors ``label_context`` in R (facet-labeller.R:147-153).
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
labels : dict
|
|
160
|
+
``{variable_name: [value_per_panel...]}``.
|
|
161
|
+
multi_line : bool
|
|
162
|
+
Multi-line mode.
|
|
163
|
+
sep : str
|
|
164
|
+
Separator for ``label_both``.
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
list of str
|
|
169
|
+
Formatted labels.
|
|
170
|
+
"""
|
|
171
|
+
if len(labels) <= 1:
|
|
172
|
+
return label_value(labels, multi_line)
|
|
173
|
+
else:
|
|
174
|
+
return label_both(labels, multi_line, sep)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
# label_parsed
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
def label_parsed(
|
|
182
|
+
labels: Dict[str, List[str]],
|
|
183
|
+
multi_line: bool = True,
|
|
184
|
+
) -> List[str]:
|
|
185
|
+
"""Interpret labels as expressions (Python: return as-is).
|
|
186
|
+
|
|
187
|
+
In R, this parses labels as plotmath expressions. In Python we
|
|
188
|
+
simply return the string values, since matplotlib mathtext or
|
|
189
|
+
LaTeX rendering is handled downstream by the text grob.
|
|
190
|
+
|
|
191
|
+
Mirrors ``label_parsed`` in R (facet-labeller.R:158-173).
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
labels : dict
|
|
196
|
+
``{variable_name: [value_per_panel...]}``.
|
|
197
|
+
multi_line : bool
|
|
198
|
+
Multi-line mode.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
list of str
|
|
203
|
+
Labels (unchanged).
|
|
204
|
+
"""
|
|
205
|
+
return label_value(labels, multi_line)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
# label_wrap_gen
|
|
210
|
+
# ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
def label_wrap_gen(width: int = 25) -> Callable:
|
|
213
|
+
"""Generate a labeller that wraps text at *width* characters.
|
|
214
|
+
|
|
215
|
+
Mirrors ``label_wrap_gen`` in R (facet-labeller.R:220-229).
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
width : int
|
|
220
|
+
Maximum number of characters before wrapping.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
callable
|
|
225
|
+
A labeller function.
|
|
226
|
+
|
|
227
|
+
Examples
|
|
228
|
+
--------
|
|
229
|
+
>>> labeller = label_wrap_gen(10)
|
|
230
|
+
>>> labeller({"class": ["compact car", "midsize SUV"]})
|
|
231
|
+
["compact\\ncar", "midsize\\nSUV"]
|
|
232
|
+
"""
|
|
233
|
+
def _wrap_labeller(
|
|
234
|
+
labels: Dict[str, List[str]],
|
|
235
|
+
multi_line: bool = True,
|
|
236
|
+
) -> List[str]:
|
|
237
|
+
base = label_value(labels, multi_line)
|
|
238
|
+
return ["\n".join(textwrap.wrap(str(lab), width)) for lab in base]
|
|
239
|
+
|
|
240
|
+
return _wrap_labeller
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
# as_labeller
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
_LABELLER_REGISTRY: Dict[str, Callable] = {
|
|
248
|
+
"label_value": label_value,
|
|
249
|
+
"label_both": label_both,
|
|
250
|
+
"label_context": label_context,
|
|
251
|
+
"label_parsed": label_parsed,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def as_labeller(x: Any) -> Callable:
|
|
256
|
+
"""Coerce *x* to a labeller function.
|
|
257
|
+
|
|
258
|
+
Mirrors ``as_labeller`` in R (facet-labeller.R:284-299).
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
x : str, dict, or callable
|
|
263
|
+
- ``str``: lookup by name (e.g. ``"label_value"``).
|
|
264
|
+
- ``dict``: a lookup table mapping values to labels.
|
|
265
|
+
- ``callable``: returned as-is.
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
callable
|
|
270
|
+
A labeller function.
|
|
271
|
+
"""
|
|
272
|
+
if callable(x):
|
|
273
|
+
return x
|
|
274
|
+
|
|
275
|
+
if isinstance(x, str):
|
|
276
|
+
if x in _LABELLER_REGISTRY:
|
|
277
|
+
return _LABELLER_REGISTRY[x]
|
|
278
|
+
raise ValueError(
|
|
279
|
+
f"Unknown labeller: {x!r}. "
|
|
280
|
+
f"Available: {list(_LABELLER_REGISTRY.keys())}"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if isinstance(x, dict):
|
|
284
|
+
# Lookup-table labeller: maps values to custom labels
|
|
285
|
+
lookup = x
|
|
286
|
+
|
|
287
|
+
def _dict_labeller(
|
|
288
|
+
labels: Dict[str, List[str]],
|
|
289
|
+
multi_line: bool = True,
|
|
290
|
+
) -> List[str]:
|
|
291
|
+
base = label_value(labels, multi_line)
|
|
292
|
+
return [lookup.get(lab, lab) for lab in base]
|
|
293
|
+
|
|
294
|
+
return _dict_labeller
|
|
295
|
+
|
|
296
|
+
raise TypeError(f"Cannot coerce {type(x).__name__} to a labeller.")
|
ggplot2_py/labels.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Label helpers for ggplot2 plots.
|
|
3
|
+
|
|
4
|
+
Provides ``labs()``, ``xlab()``, ``ylab()``, ``ggtitle()`` for setting
|
|
5
|
+
axis titles, plot titles, subtitles, captions, tags, and alt-text.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
|
+
|
|
12
|
+
from ggplot2_py._compat import Waiver, is_waiver, waiver, cli_warn
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"labs",
|
|
16
|
+
"xlab",
|
|
17
|
+
"ylab",
|
|
18
|
+
"ggtitle",
|
|
19
|
+
"Labels",
|
|
20
|
+
"is_labels",
|
|
21
|
+
"get_labs",
|
|
22
|
+
"update_labels",
|
|
23
|
+
"make_labels",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Labels container
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
class Labels(dict):
|
|
32
|
+
"""A thin ``dict`` subclass marking an object as a labels specification.
|
|
33
|
+
|
|
34
|
+
Behaves like a regular dictionary but carries a type tag so that the
|
|
35
|
+
``+`` operator on a ``GGPlot`` object can dispatch correctly.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __repr__(self) -> str:
|
|
39
|
+
items = ", ".join(f"{k}={v!r}" for k, v in self.items())
|
|
40
|
+
return f"Labels({items})"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_labels(x: Any) -> bool:
|
|
44
|
+
"""Return ``True`` if *x* is a :class:`Labels` instance.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
x : object
|
|
49
|
+
Object to test.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
bool
|
|
54
|
+
"""
|
|
55
|
+
return isinstance(x, Labels)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Public API
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
# Canonical label keys that are NOT aesthetics.
|
|
63
|
+
_SPECIAL_KEYS = frozenset({
|
|
64
|
+
"title", "subtitle", "caption", "tag",
|
|
65
|
+
"alt", "alt_insight", "dictionary",
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def labs(
|
|
70
|
+
*,
|
|
71
|
+
title: Any = None,
|
|
72
|
+
subtitle: Any = None,
|
|
73
|
+
caption: Any = None,
|
|
74
|
+
tag: Any = None,
|
|
75
|
+
alt: Any = None,
|
|
76
|
+
alt_insight: Any = None,
|
|
77
|
+
dictionary: Any = None,
|
|
78
|
+
**kwargs: Any,
|
|
79
|
+
) -> Labels:
|
|
80
|
+
"""Create a labels specification.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
title : str or None, optional
|
|
85
|
+
Plot title.
|
|
86
|
+
subtitle : str or None, optional
|
|
87
|
+
Plot subtitle (displayed below the title).
|
|
88
|
+
caption : str or None, optional
|
|
89
|
+
Plot caption (typically data source info).
|
|
90
|
+
tag : str or None, optional
|
|
91
|
+
Plot tag (e.g. ``"A"`` for sub-figures).
|
|
92
|
+
alt : str or callable or None, optional
|
|
93
|
+
Alt-text for accessibility.
|
|
94
|
+
alt_insight : str or None, optional
|
|
95
|
+
Short insight appended to auto-generated alt-text.
|
|
96
|
+
dictionary : dict or None, optional
|
|
97
|
+
Named dictionary for label look-ups.
|
|
98
|
+
**kwargs
|
|
99
|
+
Aesthetic-name = label pairs, e.g. ``x="Weight"``, ``colour="Class"``.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
Labels
|
|
104
|
+
A labels specification suitable for adding to a ggplot via ``+``.
|
|
105
|
+
|
|
106
|
+
Examples
|
|
107
|
+
--------
|
|
108
|
+
>>> labs(x="Engine displacement", y="Highway MPG", colour="Vehicle class")
|
|
109
|
+
"""
|
|
110
|
+
# Collect special keyword args
|
|
111
|
+
args: Dict[str, Any] = {}
|
|
112
|
+
if title is not None:
|
|
113
|
+
args["title"] = title
|
|
114
|
+
if subtitle is not None:
|
|
115
|
+
args["subtitle"] = subtitle
|
|
116
|
+
if caption is not None:
|
|
117
|
+
args["caption"] = caption
|
|
118
|
+
if tag is not None:
|
|
119
|
+
args["tag"] = tag
|
|
120
|
+
if alt is not None:
|
|
121
|
+
args["alt"] = alt
|
|
122
|
+
if alt_insight is not None:
|
|
123
|
+
args["alt_insight"] = alt_insight
|
|
124
|
+
if dictionary is not None:
|
|
125
|
+
args["dictionary"] = dictionary
|
|
126
|
+
|
|
127
|
+
# Aesthetic label kwargs -- apply alias normalisation
|
|
128
|
+
from ggplot2_py.aes import standardise_aes_names # lazy to avoid circular
|
|
129
|
+
|
|
130
|
+
for k, v in kwargs.items():
|
|
131
|
+
canonical = standardise_aes_names([k])[0]
|
|
132
|
+
args[canonical] = v
|
|
133
|
+
|
|
134
|
+
return Labels(args)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def xlab(label: Optional[str]) -> Labels:
|
|
138
|
+
"""Set the x-axis label.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
label : str or None
|
|
143
|
+
Label text. ``None`` removes the label.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
Labels
|
|
148
|
+
"""
|
|
149
|
+
return labs(x=label)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def ylab(label: Optional[str]) -> Labels:
|
|
153
|
+
"""Set the y-axis label.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
label : str or None
|
|
158
|
+
Label text. ``None`` removes the label.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
Labels
|
|
163
|
+
"""
|
|
164
|
+
return labs(y=label)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def ggtitle(
|
|
168
|
+
label: Optional[str],
|
|
169
|
+
subtitle: Optional[str] = None,
|
|
170
|
+
) -> Labels:
|
|
171
|
+
"""Set the plot title (and optionally subtitle).
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
label : str or None
|
|
176
|
+
Title text.
|
|
177
|
+
subtitle : str or None, optional
|
|
178
|
+
Subtitle text.
|
|
179
|
+
|
|
180
|
+
Returns
|
|
181
|
+
-------
|
|
182
|
+
Labels
|
|
183
|
+
"""
|
|
184
|
+
return labs(title=label, subtitle=subtitle)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# update_labels helper
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def update_labels(plot: Any, labels: Union[Labels, Dict[str, Any]]) -> Any:
|
|
192
|
+
"""Merge *labels* into *plot*'s existing labels.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
plot : GGPlot
|
|
197
|
+
The plot to update (will be cloned).
|
|
198
|
+
labels : Labels or dict
|
|
199
|
+
New labels to merge in.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
GGPlot
|
|
204
|
+
A shallow copy of *plot* with updated labels.
|
|
205
|
+
"""
|
|
206
|
+
# Import here to avoid circular dependency
|
|
207
|
+
p = plot._clone()
|
|
208
|
+
# Merge: new labels override existing
|
|
209
|
+
merged = dict(p.labels)
|
|
210
|
+
merged.update(labels)
|
|
211
|
+
p.labels = Labels(merged)
|
|
212
|
+
return p
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ---------------------------------------------------------------------------
|
|
216
|
+
# make_labels
|
|
217
|
+
# ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
def make_labels(mapping: Any) -> Dict[str, str]:
|
|
220
|
+
"""Convert an aesthetic mapping into text labels.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
mapping : Mapping
|
|
225
|
+
An aesthetic mapping (from ``aes()``).
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
dict
|
|
230
|
+
A dictionary of aesthetic-name -> label-string.
|
|
231
|
+
"""
|
|
232
|
+
from ggplot2_py.aes import Mapping, AfterStat, AfterScale, Stage
|
|
233
|
+
|
|
234
|
+
# Accept both ``Mapping`` (from ``aes()``) and plain ``dict``
|
|
235
|
+
# (used by Stat.default_aes / Geom.default_aes). R's
|
|
236
|
+
# ``make_labels()`` treats any named list uniformly (labels.R:124).
|
|
237
|
+
if isinstance(mapping, Mapping):
|
|
238
|
+
mapping_items = mapping.items()
|
|
239
|
+
elif isinstance(mapping, dict):
|
|
240
|
+
mapping_items = mapping.items()
|
|
241
|
+
else:
|
|
242
|
+
return {}
|
|
243
|
+
|
|
244
|
+
def _label_for(val: Any) -> str:
|
|
245
|
+
"""Generate a human-readable label for an aesthetic value."""
|
|
246
|
+
if val is None:
|
|
247
|
+
return ""
|
|
248
|
+
if callable(val) and not isinstance(val, str):
|
|
249
|
+
return getattr(val, "__name__", "<expr>")
|
|
250
|
+
return str(val)
|
|
251
|
+
|
|
252
|
+
result: Dict[str, str] = {}
|
|
253
|
+
for aes_name, val in mapping_items:
|
|
254
|
+
if val is None:
|
|
255
|
+
result[aes_name] = aes_name
|
|
256
|
+
elif isinstance(val, str):
|
|
257
|
+
result[aes_name] = val
|
|
258
|
+
elif isinstance(val, AfterStat):
|
|
259
|
+
result[aes_name] = _label_for(val.x)
|
|
260
|
+
elif isinstance(val, AfterScale):
|
|
261
|
+
result[aes_name] = _label_for(val.x)
|
|
262
|
+
elif isinstance(val, Stage):
|
|
263
|
+
# Use the start mapping if available, then after_stat, then after_scale
|
|
264
|
+
if val.start is not None:
|
|
265
|
+
result[aes_name] = _label_for(val.start)
|
|
266
|
+
elif val.after_stat is not None:
|
|
267
|
+
result[aes_name] = _label_for(val.after_stat)
|
|
268
|
+
elif val.after_scale is not None:
|
|
269
|
+
result[aes_name] = _label_for(val.after_scale)
|
|
270
|
+
else:
|
|
271
|
+
result[aes_name] = aes_name
|
|
272
|
+
elif callable(val):
|
|
273
|
+
result[aes_name] = _label_for(val)
|
|
274
|
+
else:
|
|
275
|
+
result[aes_name] = str(val)
|
|
276
|
+
return result
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ---------------------------------------------------------------------------
|
|
280
|
+
# get_labs (requires a built plot)
|
|
281
|
+
# ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
def get_labs(plot: Any = None) -> Labels:
|
|
284
|
+
"""Retrieve completed labels from a plot.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
plot : GGPlot or None
|
|
289
|
+
A ggplot object. If ``None``, uses ``get_last_plot()``.
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
Labels
|
|
294
|
+
Resolved label dictionary.
|
|
295
|
+
"""
|
|
296
|
+
if plot is None:
|
|
297
|
+
# lazy import to avoid circular
|
|
298
|
+
from ggplot2_py.plot import get_last_plot
|
|
299
|
+
plot = get_last_plot()
|
|
300
|
+
|
|
301
|
+
if plot is None:
|
|
302
|
+
return Labels()
|
|
303
|
+
|
|
304
|
+
# If already built, grab labels directly
|
|
305
|
+
if hasattr(plot, "plot") and hasattr(plot, "layout"):
|
|
306
|
+
# BuiltGGPlot
|
|
307
|
+
return Labels(plot.plot.labels)
|
|
308
|
+
|
|
309
|
+
return Labels(plot.labels)
|