plotnine 0.14.5__py3-none-any.whl → 0.15.0a2__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.
- plotnine/__init__.py +31 -37
- plotnine/_mpl/gridspec.py +265 -0
- plotnine/_mpl/layout_manager/__init__.py +6 -0
- plotnine/_mpl/layout_manager/_engine.py +87 -0
- plotnine/_mpl/layout_manager/_layout_items.py +957 -0
- plotnine/_mpl/layout_manager/_layout_tree.py +905 -0
- plotnine/_mpl/layout_manager/_spaces.py +1154 -0
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +159 -37
- plotnine/_mpl/utils.py +78 -10
- plotnine/_utils/__init__.py +35 -9
- plotnine/_utils/dev.py +45 -27
- plotnine/_utils/yippie.py +115 -0
- plotnine/animation.py +1 -1
- plotnine/coords/coord.py +3 -3
- plotnine/coords/coord_trans.py +1 -1
- plotnine/data/__init__.py +43 -8
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +2 -2
- plotnine/facets/facet.py +34 -43
- plotnine/facets/facet_grid.py +14 -6
- plotnine/facets/facet_wrap.py +3 -5
- plotnine/facets/strips.py +20 -33
- plotnine/geoms/annotate.py +3 -3
- plotnine/geoms/annotation_logticks.py +2 -0
- plotnine/geoms/annotation_stripes.py +2 -0
- plotnine/geoms/geom.py +3 -3
- plotnine/geoms/geom_bar.py +10 -2
- plotnine/geoms/geom_col.py +6 -0
- plotnine/geoms/geom_crossbar.py +2 -3
- plotnine/geoms/geom_path.py +2 -2
- plotnine/geoms/geom_violin.py +24 -7
- plotnine/ggplot.py +95 -66
- plotnine/guides/guide.py +19 -20
- plotnine/guides/guide_colorbar.py +6 -6
- plotnine/guides/guide_legend.py +15 -16
- plotnine/guides/guides.py +8 -8
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +33 -7
- plotnine/labels.py +8 -3
- plotnine/layer.py +4 -4
- plotnine/mapping/_env.py +2 -2
- plotnine/mapping/_eval_environment.py +85 -0
- plotnine/mapping/aes.py +14 -30
- plotnine/mapping/evaluation.py +7 -65
- plotnine/options.py +14 -7
- plotnine/plot_composition/__init__.py +10 -0
- plotnine/plot_composition/_compose.py +462 -0
- plotnine/plot_composition/_plotspec.py +50 -0
- plotnine/plot_composition/_spacer.py +32 -0
- plotnine/positions/position_dodge.py +1 -1
- plotnine/positions/position_dodge2.py +1 -1
- plotnine/positions/position_stack.py +1 -2
- plotnine/qplot.py +1 -2
- plotnine/scales/__init__.py +0 -6
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +4 -4
- plotnine/scales/scale_continuous.py +2 -1
- plotnine/scales/scale_identity.py +10 -2
- plotnine/scales/scale_manual.py +6 -2
- plotnine/stats/binning.py +5 -2
- plotnine/stats/smoothers.py +3 -5
- plotnine/stats/stat.py +3 -3
- plotnine/stats/stat_bindot.py +1 -3
- plotnine/stats/stat_density.py +2 -2
- plotnine/stats/stat_qq_line.py +1 -1
- plotnine/stats/stat_sina.py +34 -1
- plotnine/themes/elements/__init__.py +3 -0
- plotnine/themes/elements/element_text.py +35 -24
- plotnine/themes/elements/margin.py +137 -61
- plotnine/themes/targets.py +3 -1
- plotnine/themes/theme.py +21 -7
- plotnine/themes/theme_538.py +0 -1
- plotnine/themes/theme_bw.py +0 -1
- plotnine/themes/theme_dark.py +0 -1
- plotnine/themes/theme_gray.py +32 -34
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +28 -31
- plotnine/themes/theme_seaborn.py +36 -36
- plotnine/themes/theme_void.py +25 -27
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +369 -169
- plotnine/typing.py +3 -3
- plotnine/watermark.py +3 -3
- {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/METADATA +8 -5
- {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/RECORD +89 -78
- {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/WHEEL +1 -1
- plotnine/_mpl/_plot_side_space.py +0 -888
- plotnine/_mpl/_plotnine_tight_layout.py +0 -293
- plotnine/_mpl/layout_engine.py +0 -110
- {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info/licenses}/LICENSE +0 -0
- {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/top_level.txt +0 -0
|
@@ -43,6 +43,8 @@ class scale_color_identity(MapTrainMixin, scale_discrete):
|
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
_aesthetics = ["color"]
|
|
46
|
+
_: KW_ONLY
|
|
47
|
+
guide: Literal["legend"] | None = None
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
@dataclass
|
|
@@ -52,6 +54,8 @@ class scale_fill_identity(scale_color_identity):
|
|
|
52
54
|
"""
|
|
53
55
|
|
|
54
56
|
_aesthetics = ["fill"]
|
|
57
|
+
_: KW_ONLY
|
|
58
|
+
guide: Literal["legend"] | None = None
|
|
55
59
|
|
|
56
60
|
|
|
57
61
|
@dataclass
|
|
@@ -61,6 +65,8 @@ class scale_shape_identity(MapTrainMixin, scale_discrete):
|
|
|
61
65
|
"""
|
|
62
66
|
|
|
63
67
|
_aesthetics = ["shape"]
|
|
68
|
+
_: KW_ONLY
|
|
69
|
+
guide: Literal["legend"] | None = None
|
|
64
70
|
|
|
65
71
|
|
|
66
72
|
@dataclass
|
|
@@ -70,6 +76,8 @@ class scale_linetype_identity(MapTrainMixin, scale_discrete):
|
|
|
70
76
|
"""
|
|
71
77
|
|
|
72
78
|
_aesthetics = ["linetype"]
|
|
79
|
+
_: KW_ONLY
|
|
80
|
+
guide: Literal["legend"] | None = None
|
|
73
81
|
|
|
74
82
|
|
|
75
83
|
@dataclass
|
|
@@ -82,7 +90,7 @@ class scale_alpha_identity(
|
|
|
82
90
|
|
|
83
91
|
_aesthetics = ["alpha"]
|
|
84
92
|
_: KW_ONLY
|
|
85
|
-
guide: Literal["legend"] | None =
|
|
93
|
+
guide: Literal["legend"] | None = None
|
|
86
94
|
|
|
87
95
|
|
|
88
96
|
@dataclass
|
|
@@ -95,7 +103,7 @@ class scale_size_identity(
|
|
|
95
103
|
|
|
96
104
|
_aesthetics = ["size"]
|
|
97
105
|
_: KW_ONLY
|
|
98
|
-
guide: Literal["legend"] | None =
|
|
106
|
+
guide: Literal["legend"] | None = None
|
|
99
107
|
|
|
100
108
|
|
|
101
109
|
# American to British spelling
|
plotnine/scales/scale_manual.py
CHANGED
|
@@ -21,11 +21,15 @@ class _scale_manual(scale_discrete):
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
def __post_init__(self, values):
|
|
24
|
-
from collections.abc import Sized
|
|
24
|
+
from collections.abc import Iterable, Sized
|
|
25
25
|
|
|
26
26
|
super().__post_init__()
|
|
27
27
|
|
|
28
|
-
if
|
|
28
|
+
if (
|
|
29
|
+
isinstance(self.breaks, Iterable)
|
|
30
|
+
and isinstance(self.breaks, Sized)
|
|
31
|
+
and len(self.breaks) == len(values)
|
|
32
|
+
):
|
|
29
33
|
values = dict(zip(self.breaks, values))
|
|
30
34
|
|
|
31
35
|
def palette(n):
|
plotnine/stats/binning.py
CHANGED
|
@@ -73,7 +73,7 @@ def breaks_from_binwidth(
|
|
|
73
73
|
|
|
74
74
|
if boundary is not None and center is not None:
|
|
75
75
|
raise PlotnineError(
|
|
76
|
-
"Only one of 'boundary' and 'center'
|
|
76
|
+
"Only one of 'boundary' and 'center' may be specified."
|
|
77
77
|
)
|
|
78
78
|
elif boundary is None:
|
|
79
79
|
# When center is None, put the min and max of data in outer
|
|
@@ -165,7 +165,10 @@ def assign_bins(
|
|
|
165
165
|
if weight is None:
|
|
166
166
|
weight = np.ones(len(x))
|
|
167
167
|
else:
|
|
168
|
-
weight
|
|
168
|
+
# If weight is a dtype that isn't writeable
|
|
169
|
+
# and does not own it's memory. Using a list
|
|
170
|
+
# as an intermediate easily solves this.
|
|
171
|
+
weight = np.array(list(weight))
|
|
169
172
|
weight[np.isnan(weight)] = 0
|
|
170
173
|
|
|
171
174
|
bin_idx = pd.cut(
|
plotnine/stats/smoothers.py
CHANGED
|
@@ -37,7 +37,7 @@ def predictdf(data, xseq, **params) -> pd.DataFrame:
|
|
|
37
37
|
"gpr": gpr,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
method = cast(str | Callable[..., pd.DataFrame], params["method"])
|
|
40
|
+
method = cast("str | Callable[..., pd.DataFrame]", params["method"])
|
|
41
41
|
|
|
42
42
|
if isinstance(method, str):
|
|
43
43
|
try:
|
|
@@ -163,8 +163,7 @@ def rlm(data, xseq, **params) -> pd.DataFrame:
|
|
|
163
163
|
|
|
164
164
|
if params["se"]:
|
|
165
165
|
warnings.warn(
|
|
166
|
-
"Confidence intervals are not yet implemented"
|
|
167
|
-
" for RLM smoothing.",
|
|
166
|
+
"Confidence intervals are not yet implemented for RLM smoothing.",
|
|
168
167
|
PlotnineWarning,
|
|
169
168
|
)
|
|
170
169
|
|
|
@@ -190,8 +189,7 @@ def rlm_formula(data, xseq, **params) -> pd.DataFrame:
|
|
|
190
189
|
|
|
191
190
|
if params["se"]:
|
|
192
191
|
warnings.warn(
|
|
193
|
-
"Confidence intervals are not yet implemented"
|
|
194
|
-
" for RLM smoothing.",
|
|
192
|
+
"Confidence intervals are not yet implemented for RLM smoothing.",
|
|
195
193
|
PlotnineWarning,
|
|
196
194
|
)
|
|
197
195
|
|
plotnine/stats/stat.py
CHANGED
|
@@ -392,7 +392,7 @@ class stat(ABC, metaclass=Register):
|
|
|
392
392
|
msg = "{} should implement this method."
|
|
393
393
|
raise NotImplementedError(msg.format(cls.__name__))
|
|
394
394
|
|
|
395
|
-
def __radd__(self,
|
|
395
|
+
def __radd__(self, other: ggplot) -> ggplot:
|
|
396
396
|
"""
|
|
397
397
|
Add layer representing stat object on the right
|
|
398
398
|
|
|
@@ -406,8 +406,8 @@ class stat(ABC, metaclass=Register):
|
|
|
406
406
|
out :
|
|
407
407
|
ggplot object with added layer
|
|
408
408
|
"""
|
|
409
|
-
|
|
410
|
-
return
|
|
409
|
+
other += self.to_layer() # Add layer
|
|
410
|
+
return other
|
|
411
411
|
|
|
412
412
|
def to_layer(self) -> layer:
|
|
413
413
|
"""
|
plotnine/stats/stat_bindot.py
CHANGED
|
@@ -281,9 +281,7 @@ def densitybin(
|
|
|
281
281
|
if all(pd.isna(x)):
|
|
282
282
|
return pd.DataFrame()
|
|
283
283
|
|
|
284
|
-
if weight is None
|
|
285
|
-
weight = np.ones(len(x))
|
|
286
|
-
weight = np.asarray(weight)
|
|
284
|
+
weight = np.ones(len(x)) if weight is None else np.array(list(weight))
|
|
287
285
|
weight[np.isnan(weight)] = 0
|
|
288
286
|
|
|
289
287
|
if rangee is None:
|
plotnine/stats/stat_density.py
CHANGED
|
@@ -102,9 +102,9 @@ class stat_density(stat):
|
|
|
102
102
|
# useful for stacked density plots
|
|
103
103
|
|
|
104
104
|
'scaled' # density estimate, scaled to maximum of 1
|
|
105
|
+
'n' # Number of observations at a position
|
|
105
106
|
```
|
|
106
107
|
|
|
107
|
-
'n' # Number of observations at a position
|
|
108
108
|
|
|
109
109
|
"""
|
|
110
110
|
REQUIRED_AES = {"x"}
|
|
@@ -171,7 +171,7 @@ def compute_density(x, weight, range, **params):
|
|
|
171
171
|
x = np.asarray(x, dtype=float)
|
|
172
172
|
not_nan = ~np.isnan(x)
|
|
173
173
|
x = x[not_nan]
|
|
174
|
-
bw = cast(str | float, params["bw"])
|
|
174
|
+
bw = cast("str | float", params["bw"])
|
|
175
175
|
kernel = params["kernel"]
|
|
176
176
|
bounds = params["bounds"]
|
|
177
177
|
has_bounds = not (np.isneginf(bounds[0]) and np.isposinf(bounds[1]))
|
plotnine/stats/stat_qq_line.py
CHANGED
|
@@ -62,7 +62,7 @@ class stat_qq_line(stat):
|
|
|
62
62
|
def setup_params(self, data):
|
|
63
63
|
if len(self.params["line_p"]) != 2:
|
|
64
64
|
raise PlotnineError(
|
|
65
|
-
"Cannot fit line quantiles.
|
|
65
|
+
"Cannot fit line quantiles. 'line_p' must be of length 2"
|
|
66
66
|
)
|
|
67
67
|
return self.params
|
|
68
68
|
|
plotnine/stats/stat_sina.py
CHANGED
|
@@ -57,6 +57,15 @@ class stat_sina(stat):
|
|
|
57
57
|
- `area` - Scale by the largest density/bin among the different sinas
|
|
58
58
|
- `count` - areas are scaled proportionally to the number of points
|
|
59
59
|
- `width` - Only scale according to the maxwidth parameter.
|
|
60
|
+
style :
|
|
61
|
+
Type of sina plot to draw. The options are
|
|
62
|
+
```python
|
|
63
|
+
'full' # Regular (2 sided)
|
|
64
|
+
'left' # Left-sided half
|
|
65
|
+
'right' # Right-sided half
|
|
66
|
+
'left-right' # Alternate (left first) half by the group
|
|
67
|
+
'right-left' # Alternate (right first) half by the group
|
|
68
|
+
```
|
|
60
69
|
|
|
61
70
|
See Also
|
|
62
71
|
--------
|
|
@@ -91,6 +100,7 @@ class stat_sina(stat):
|
|
|
91
100
|
"bin_limit": 1,
|
|
92
101
|
"random_state": None,
|
|
93
102
|
"scale": "area",
|
|
103
|
+
"style": "full",
|
|
94
104
|
}
|
|
95
105
|
CREATES = {"scaled"}
|
|
96
106
|
|
|
@@ -101,7 +111,7 @@ class stat_sina(stat):
|
|
|
101
111
|
and (data["x"] != data["x"].iloc[0]).any()
|
|
102
112
|
):
|
|
103
113
|
raise TypeError(
|
|
104
|
-
"Continuous x aesthetic -- did you forget
|
|
114
|
+
"Continuous x aesthetic -- did you forget aes(group=...)?"
|
|
105
115
|
)
|
|
106
116
|
return data
|
|
107
117
|
|
|
@@ -245,6 +255,29 @@ class stat_sina(stat):
|
|
|
245
255
|
|
|
246
256
|
def finish_layer(self, data, params):
|
|
247
257
|
# Rescale x in case positions have been adjusted
|
|
258
|
+
style = params["style"]
|
|
259
|
+
x_mean = data["x"].to_numpy()
|
|
248
260
|
x_mod = (data["xmax"] - data["xmin"]) / data["width"]
|
|
249
261
|
data["x"] = data["x"] + data["x_diff"] * x_mod
|
|
262
|
+
x = data["x"].to_numpy()
|
|
263
|
+
even = data["group"].to_numpy() % 2 == 0
|
|
264
|
+
|
|
265
|
+
def mirror_x(bool_idx):
|
|
266
|
+
"""
|
|
267
|
+
Mirror x locations along the mean value
|
|
268
|
+
"""
|
|
269
|
+
data.loc[bool_idx, "x"] = (
|
|
270
|
+
2 * x_mean[bool_idx] - data.loc[bool_idx, "x"]
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
match style:
|
|
274
|
+
case "left":
|
|
275
|
+
mirror_x(x_mean < x)
|
|
276
|
+
case "right":
|
|
277
|
+
mirror_x(x < x_mean)
|
|
278
|
+
case "left-right":
|
|
279
|
+
mirror_x(even & (x < x_mean) | ~even & (x_mean < x))
|
|
280
|
+
case "right-left":
|
|
281
|
+
mirror_x(even & (x_mean < x) | ~even & (x < x_mean))
|
|
282
|
+
|
|
250
283
|
return data
|
|
@@ -2,10 +2,13 @@ from .element_blank import element_blank
|
|
|
2
2
|
from .element_line import element_line
|
|
3
3
|
from .element_rect import element_rect
|
|
4
4
|
from .element_text import element_text
|
|
5
|
+
from .margin import margin, margin_auto
|
|
5
6
|
|
|
6
7
|
__all__ = (
|
|
7
8
|
"element_blank",
|
|
8
9
|
"element_line",
|
|
9
10
|
"element_rect",
|
|
10
11
|
"element_text",
|
|
12
|
+
"margin",
|
|
13
|
+
"margin_auto",
|
|
11
14
|
)
|
|
@@ -8,10 +8,10 @@ from contextlib import suppress
|
|
|
8
8
|
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
from .element_base import element_base
|
|
11
|
-
from .margin import Margin
|
|
11
|
+
from .margin import margin as Margin
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from typing import Any, Literal,
|
|
14
|
+
from typing import Any, Literal, Sequence
|
|
15
15
|
|
|
16
16
|
from plotnine import theme
|
|
17
17
|
|
|
@@ -56,7 +56,7 @@ class element_text(element_base):
|
|
|
56
56
|
Margin around the text. The keys are
|
|
57
57
|
`t`, `b`, `l`, `r` and `units`.
|
|
58
58
|
The `tblr` keys are floats.
|
|
59
|
-
The `
|
|
59
|
+
The `unit` is one of `pt`, `lines` or `in`.
|
|
60
60
|
Not all text themeables support margin parameters and other
|
|
61
61
|
than the `units`, only some of the other keys may apply.
|
|
62
62
|
kwargs :
|
|
@@ -71,10 +71,10 @@ class element_text(element_base):
|
|
|
71
71
|
|
|
72
72
|
def __init__(
|
|
73
73
|
self,
|
|
74
|
-
family:
|
|
75
|
-
style:
|
|
76
|
-
weight:
|
|
77
|
-
color:
|
|
74
|
+
family: str | list[str] | None = None,
|
|
75
|
+
style: str | Sequence[str] | None = None,
|
|
76
|
+
weight: int | str | Sequence[int | str] | None = None,
|
|
77
|
+
color: (
|
|
78
78
|
str
|
|
79
79
|
| tuple[float, float, float]
|
|
80
80
|
| tuple[float, float, float, float]
|
|
@@ -83,22 +83,25 @@ class element_text(element_base):
|
|
|
83
83
|
| tuple[float, float, float]
|
|
84
84
|
| tuple[float, float, float, float]
|
|
85
85
|
]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
| None
|
|
87
|
+
) = None,
|
|
88
|
+
size: float | Sequence[float] | None = None,
|
|
89
|
+
ha: Literal["center", "left", "right"] | float | None = None,
|
|
90
|
+
va: (
|
|
90
91
|
Literal["center", "top", "bottom", "baseline", "center_baseline"]
|
|
91
92
|
| float
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
| None
|
|
94
|
+
) = None,
|
|
95
|
+
ma: Literal["center", "left", "right"] | float | None = None,
|
|
96
|
+
rotation: (
|
|
95
97
|
Literal["vertical", "horizontal"]
|
|
96
98
|
| float
|
|
97
99
|
| Sequence[Literal["vertical", "horizontal"]]
|
|
98
100
|
| Sequence[float]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
| None
|
|
102
|
+
) = None,
|
|
103
|
+
linespacing: float | None = None,
|
|
104
|
+
backgroundcolor: (
|
|
102
105
|
str
|
|
103
106
|
| tuple[float, float, float]
|
|
104
107
|
| tuple[float, float, float, float]
|
|
@@ -107,10 +110,11 @@ class element_text(element_base):
|
|
|
107
110
|
| tuple[float, float, float]
|
|
108
111
|
| tuple[float, float, float, float]
|
|
109
112
|
]
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
| None
|
|
114
|
+
) = None,
|
|
115
|
+
margin: (
|
|
116
|
+
Margin | dict[Literal["t", "b", "l", "r", "unit"], Any] | None
|
|
117
|
+
) = None,
|
|
114
118
|
rotation_mode: Literal["default", "anchor"] | None = None,
|
|
115
119
|
**kwargs: Any,
|
|
116
120
|
):
|
|
@@ -139,8 +143,15 @@ class element_text(element_base):
|
|
|
139
143
|
|
|
140
144
|
super().__init__()
|
|
141
145
|
self.properties.update(**kwargs)
|
|
146
|
+
|
|
142
147
|
if margin is not None:
|
|
143
|
-
|
|
148
|
+
if isinstance(margin, dict):
|
|
149
|
+
if "units" in margin:
|
|
150
|
+
# for backward compatibility
|
|
151
|
+
margin["unit"] = margin.pop("units") # pyright: ignore[reportArgumentType]
|
|
152
|
+
margin = Margin(**margin)
|
|
153
|
+
|
|
154
|
+
self.properties["margin"] = margin
|
|
144
155
|
|
|
145
156
|
# Use the parameters that have been set
|
|
146
157
|
names = (
|
|
@@ -153,6 +164,7 @@ class element_text(element_base):
|
|
|
153
164
|
"size",
|
|
154
165
|
"style",
|
|
155
166
|
"va",
|
|
167
|
+
"ma",
|
|
156
168
|
"weight",
|
|
157
169
|
"rotation_mode",
|
|
158
170
|
)
|
|
@@ -166,8 +178,7 @@ class element_text(element_base):
|
|
|
166
178
|
Setup the theme_element before drawing
|
|
167
179
|
"""
|
|
168
180
|
if m := self.properties.get("margin"):
|
|
169
|
-
m
|
|
170
|
-
m.themeable_name = themeable_name
|
|
181
|
+
m = m.setup(theme, themeable_name)
|
|
171
182
|
|
|
172
183
|
def _translate_hjust(
|
|
173
184
|
self, just: float
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Margin
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from contextlib import suppress
|
|
8
|
+
from copy import copy
|
|
9
|
+
from dataclasses import dataclass, field
|
|
8
10
|
from typing import TYPE_CHECKING
|
|
9
11
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
@@ -12,80 +14,154 @@ if TYPE_CHECKING:
|
|
|
12
14
|
|
|
13
15
|
from plotnine import theme
|
|
14
16
|
|
|
15
|
-
from .element_base import element_base
|
|
16
|
-
|
|
17
17
|
|
|
18
18
|
@dataclass
|
|
19
|
-
class
|
|
20
|
-
|
|
19
|
+
class margin:
|
|
20
|
+
"""
|
|
21
|
+
Margin
|
|
22
|
+
"""
|
|
23
|
+
|
|
21
24
|
t: float = 0
|
|
25
|
+
"""
|
|
26
|
+
Top margin
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
r: float = 0
|
|
30
|
+
"""
|
|
31
|
+
Right margin
|
|
32
|
+
"""
|
|
33
|
+
|
|
22
34
|
b: float = 0
|
|
35
|
+
"""
|
|
36
|
+
Bottom margin
|
|
37
|
+
"""
|
|
38
|
+
|
|
23
39
|
l: float = 0
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
"""
|
|
41
|
+
Left Margin
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
unit: Literal["pt", "in", "lines", "fig"] = "pt"
|
|
45
|
+
"""
|
|
46
|
+
The units (coordinate space) of the values
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
# These are set by the themeable when it is applied
|
|
50
|
+
fontsize: float = field(init=False, default=0)
|
|
51
|
+
"""
|
|
52
|
+
Font size of text that this margin applies to
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
figure_size: tuple[float, float] = field(init=False, default=(0, 0))
|
|
56
|
+
"""
|
|
57
|
+
Size of the figure in inches
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def setup(self, theme: theme, themeable_name: str):
|
|
61
|
+
"""
|
|
62
|
+
Setup the margin to be used in the layout
|
|
63
|
+
|
|
64
|
+
For the margin's values to be useful, we need to be able to
|
|
65
|
+
convert them to different units as is required. Here we get
|
|
66
|
+
all the parameters that we shall need to do the conversions.
|
|
67
|
+
"""
|
|
68
|
+
self.themeable_name = themeable_name
|
|
69
|
+
self.fontsize = theme.getp((themeable_name, "size"), 11)
|
|
70
|
+
self.figure_size = theme.getp("figure_size")
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def pt(self) -> margin:
|
|
74
|
+
"""
|
|
75
|
+
Return margin in points
|
|
76
|
+
|
|
77
|
+
These are the units of the display coordinate system
|
|
78
|
+
"""
|
|
79
|
+
return self.to("pt")
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def inch(self) -> margin:
|
|
83
|
+
"""
|
|
84
|
+
Return margin in inches
|
|
85
|
+
|
|
86
|
+
These are the units of the figure-inches coordinate system
|
|
87
|
+
"""
|
|
88
|
+
return self.to("in")
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def lines(self) -> margin:
|
|
92
|
+
"""
|
|
93
|
+
Return margin in lines units
|
|
94
|
+
"""
|
|
95
|
+
return self.to("lines")
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def fig(self) -> margin:
|
|
99
|
+
"""
|
|
100
|
+
Return margin in figure units
|
|
101
|
+
|
|
102
|
+
These are the units of the figure coordinate system
|
|
103
|
+
"""
|
|
104
|
+
return self.to("fig")
|
|
105
|
+
|
|
106
|
+
def to(self, unit: Literal["pt", "in", "lines", "fig"]) -> margin:
|
|
57
107
|
"""
|
|
58
|
-
Return
|
|
108
|
+
Return margin in request unit
|
|
59
109
|
"""
|
|
110
|
+
m = copy(self)
|
|
111
|
+
if self.unit == unit:
|
|
112
|
+
return m
|
|
113
|
+
|
|
114
|
+
conversion = f"{self.unit}-{unit}"
|
|
115
|
+
W, H = self.figure_size
|
|
116
|
+
|
|
117
|
+
with suppress(ZeroDivisionError):
|
|
118
|
+
m.t = self._convert(conversion, H, self.t)
|
|
119
|
+
with suppress(ZeroDivisionError):
|
|
120
|
+
m.r = self._convert(conversion, W, self.r)
|
|
121
|
+
with suppress(ZeroDivisionError):
|
|
122
|
+
m.b = self._convert(conversion, H, self.b)
|
|
123
|
+
with suppress(ZeroDivisionError):
|
|
124
|
+
m.l = self._convert(conversion, W, self.l)
|
|
125
|
+
|
|
126
|
+
m.unit = unit
|
|
127
|
+
return m
|
|
128
|
+
|
|
129
|
+
def _convert(self, conversion: str, D: float, value: float) -> float:
|
|
60
130
|
dpi = 72
|
|
61
|
-
|
|
62
|
-
from_units = self.units
|
|
63
|
-
to_units = units
|
|
64
|
-
W: float
|
|
65
|
-
H: float
|
|
66
|
-
W, H = self.theme.getp("figure_size") # inches
|
|
67
|
-
L = (W * dpi) if loc in "tb" else (H * dpi) # pts
|
|
131
|
+
L = D * dpi # pts
|
|
68
132
|
|
|
69
133
|
functions: dict[str, Callable[[float], float]] = {
|
|
70
134
|
"fig-in": lambda x: x * L / dpi,
|
|
71
|
-
"fig-lines": lambda x: x * L /
|
|
135
|
+
"fig-lines": lambda x: x * L / self.fontsize,
|
|
72
136
|
"fig-pt": lambda x: x * L,
|
|
73
137
|
"in-fig": lambda x: x * dpi / L,
|
|
74
|
-
"in-lines": lambda x: x * dpi /
|
|
138
|
+
"in-lines": lambda x: x * dpi / self.fontsize,
|
|
75
139
|
"in-pt": lambda x: x * dpi,
|
|
76
|
-
"lines-fig": lambda x: x *
|
|
77
|
-
"lines-in": lambda x: x *
|
|
78
|
-
"lines-pt": lambda x: x *
|
|
140
|
+
"lines-fig": lambda x: x * self.fontsize / L,
|
|
141
|
+
"lines-in": lambda x: x * self.fontsize / dpi,
|
|
142
|
+
"lines-pt": lambda x: x * self.fontsize,
|
|
79
143
|
"pt-fig": lambda x: x / L,
|
|
80
144
|
"pt-in": lambda x: x / dpi,
|
|
81
|
-
"pt-lines": lambda x: x /
|
|
145
|
+
"pt-lines": lambda x: x / self.fontsize,
|
|
82
146
|
}
|
|
83
147
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
148
|
+
return functions[conversion](value)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def margin_auto(
|
|
152
|
+
t: float = 0.0,
|
|
153
|
+
r: float | None = None,
|
|
154
|
+
b: float | None = None,
|
|
155
|
+
l: float | None = None,
|
|
156
|
+
unit: Literal["pt", "in", "lines", "fig"] = "pt",
|
|
157
|
+
) -> margin:
|
|
158
|
+
"""
|
|
159
|
+
Create margin with minimal arguments
|
|
160
|
+
"""
|
|
161
|
+
if r is None:
|
|
162
|
+
r = t
|
|
163
|
+
if b is None:
|
|
164
|
+
b = t
|
|
165
|
+
if l is None:
|
|
166
|
+
l = r
|
|
167
|
+
return margin(t, r, b, l, unit)
|
plotnine/themes/targets.py
CHANGED
|
@@ -21,7 +21,7 @@ class ThemeTargets:
|
|
|
21
21
|
"""
|
|
22
22
|
Artists that will be themed
|
|
23
23
|
|
|
24
|
-
This includes only artist that cannot be accessed
|
|
24
|
+
This includes only artist that cannot be easily accessed from
|
|
25
25
|
the figure or the axes.
|
|
26
26
|
"""
|
|
27
27
|
|
|
@@ -37,7 +37,9 @@ class ThemeTargets:
|
|
|
37
37
|
panel_border: list[Rectangle] = field(default_factory=list)
|
|
38
38
|
plot_caption: Optional[Text] = None
|
|
39
39
|
plot_subtitle: Optional[Text] = None
|
|
40
|
+
plot_tag: Optional[Text] = None
|
|
40
41
|
plot_title: Optional[Text] = None
|
|
42
|
+
plot_background: Optional[Rectangle] = None
|
|
41
43
|
strip_background_x: list[StripTextPatch] = field(default_factory=list)
|
|
42
44
|
strip_background_y: list[StripTextPatch] = field(default_factory=list)
|
|
43
45
|
strip_text_x: list[StripText] = field(default_factory=list)
|