plotnine 0.15.0a1__py3-none-any.whl → 0.15.0a3__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/_mpl/layout_manager/_layout_items.py +85 -23
- plotnine/_mpl/layout_manager/_layout_tree.py +16 -6
- plotnine/_mpl/layout_manager/_spaces.py +5 -5
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +150 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +30 -2
- plotnine/doctools.py +1 -1
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +20 -8
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +4 -4
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_dotplot.py +1 -1
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +3 -3
- plotnine/geoms/geom_path.py +10 -11
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_raster.py +4 -5
- plotnine/geoms/geom_rect.py +3 -4
- plotnine/geoms/geom_ribbon.py +7 -7
- plotnine/geoms/geom_rug.py +1 -1
- plotnine/geoms/geom_segment.py +2 -2
- plotnine/geoms/geom_smooth.py +3 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +4 -5
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/guides/guides.py +1 -1
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/layer.py +18 -12
- plotnine/mapping/_eval_environment.py +1 -1
- plotnine/scales/scale_color.py +46 -14
- plotnine/scales/scale_continuous.py +5 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +2 -2
- plotnine/scales/scale_identity.py +10 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +19 -19
- plotnine/stats/stat.py +15 -25
- plotnine/stats/stat_bin.py +2 -5
- plotnine/stats/stat_bin_2d.py +7 -9
- plotnine/stats/stat_bindot.py +6 -11
- plotnine/stats/stat_boxplot.py +5 -5
- plotnine/stats/stat_count.py +5 -9
- plotnine/stats/stat_density.py +6 -9
- plotnine/stats/stat_density_2d.py +12 -9
- plotnine/stats/stat_ecdf.py +6 -5
- plotnine/stats/stat_ellipse.py +5 -6
- plotnine/stats/stat_function.py +6 -8
- plotnine/stats/stat_hull.py +2 -3
- plotnine/stats/stat_identity.py +1 -2
- plotnine/stats/stat_pointdensity.py +4 -7
- plotnine/stats/stat_qq.py +45 -20
- plotnine/stats/stat_qq_line.py +15 -11
- plotnine/stats/stat_quantile.py +6 -7
- plotnine/stats/stat_sina.py +12 -14
- plotnine/stats/stat_smooth.py +7 -10
- plotnine/stats/stat_sum.py +1 -2
- plotnine/stats/stat_summary.py +6 -9
- plotnine/stats/stat_summary_bin.py +10 -13
- plotnine/stats/stat_unique.py +1 -2
- plotnine/stats/stat_ydensity.py +7 -10
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme_gray.py +5 -3
- plotnine/themes/theme_matplotlib.py +5 -4
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +11 -4
- plotnine/themes/themeable.py +2 -2
- plotnine/typing.py +2 -2
- {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/METADATA +7 -4
- {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/RECORD +86 -85
- {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/WHEEL +1 -1
- {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/top_level.txt +0 -0
plotnine/_mpl/text.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from functools import lru_cache
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
6
|
+
from matplotlib import artist
|
|
5
7
|
from matplotlib.text import Text
|
|
6
8
|
|
|
9
|
+
from plotnine._utils import ha_as_float, va_as_float
|
|
10
|
+
|
|
7
11
|
from .patches import StripTextPatch
|
|
8
12
|
from .utils import bbox_in_axes_space, rel_position
|
|
9
13
|
|
|
@@ -11,7 +15,6 @@ if TYPE_CHECKING:
|
|
|
11
15
|
from matplotlib.backend_bases import RendererBase
|
|
12
16
|
|
|
13
17
|
from plotnine.iapi import strip_draw_info
|
|
14
|
-
from plotnine.typing import HorizontalJustification, VerticalJustification
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class StripText(Text):
|
|
@@ -28,86 +31,170 @@ class StripText(Text):
|
|
|
28
31
|
"transform": info.ax.transAxes,
|
|
29
32
|
"clip_on": False,
|
|
30
33
|
"zorder": 3.3,
|
|
34
|
+
# Since the text can be rotated, it is simpler to anchor it at
|
|
35
|
+
# the center, align it then do the rotation. Vertically,
|
|
36
|
+
# center_baseline places the text in the visual center, but
|
|
37
|
+
# only if it is one line. For multiline text, we are better
|
|
38
|
+
# off with plain center.
|
|
39
|
+
"ha": "center",
|
|
40
|
+
"va": "center_baseline" if info.is_oneline else "center",
|
|
41
|
+
"rotation_mode": "anchor",
|
|
31
42
|
}
|
|
32
43
|
|
|
33
|
-
super().__init__(
|
|
34
|
-
info.x,
|
|
35
|
-
info.y,
|
|
36
|
-
info.label,
|
|
37
|
-
**kwargs,
|
|
38
|
-
)
|
|
44
|
+
super().__init__(0, 0, info.label, **kwargs)
|
|
39
45
|
self.draw_info = info
|
|
40
46
|
self.patch = StripTextPatch(self)
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
# self.set_horizontalalignment("center")
|
|
49
|
+
# self.set_verticalalignment(
|
|
50
|
+
# "center_baseline" if info.is_oneline else "center"
|
|
51
|
+
# )
|
|
52
|
+
# self.set_rotation_mode("anchor")
|
|
53
|
+
|
|
54
|
+
# TODO: This should really be part of the unit conversions in the
|
|
55
|
+
# margin class.
|
|
56
|
+
@lru_cache(2)
|
|
57
|
+
def _line_height(self, renderer) -> float:
|
|
58
|
+
"""
|
|
59
|
+
The line height in display space of the text on the canvas
|
|
60
|
+
"""
|
|
61
|
+
# Text string, (width, height), x, y
|
|
62
|
+
parts: list[tuple[str, tuple[float, float], float, float]]
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# matplotlib.Text._get_layout is a private API and we cannot
|
|
66
|
+
# tell how using it may fail in the future.
|
|
67
|
+
_, parts, _ = self._get_layout(renderer) # pyright: ignore[reportAttributeAccessIssue]
|
|
68
|
+
except Exception:
|
|
69
|
+
from warnings import warn
|
|
70
|
+
|
|
71
|
+
from plotnine.exceptions import PlotnineWarning
|
|
72
|
+
|
|
73
|
+
# The canvas height is nearly always bigger than the stated
|
|
74
|
+
# fontsize. 1.36 is a good multiplication factor obtained by
|
|
75
|
+
# some rough exploration
|
|
76
|
+
f = 1.36
|
|
77
|
+
size = self.get_fontsize()
|
|
78
|
+
height = round(size * f) if isinstance(size, int) else 14
|
|
79
|
+
warn(
|
|
80
|
+
f"Could not calculate line height for {self.get_text()}. "
|
|
81
|
+
"Using an estimate, please let us know about this at "
|
|
82
|
+
"https://github.com/has2k1/plotnine/issues",
|
|
83
|
+
PlotnineWarning,
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
# The the text has multiple lines, we use the maximum height
|
|
87
|
+
# of anyone single line.
|
|
88
|
+
height = max([p[1][1] for p in parts])
|
|
89
|
+
|
|
90
|
+
return height
|
|
91
|
+
|
|
92
|
+
def _set_position(self, renderer):
|
|
93
|
+
"""
|
|
94
|
+
Set the postion of the text within the strip_background
|
|
95
|
+
"""
|
|
96
|
+
# We have to two premises that depend on each other:
|
|
97
|
+
#
|
|
98
|
+
# 1. The breadth of the strip_background grows to accomodate
|
|
99
|
+
# the strip_text.
|
|
100
|
+
# 2. The strip_text is justified within the strip_background.
|
|
101
|
+
#
|
|
102
|
+
# From these we note that the strip_background does not need the
|
|
103
|
+
# position of the strip_text, but it needs its size. Therefore
|
|
104
|
+
# we implement StripTextPatch.get_window_extent can use
|
|
105
|
+
# StripText.get_window_extent, peeking only at the size.
|
|
106
|
+
#
|
|
107
|
+
# And we implement StripText._set_position_* to use
|
|
108
|
+
# StripTextPatch.get_window_extent and make the calculations in
|
|
109
|
+
# both methods independent.
|
|
110
|
+
if self.draw_info.position == "top":
|
|
111
|
+
self._set_position_top(renderer)
|
|
112
|
+
else: # "right"
|
|
113
|
+
self._set_position_right(renderer)
|
|
114
|
+
|
|
115
|
+
def _set_position_top(self, renderer):
|
|
47
116
|
"""
|
|
48
|
-
|
|
117
|
+
Set position of the text within the top strip_background
|
|
49
118
|
"""
|
|
50
119
|
info = self.draw_info
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
120
|
+
ha, va, ax, m = info.ha, info.va, info.ax, info.margin
|
|
121
|
+
|
|
122
|
+
rel_x, rel_y = ha_as_float(ha), va_as_float(va)
|
|
123
|
+
patch_bbox = bbox_in_axes_space(self.patch, ax, renderer)
|
|
124
|
+
text_bbox = bbox_in_axes_space(self, ax, renderer)
|
|
125
|
+
|
|
126
|
+
# line_height and margins in axes space
|
|
127
|
+
line_height = self._line_height(renderer) / ax.bbox.height
|
|
128
|
+
|
|
129
|
+
x = (
|
|
130
|
+
# Justify horizontally within the strip_background
|
|
131
|
+
rel_position(
|
|
132
|
+
rel_x,
|
|
133
|
+
text_bbox.width + (line_height * (m.l + m.r)),
|
|
134
|
+
patch_bbox.x0,
|
|
135
|
+
patch_bbox.x1,
|
|
136
|
+
)
|
|
137
|
+
+ (m.l * line_height)
|
|
138
|
+
+ text_bbox.width / 2
|
|
139
|
+
)
|
|
140
|
+
# Setting the y position based on the bounding box is wrong
|
|
141
|
+
y = (
|
|
142
|
+
rel_position(
|
|
143
|
+
rel_y,
|
|
144
|
+
text_bbox.height,
|
|
145
|
+
patch_bbox.y0 + m.b * line_height,
|
|
146
|
+
patch_bbox.y1 - m.t * line_height,
|
|
147
|
+
)
|
|
148
|
+
+ text_bbox.height / 2
|
|
149
|
+
)
|
|
65
150
|
self.set_position((x, y))
|
|
66
151
|
|
|
67
|
-
def
|
|
152
|
+
def _set_position_right(self, renderer):
|
|
68
153
|
"""
|
|
69
|
-
|
|
154
|
+
Set position of the text within the bottom strip_background
|
|
70
155
|
"""
|
|
71
|
-
# Note that the strip text & background and horizontal but
|
|
72
|
-
# rotated to appear vertical. So we really are still justifying
|
|
73
|
-
# horizontally.
|
|
74
156
|
info = self.draw_info
|
|
75
|
-
|
|
76
|
-
"bottom": 0.0,
|
|
77
|
-
"center": 0.5,
|
|
78
|
-
"top": 1.0,
|
|
79
|
-
}
|
|
80
|
-
rel = lookup.get(info.va, 0.5) if isinstance(info.va, str) else info.va
|
|
81
|
-
patch_bbox = bbox_in_axes_space(self.patch, info.ax, renderer)
|
|
82
|
-
text_bbox = bbox_in_axes_space(self, info.ax, renderer)
|
|
83
|
-
l, b, w, h = info.x, info.y, patch_bbox.width, info.box_height
|
|
84
|
-
l = l + patch_bbox.width * info.strip_align
|
|
85
|
-
x = l + w / 2
|
|
86
|
-
y = rel_position(rel, text_bbox.height, patch_bbox.y0, patch_bbox.y1)
|
|
87
|
-
self.set_horizontalalignment("right") # 90CW right means bottom
|
|
88
|
-
self.patch.set_bounds(l, b, w, h)
|
|
89
|
-
self.set_position((x, y))
|
|
157
|
+
ha, va, ax, m = info.ha, info.va, info.ax, info.margin
|
|
90
158
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
159
|
+
# bboxes in axes space
|
|
160
|
+
patch_bbox = bbox_in_axes_space(self.patch, ax, renderer)
|
|
161
|
+
text_bbox = bbox_in_axes_space(self, ax, renderer)
|
|
94
162
|
|
|
95
|
-
#
|
|
96
|
-
self.
|
|
163
|
+
# line_height in axes space
|
|
164
|
+
line_height = self._line_height(renderer) / ax.bbox.width
|
|
97
165
|
|
|
98
|
-
|
|
99
|
-
if self.draw_info.position == "top":
|
|
100
|
-
self._justify_horizontally(renderer)
|
|
101
|
-
else: # "right"
|
|
102
|
-
self._justify_vertically(renderer)
|
|
166
|
+
rel_x, rel_y = ha_as_float(ha), va_as_float(va)
|
|
103
167
|
|
|
104
|
-
|
|
105
|
-
|
|
168
|
+
x = (
|
|
169
|
+
rel_position(
|
|
170
|
+
rel_x,
|
|
171
|
+
text_bbox.width,
|
|
172
|
+
patch_bbox.x0 + m.l * line_height,
|
|
173
|
+
patch_bbox.x1 - m.r * line_height,
|
|
174
|
+
)
|
|
175
|
+
+ text_bbox.width / 2
|
|
176
|
+
)
|
|
177
|
+
y = (
|
|
178
|
+
# Justify vertically within the strip_background
|
|
179
|
+
rel_position(
|
|
180
|
+
rel_y,
|
|
181
|
+
text_bbox.height + ((m.b + m.t) * line_height),
|
|
182
|
+
patch_bbox.y0,
|
|
183
|
+
patch_bbox.y1,
|
|
184
|
+
)
|
|
185
|
+
+ (m.b * line_height)
|
|
186
|
+
+ text_bbox.height / 2
|
|
187
|
+
)
|
|
188
|
+
self.set_position((x, y))
|
|
106
189
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
190
|
+
@artist.allow_rasterization
|
|
191
|
+
def draw(self, renderer: RendererBase):
|
|
192
|
+
"""
|
|
193
|
+
Draw text along with the patch
|
|
194
|
+
"""
|
|
195
|
+
if not self.get_visible():
|
|
196
|
+
return
|
|
110
197
|
|
|
111
|
-
|
|
198
|
+
self._set_position(renderer)
|
|
112
199
|
self.patch.draw(renderer)
|
|
113
200
|
return super().draw(renderer)
|
plotnine/_mpl/utils.py
CHANGED
plotnine/_utils/__init__.py
CHANGED
|
@@ -35,8 +35,10 @@ if TYPE_CHECKING:
|
|
|
35
35
|
DataLike,
|
|
36
36
|
FloatArray,
|
|
37
37
|
FloatArrayLike,
|
|
38
|
+
HorizontalJustification,
|
|
38
39
|
IntArray,
|
|
39
40
|
Side,
|
|
41
|
+
VerticalJustification,
|
|
40
42
|
)
|
|
41
43
|
|
|
42
44
|
T = TypeVar("T")
|
|
@@ -344,6 +346,8 @@ def _id_var(x: AnyArrayLike, drop: bool = False) -> list[int]:
|
|
|
344
346
|
# NaNs are -1, we give them the highest code
|
|
345
347
|
nan_code = -1
|
|
346
348
|
new_nan_code = np.max(x.cat.codes) + 1
|
|
349
|
+
# TODO: We are assuming that x is of type Sequence[int|nan]
|
|
350
|
+
# is that accurate.
|
|
347
351
|
lst = [val if val != nan_code else new_nan_code for val in x]
|
|
348
352
|
else:
|
|
349
353
|
lst = list(x.cat.codes + 1)
|
|
@@ -357,7 +361,7 @@ def _id_var(x: AnyArrayLike, drop: bool = False) -> list[int]:
|
|
|
357
361
|
lst = match(x, levels)
|
|
358
362
|
lst = [item + 1 for item in lst]
|
|
359
363
|
|
|
360
|
-
return lst
|
|
364
|
+
return lst # pyright: ignore[reportReturnType]
|
|
361
365
|
|
|
362
366
|
|
|
363
367
|
def join_keys(x, y, by=None):
|
|
@@ -511,7 +515,7 @@ def remove_missing(
|
|
|
511
515
|
if finite:
|
|
512
516
|
lst = [np.inf, -np.inf]
|
|
513
517
|
to_replace = {v: lst for v in vars}
|
|
514
|
-
data.replace(to_replace, np.nan, inplace=True)
|
|
518
|
+
data.replace(to_replace, np.nan, inplace=True)
|
|
515
519
|
txt = "non-finite"
|
|
516
520
|
else:
|
|
517
521
|
txt = "missing"
|
|
@@ -1264,3 +1268,27 @@ def ensure_xy_location(
|
|
|
1264
1268
|
if isinstance(h, (int, float)) and isinstance(v, (int, float)):
|
|
1265
1269
|
return (h, v)
|
|
1266
1270
|
raise ValueError(f"Cannot make a location from '{loc}'")
|
|
1271
|
+
|
|
1272
|
+
|
|
1273
|
+
def ha_as_float(ha: HorizontalJustification | float) -> float:
|
|
1274
|
+
"""
|
|
1275
|
+
Return horizontal alignment as a float
|
|
1276
|
+
"""
|
|
1277
|
+
lookup = {"left": 0.0, "center": 0.5, "right": 1.0}
|
|
1278
|
+
return lookup[ha] if isinstance(ha, str) else ha
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
def va_as_float(va: VerticalJustification | float) -> float:
|
|
1282
|
+
"""
|
|
1283
|
+
Return vertical alignment as a float
|
|
1284
|
+
"""
|
|
1285
|
+
lookup = {
|
|
1286
|
+
"top": 1.0,
|
|
1287
|
+
"center": 0.5,
|
|
1288
|
+
"bottom": 0.0,
|
|
1289
|
+
# baseline and center_baseline are valid for texts but we do
|
|
1290
|
+
# not handle them accurately
|
|
1291
|
+
"baseline": 0.5,
|
|
1292
|
+
"center_baseline": 0.5,
|
|
1293
|
+
}
|
|
1294
|
+
return lookup[va] if isinstance(va, str) else va
|
plotnine/doctools.py
CHANGED
|
@@ -451,7 +451,7 @@ def document_geom(geom: type[geom]) -> type[geom]:
|
|
|
451
451
|
table = dict_to_table(("Aesthetic", "Default value"), contents)
|
|
452
452
|
aesthetics_table = AESTHETICS_TABLE_TPL.format(table=table)
|
|
453
453
|
tpl = dedent(geom._aesthetics_doc).strip()
|
|
454
|
-
aesthetics_doc = tpl.
|
|
454
|
+
aesthetics_doc = tpl.replace("{aesthetics_table}", aesthetics_table)
|
|
455
455
|
aesthetics_doc = indent(aesthetics_doc, " " * 4)
|
|
456
456
|
|
|
457
457
|
# common_parameters
|
plotnine/facets/strips.py
CHANGED
|
@@ -67,56 +67,45 @@ class strip:
|
|
|
67
67
|
if position == "top":
|
|
68
68
|
# The x & y values are just starting locations
|
|
69
69
|
# The final location is determined by the layout manager.
|
|
70
|
-
|
|
70
|
+
bg_y = 1
|
|
71
71
|
ha = theme.getp(("strip_text_x", "ha"), "center")
|
|
72
|
-
va = theme.getp(("strip_text_x", "va"), "
|
|
72
|
+
va = theme.getp(("strip_text_x", "va"), "center")
|
|
73
73
|
rotation = theme.getp(("strip_text_x", "rotation"))
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# TODO: Allow two unique paddings for either side.
|
|
77
|
-
# Requires implementing an mpl.patches.boxstyle that recognises
|
|
78
|
-
# two padding values.
|
|
79
|
-
strip_text_margin = (
|
|
80
|
-
theme.getp(("strip_text_x", "margin")).to("lines").b
|
|
81
|
-
)
|
|
74
|
+
bg_height = 0 # Determined by the text size
|
|
75
|
+
margin = theme.getp(("strip_text_x", "margin")).to("lines")
|
|
82
76
|
strip_align = theme.getp("strip_align_x")
|
|
83
77
|
|
|
84
78
|
# x & width properties of the background slide and
|
|
85
79
|
# shrink the strip horizontally.
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
bg_x = theme.getp(("strip_text_x", "x"), 0)
|
|
81
|
+
bg_width = theme.getp(("strip_background_x", "width"), 1)
|
|
88
82
|
|
|
89
83
|
elif position == "right":
|
|
90
84
|
# The x & y values are just starting locations
|
|
91
85
|
# The final location is determined by the layout manager.
|
|
92
|
-
|
|
93
|
-
ha = theme.getp(("strip_text_y", "ha"), "
|
|
86
|
+
bg_x = 1
|
|
87
|
+
ha = theme.getp(("strip_text_y", "ha"), "center")
|
|
94
88
|
va = theme.getp(("strip_text_y", "va"), "center")
|
|
95
89
|
rotation = theme.getp(("strip_text_y", "rotation"))
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Requires implementing an mpl.patches.boxstyle that recognises
|
|
99
|
-
# two padding values.
|
|
100
|
-
strip_text_margin = (
|
|
101
|
-
theme.getp(("strip_text_y", "margin")).to("lines").r
|
|
102
|
-
)
|
|
90
|
+
bg_width = 0 # Determine by the text height
|
|
91
|
+
margin = theme.getp(("strip_text_y", "margin")).to("lines")
|
|
103
92
|
strip_align = theme.getp("strip_align_y")
|
|
104
93
|
|
|
105
94
|
# y & height properties of the background slide and
|
|
106
95
|
# shrink the strip vertically.
|
|
107
|
-
|
|
108
|
-
|
|
96
|
+
bg_y = theme.getp(("strip_text_y", "y"), 0)
|
|
97
|
+
bg_height = theme.getp(("strip_background_y", "height"), 1)
|
|
109
98
|
else:
|
|
110
99
|
raise ValueError(f"Unknown position for strip text: {position!r}")
|
|
111
100
|
|
|
112
101
|
return strip_draw_info(
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
bg_x=bg_x,
|
|
103
|
+
bg_y=bg_y,
|
|
115
104
|
ha=ha,
|
|
116
105
|
va=va,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
bg_width=bg_width,
|
|
107
|
+
bg_height=bg_height,
|
|
108
|
+
margin=margin,
|
|
120
109
|
strip_align=strip_align,
|
|
121
110
|
position=position,
|
|
122
111
|
label=self.label_info.text(),
|
|
@@ -46,9 +46,7 @@ class _geom_logticks(geom_rug):
|
|
|
46
46
|
}
|
|
47
47
|
draw_legend = staticmethod(geom_path.draw_legend)
|
|
48
48
|
|
|
49
|
-
def draw_layer(
|
|
50
|
-
self, data: pd.DataFrame, layout: Layout, coord: coord, **params: Any
|
|
51
|
-
):
|
|
49
|
+
def draw_layer(self, data: pd.DataFrame, layout: Layout, coord: coord):
|
|
52
50
|
"""
|
|
53
51
|
Draw ticks on every panel
|
|
54
52
|
"""
|
|
@@ -56,7 +54,7 @@ class _geom_logticks(geom_rug):
|
|
|
56
54
|
ploc = pid - 1
|
|
57
55
|
panel_params = layout.panel_params[ploc]
|
|
58
56
|
ax = layout.axs[ploc]
|
|
59
|
-
self.draw_panel(data, panel_params, coord, ax
|
|
57
|
+
self.draw_panel(data, panel_params, coord, ax)
|
|
60
58
|
|
|
61
59
|
@staticmethod
|
|
62
60
|
def _check_log_scale(
|
|
@@ -184,8 +182,8 @@ class _geom_logticks(geom_rug):
|
|
|
184
182
|
panel_params: panel_view,
|
|
185
183
|
coord: coord,
|
|
186
184
|
ax: Axes,
|
|
187
|
-
**params: Any,
|
|
188
185
|
):
|
|
186
|
+
params = self.params
|
|
189
187
|
# Any passed data is ignored, the relevant data is created
|
|
190
188
|
sides = params["sides"]
|
|
191
189
|
lengths = params["lengths"]
|
|
@@ -203,9 +201,8 @@ class _geom_logticks(geom_rug):
|
|
|
203
201
|
):
|
|
204
202
|
for position, length in zip(tick_positions, lengths):
|
|
205
203
|
data = pd.DataFrame({axis: position, **_aesthetics})
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
)
|
|
204
|
+
params["length"] = length
|
|
205
|
+
geom.draw_group(data, panel_params, coord, ax, params)
|
|
209
206
|
|
|
210
207
|
if isinstance(coord, coord_flip):
|
|
211
208
|
tick_range_x = panel_params.y.range
|
|
@@ -285,4 +282,6 @@ class annotation_logticks(annotate):
|
|
|
285
282
|
linetype=linetype,
|
|
286
283
|
lengths=lengths,
|
|
287
284
|
base=base,
|
|
285
|
+
inherit_aes=False,
|
|
286
|
+
show_legend=False,
|
|
288
287
|
)
|
|
@@ -72,6 +72,8 @@ class annotation_stripes(annotate):
|
|
|
72
72
|
fill_range=fill_range,
|
|
73
73
|
extend=extend,
|
|
74
74
|
direction=direction,
|
|
75
|
+
inherit_aes=False,
|
|
76
|
+
show_legend=False,
|
|
75
77
|
**kwargs,
|
|
76
78
|
)
|
|
77
79
|
|
|
@@ -94,9 +96,7 @@ class _geom_stripes(geom):
|
|
|
94
96
|
}
|
|
95
97
|
draw_legend = staticmethod(geom_polygon.draw_legend)
|
|
96
98
|
|
|
97
|
-
def draw_layer(
|
|
98
|
-
self, data: pd.DataFrame, layout: Layout, coord: coord, **params: Any
|
|
99
|
-
):
|
|
99
|
+
def draw_layer(self, data: pd.DataFrame, layout: Layout, coord: coord):
|
|
100
100
|
"""
|
|
101
101
|
Draw stripes on every panel
|
|
102
102
|
"""
|
|
@@ -104,7 +104,7 @@ class _geom_stripes(geom):
|
|
|
104
104
|
ploc = pid - 1
|
|
105
105
|
panel_params = layout.panel_params[ploc]
|
|
106
106
|
ax = layout.axs[ploc]
|
|
107
|
-
self.draw_group(data, panel_params, coord, ax,
|
|
107
|
+
self.draw_group(data, panel_params, coord, ax, self.params)
|
|
108
108
|
|
|
109
109
|
@staticmethod
|
|
110
110
|
def draw_group(
|
|
@@ -112,7 +112,7 @@ class _geom_stripes(geom):
|
|
|
112
112
|
panel_params: panel_view,
|
|
113
113
|
coord: coord,
|
|
114
114
|
ax: Axes,
|
|
115
|
-
|
|
115
|
+
params: dict[str, Any],
|
|
116
116
|
):
|
|
117
117
|
extend = params["extend"]
|
|
118
118
|
fill_range = params["fill_range"]
|
|
@@ -193,4 +193,4 @@ class _geom_stripes(geom):
|
|
|
193
193
|
}
|
|
194
194
|
)
|
|
195
195
|
|
|
196
|
-
return geom_rect.draw_group(data, panel_params, coord, ax,
|
|
196
|
+
return geom_rect.draw_group(data, panel_params, coord, ax, params)
|
plotnine/geoms/geom.py
CHANGED
|
@@ -171,6 +171,21 @@ class geom(ABC, metaclass=Register):
|
|
|
171
171
|
|
|
172
172
|
return result
|
|
173
173
|
|
|
174
|
+
def setup_params(self, data: pd.DataFrame):
|
|
175
|
+
"""
|
|
176
|
+
Override this method to verify and/or adjust parameters
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
data :
|
|
181
|
+
Data
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
out :
|
|
186
|
+
Parameters used by the geoms.
|
|
187
|
+
"""
|
|
188
|
+
|
|
174
189
|
def setup_data(self, data: pd.DataFrame) -> pd.DataFrame:
|
|
175
190
|
"""
|
|
176
191
|
Modify the data before drawing takes place
|
|
@@ -261,9 +276,7 @@ class geom(ABC, metaclass=Register):
|
|
|
261
276
|
|
|
262
277
|
return data
|
|
263
278
|
|
|
264
|
-
def draw_layer(
|
|
265
|
-
self, data: pd.DataFrame, layout: Layout, coord: coord, **params: Any
|
|
266
|
-
):
|
|
279
|
+
def draw_layer(self, data: pd.DataFrame, layout: Layout, coord: coord):
|
|
267
280
|
"""
|
|
268
281
|
Draw layer across all panels
|
|
269
282
|
|
|
@@ -289,7 +302,7 @@ class geom(ABC, metaclass=Register):
|
|
|
289
302
|
ploc = pdata["PANEL"].iloc[0] - 1
|
|
290
303
|
panel_params = layout.panel_params[ploc]
|
|
291
304
|
ax = layout.axs[ploc]
|
|
292
|
-
self.draw_panel(pdata, panel_params, coord, ax
|
|
305
|
+
self.draw_panel(pdata, panel_params, coord, ax)
|
|
293
306
|
|
|
294
307
|
def draw_panel(
|
|
295
308
|
self,
|
|
@@ -297,7 +310,6 @@ class geom(ABC, metaclass=Register):
|
|
|
297
310
|
panel_params: panel_view,
|
|
298
311
|
coord: coord,
|
|
299
312
|
ax: Axes,
|
|
300
|
-
**params: Any,
|
|
301
313
|
):
|
|
302
314
|
"""
|
|
303
315
|
Plot all groups
|
|
@@ -331,7 +343,7 @@ class geom(ABC, metaclass=Register):
|
|
|
331
343
|
"""
|
|
332
344
|
for _, gdata in data.groupby("group"):
|
|
333
345
|
gdata.reset_index(inplace=True, drop=True)
|
|
334
|
-
self.draw_group(gdata, panel_params, coord, ax,
|
|
346
|
+
self.draw_group(gdata, panel_params, coord, ax, self.params)
|
|
335
347
|
|
|
336
348
|
@staticmethod
|
|
337
349
|
def draw_group(
|
|
@@ -339,7 +351,7 @@ class geom(ABC, metaclass=Register):
|
|
|
339
351
|
panel_params: panel_view,
|
|
340
352
|
coord: coord,
|
|
341
353
|
ax: Axes,
|
|
342
|
-
|
|
354
|
+
params: dict[str, Any],
|
|
343
355
|
):
|
|
344
356
|
"""
|
|
345
357
|
Plot data belonging to a group.
|
|
@@ -376,7 +388,7 @@ class geom(ABC, metaclass=Register):
|
|
|
376
388
|
panel_params: panel_view,
|
|
377
389
|
coord: coord,
|
|
378
390
|
ax: Axes,
|
|
379
|
-
|
|
391
|
+
params: dict[str, Any],
|
|
380
392
|
):
|
|
381
393
|
"""
|
|
382
394
|
Plot data belonging to a unit.
|
plotnine/geoms/geom_abline.py
CHANGED
|
@@ -102,7 +102,6 @@ class geom_abline(geom):
|
|
|
102
102
|
panel_params: panel_view,
|
|
103
103
|
coord: coord,
|
|
104
104
|
ax: Axes,
|
|
105
|
-
**params: Any,
|
|
106
105
|
):
|
|
107
106
|
"""
|
|
108
107
|
Plot all groups
|
|
@@ -116,4 +115,6 @@ class geom_abline(geom):
|
|
|
116
115
|
|
|
117
116
|
for _, gdata in data.groupby("group"):
|
|
118
117
|
gdata.reset_index(inplace=True)
|
|
119
|
-
geom_segment.draw_group(
|
|
118
|
+
geom_segment.draw_group(
|
|
119
|
+
gdata, panel_params, coord, ax, self.params
|
|
120
|
+
)
|
plotnine/geoms/geom_blank.py
CHANGED
|
@@ -6,8 +6,6 @@ from ..doctools import document
|
|
|
6
6
|
from .geom import geom
|
|
7
7
|
|
|
8
8
|
if typing.TYPE_CHECKING:
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
9
|
import pandas as pd
|
|
12
10
|
from matplotlib.axes import Axes
|
|
13
11
|
|
|
@@ -39,7 +37,6 @@ class geom_blank(geom):
|
|
|
39
37
|
panel_params: panel_view,
|
|
40
38
|
coord: coord,
|
|
41
39
|
ax: Axes,
|
|
42
|
-
**params: Any,
|
|
43
40
|
):
|
|
44
41
|
pass
|
|
45
42
|
|
plotnine/geoms/geom_boxplot.py
CHANGED
|
@@ -183,7 +183,7 @@ class geom_boxplot(geom):
|
|
|
183
183
|
panel_params: panel_view,
|
|
184
184
|
coord: coord,
|
|
185
185
|
ax: Axes,
|
|
186
|
-
|
|
186
|
+
params: dict[str, Any],
|
|
187
187
|
):
|
|
188
188
|
def flat(*args: pd.Series[Any]) -> npt.NDArray[Any]:
|
|
189
189
|
"""Flatten list-likes"""
|
|
@@ -245,11 +245,11 @@ class geom_boxplot(geom):
|
|
|
245
245
|
outliers["shape"] = outlier_value("shape")
|
|
246
246
|
outliers["size"] = outlier_value("size")
|
|
247
247
|
outliers["stroke"] = outlier_value("stroke")
|
|
248
|
-
geom_point.draw_group(outliers, panel_params, coord, ax,
|
|
248
|
+
geom_point.draw_group(outliers, panel_params, coord, ax, params)
|
|
249
249
|
|
|
250
250
|
# plot
|
|
251
|
-
geom_segment.draw_group(whiskers, panel_params, coord, ax,
|
|
252
|
-
geom_crossbar.draw_group(box, panel_params, coord, ax,
|
|
251
|
+
geom_segment.draw_group(whiskers, panel_params, coord, ax, params)
|
|
252
|
+
geom_crossbar.draw_group(box, panel_params, coord, ax, params)
|
|
253
253
|
|
|
254
254
|
@staticmethod
|
|
255
255
|
def draw_legend(
|
plotnine/geoms/geom_crossbar.py
CHANGED
|
@@ -78,7 +78,7 @@ class geom_crossbar(geom):
|
|
|
78
78
|
panel_params: panel_view,
|
|
79
79
|
coord: coord,
|
|
80
80
|
ax: Axes,
|
|
81
|
-
|
|
81
|
+
params: dict[str, Any],
|
|
82
82
|
):
|
|
83
83
|
y = data["y"]
|
|
84
84
|
xmin = data["xmin"]
|
|
@@ -160,8 +160,8 @@ class geom_crossbar(geom):
|
|
|
160
160
|
)
|
|
161
161
|
|
|
162
162
|
copy_missing_columns(box, data)
|
|
163
|
-
geom_polygon.draw_group(box, panel_params, coord, ax,
|
|
164
|
-
geom_segment.draw_group(middle, panel_params, coord, ax,
|
|
163
|
+
geom_polygon.draw_group(box, panel_params, coord, ax, params)
|
|
164
|
+
geom_segment.draw_group(middle, panel_params, coord, ax, params)
|
|
165
165
|
|
|
166
166
|
@staticmethod
|
|
167
167
|
def draw_legend(
|
plotnine/geoms/geom_dotplot.py
CHANGED