plotnine 0.15.0.dev3__py3-none-any.whl → 0.15.2__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 +2 -0
- plotnine/_mpl/layout_manager/_engine.py +1 -1
- plotnine/_mpl/layout_manager/_layout_items.py +126 -41
- plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
- plotnine/_mpl/layout_manager/_spaces.py +305 -101
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +144 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +50 -107
- plotnine/_utils/context.py +78 -2
- plotnine/_utils/ipython.py +35 -51
- plotnine/_utils/quarto.py +26 -0
- plotnine/_utils/yippie.py +115 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/composition/_beside.py +55 -0
- plotnine/composition/_compose.py +471 -0
- plotnine/composition/_plot_spacer.py +60 -0
- plotnine/composition/_stack.py +55 -0
- plotnine/coords/coord.py +3 -3
- plotnine/data/__init__.py +31 -0
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +4 -4
- plotnine/facets/facet.py +4 -4
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotate.py +13 -13
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +60 -27
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_area.py +2 -2
- plotnine/geoms/geom_bar.py +1 -0
- plotnine/geoms/geom_bin_2d.py +6 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +8 -4
- plotnine/geoms/geom_col.py +2 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +2 -2
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +5 -5
- plotnine/geoms/geom_path.py +11 -12
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointdensity.py +4 -0
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_qq.py +4 -0
- plotnine/geoms/geom_qq_line.py +4 -0
- plotnine/geoms/geom_quantile.py +4 -0
- 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_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +7 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +8 -5
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/ggplot.py +64 -85
- plotnine/guides/guide.py +7 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +3 -3
- plotnine/guides/guides.py +6 -6
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/labels.py +3 -3
- plotnine/layer.py +36 -19
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/_env.py +13 -2
- plotnine/mapping/_eval_environment.py +1 -1
- plotnine/mapping/aes.py +85 -49
- plotnine/scales/__init__.py +2 -0
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +3 -3
- plotnine/scales/scale_color.py +82 -18
- plotnine/scales/scale_continuous.py +6 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/scales/scale_identity.py +21 -2
- plotnine/scales/scale_manual.py +8 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +23 -36
- plotnine/stats/stat.py +20 -32
- plotnine/stats/stat_bin.py +6 -5
- plotnine/stats/stat_bin_2d.py +11 -9
- plotnine/stats/stat_bindot.py +13 -16
- plotnine/stats/stat_boxplot.py +6 -6
- plotnine/stats/stat_count.py +6 -9
- plotnine/stats/stat_density.py +7 -10
- plotnine/stats/stat_density_2d.py +12 -8
- plotnine/stats/stat_ecdf.py +7 -6
- plotnine/stats/stat_ellipse.py +9 -6
- plotnine/stats/stat_function.py +10 -8
- plotnine/stats/stat_hull.py +6 -3
- plotnine/stats/stat_identity.py +5 -2
- plotnine/stats/stat_pointdensity.py +5 -7
- plotnine/stats/stat_qq.py +46 -20
- plotnine/stats/stat_qq_line.py +16 -11
- plotnine/stats/stat_quantile.py +15 -9
- plotnine/stats/stat_sina.py +13 -15
- plotnine/stats/stat_smooth.py +8 -10
- plotnine/stats/stat_sum.py +5 -2
- plotnine/stats/stat_summary.py +7 -10
- plotnine/stats/stat_summary_bin.py +11 -14
- plotnine/stats/stat_unique.py +5 -2
- plotnine/stats/stat_ydensity.py +8 -11
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme.py +9 -1
- 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 +6 -5
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +5 -5
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +9 -8
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +109 -31
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
- plotnine-0.15.2.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_compose.py +0 -436
- plotnine/plot_composition/_spacer.py +0 -32
- plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
plotnine/ggplot.py
CHANGED
|
@@ -13,7 +13,6 @@ from typing import (
|
|
|
13
13
|
Iterable,
|
|
14
14
|
Optional,
|
|
15
15
|
cast,
|
|
16
|
-
overload,
|
|
17
16
|
)
|
|
18
17
|
from warnings import warn
|
|
19
18
|
|
|
@@ -26,20 +25,20 @@ from ._utils import (
|
|
|
26
25
|
)
|
|
27
26
|
from ._utils.context import plot_context
|
|
28
27
|
from ._utils.ipython import (
|
|
29
|
-
get_display_function,
|
|
30
28
|
get_ipython,
|
|
29
|
+
get_mimebundle,
|
|
31
30
|
is_inline_backend,
|
|
32
31
|
)
|
|
33
|
-
from ._utils.quarto import is_quarto_environment
|
|
32
|
+
from ._utils.quarto import is_knitr_engine, is_quarto_environment
|
|
34
33
|
from .coords import coord_cartesian
|
|
35
34
|
from .exceptions import PlotnineError, PlotnineWarning
|
|
36
35
|
from .facets import facet_null
|
|
37
36
|
from .facets.layout import Layout
|
|
38
37
|
from .geoms.geom_blank import geom_blank
|
|
39
38
|
from .guides.guides import guides
|
|
40
|
-
from .iapi import mpl_save_view
|
|
39
|
+
from .iapi import labels_view, mpl_save_view
|
|
41
40
|
from .layer import Layers
|
|
42
|
-
from .mapping.aes import aes
|
|
41
|
+
from .mapping.aes import aes
|
|
43
42
|
from .options import get_option
|
|
44
43
|
from .scales.scales import Scales
|
|
45
44
|
from .themes.theme import theme, theme_get
|
|
@@ -53,18 +52,17 @@ if TYPE_CHECKING:
|
|
|
53
52
|
|
|
54
53
|
from plotnine import watermark
|
|
55
54
|
from plotnine._mpl.gridspec import p9GridSpec
|
|
55
|
+
from plotnine.composition import Compose
|
|
56
56
|
from plotnine.coords.coord import coord
|
|
57
57
|
from plotnine.facets.facet import facet
|
|
58
|
-
from plotnine.
|
|
59
|
-
from plotnine.plot_composition import Compose
|
|
60
|
-
from plotnine.typing import DataLike
|
|
58
|
+
from plotnine.typing import DataLike, FigureFormat, MimeBundle
|
|
61
59
|
|
|
62
60
|
class PlotAddable(Protocol):
|
|
63
61
|
"""
|
|
64
62
|
Object that can be added to a ggplot object
|
|
65
63
|
"""
|
|
66
64
|
|
|
67
|
-
def __radd__(self,
|
|
65
|
+
def __radd__(self, other: ggplot) -> ggplot:
|
|
68
66
|
"""
|
|
69
67
|
Add to ggplot object
|
|
70
68
|
|
|
@@ -119,7 +117,7 @@ class ggplot:
|
|
|
119
117
|
self.data = data
|
|
120
118
|
self.mapping = mapping if mapping is not None else aes()
|
|
121
119
|
self.facet: facet = facet_null()
|
|
122
|
-
self.labels =
|
|
120
|
+
self.labels = labels_view()
|
|
123
121
|
self.layers = Layers()
|
|
124
122
|
self.guides = guides()
|
|
125
123
|
self.scales = Scales()
|
|
@@ -139,21 +137,47 @@ class ggplot:
|
|
|
139
137
|
w, h = self.theme._figure_size_px
|
|
140
138
|
return f"<ggplot: ({w} x {h})>"
|
|
141
139
|
|
|
142
|
-
def
|
|
140
|
+
def __repr__(self):
|
|
141
|
+
# knitr relies on __repr__ to automatically print the last object
|
|
142
|
+
# in a cell.
|
|
143
|
+
if is_knitr_engine():
|
|
144
|
+
self.show()
|
|
145
|
+
return ""
|
|
146
|
+
return super().__repr__()
|
|
147
|
+
|
|
148
|
+
def _repr_mimebundle_(self, include=None, exclude=None) -> MimeBundle:
|
|
143
149
|
"""
|
|
144
|
-
|
|
150
|
+
Return dynamic MIME bundle for plot display
|
|
151
|
+
|
|
152
|
+
This method is called when a ggplot object is the last in the cell.
|
|
145
153
|
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
Notes
|
|
155
|
+
-----
|
|
156
|
+
- https://ipython.readthedocs.io/en/stable/config/integrating.html
|
|
148
157
|
"""
|
|
149
|
-
|
|
158
|
+
ip = get_ipython()
|
|
159
|
+
format: FigureFormat = (
|
|
160
|
+
get_option("figure_format")
|
|
161
|
+
or (ip and ip.config.InlineBackend.get("figure_format"))
|
|
162
|
+
or "retina"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# While jpegs can be displayed as retina, we restrict the output
|
|
166
|
+
# of "retina" to png
|
|
167
|
+
if format == "retina":
|
|
168
|
+
self = copy(self)
|
|
169
|
+
self.theme = self.theme.to_retina()
|
|
170
|
+
|
|
171
|
+
buf = BytesIO()
|
|
172
|
+
self.save(buf, "png" if format == "retina" else format, verbose=False)
|
|
173
|
+
figure_size_px = self.theme._figure_size_px
|
|
174
|
+
return get_mimebundle(buf.getvalue(), format, figure_size_px)
|
|
150
175
|
|
|
151
176
|
def show(self):
|
|
152
177
|
"""
|
|
153
178
|
Show plot using the matplotlib backend set by the user
|
|
154
179
|
|
|
155
|
-
|
|
156
|
-
the object.
|
|
180
|
+
This function is called for its side-effects.
|
|
157
181
|
"""
|
|
158
182
|
# Prevent against any modifications to the users
|
|
159
183
|
# ggplot object. Do the copy here as we may/may not
|
|
@@ -161,37 +185,13 @@ class ggplot:
|
|
|
161
185
|
self = deepcopy(self)
|
|
162
186
|
|
|
163
187
|
if is_inline_backend() or is_quarto_environment():
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
self.
|
|
188
|
+
from IPython.display import display
|
|
189
|
+
|
|
190
|
+
data, metadata = self._repr_mimebundle_()
|
|
191
|
+
display(data, metadata=metadata, raw=True)
|
|
167
192
|
else:
|
|
168
193
|
self.draw(show=True)
|
|
169
194
|
|
|
170
|
-
def _display(self):
|
|
171
|
-
"""
|
|
172
|
-
Display plot in the cells output
|
|
173
|
-
|
|
174
|
-
This function is called for its side-effects.
|
|
175
|
-
|
|
176
|
-
It plots the plot to an io buffer, then uses ipython display
|
|
177
|
-
methods to show the result
|
|
178
|
-
"""
|
|
179
|
-
ip = get_ipython()
|
|
180
|
-
format = get_option("figure_format") or ip.config.InlineBackend.get(
|
|
181
|
-
"figure_format", "retina"
|
|
182
|
-
)
|
|
183
|
-
# While jpegs can be displayed as retina, we restrict the output
|
|
184
|
-
# of "retina" to png
|
|
185
|
-
if format == "retina":
|
|
186
|
-
self = copy(self)
|
|
187
|
-
self.theme = self.theme.to_retina()
|
|
188
|
-
|
|
189
|
-
buf = BytesIO()
|
|
190
|
-
self.save(buf, "png" if format == "retina" else format, verbose=False)
|
|
191
|
-
figure_size_px = self.theme._figure_size_px
|
|
192
|
-
display_func = get_display_function(format, figure_size_px)
|
|
193
|
-
display_func(buf.getvalue())
|
|
194
|
-
|
|
195
195
|
def __deepcopy__(self, memo: dict[Any, Any]) -> ggplot:
|
|
196
196
|
"""
|
|
197
197
|
Deep copy without copying the dataframe and environment
|
|
@@ -230,18 +230,10 @@ class ggplot:
|
|
|
230
230
|
other.__radd__(self)
|
|
231
231
|
return self
|
|
232
232
|
|
|
233
|
-
@overload
|
|
234
|
-
def __add__(
|
|
235
|
-
self, rhs: PlotAddable | list[PlotAddable] | None
|
|
236
|
-
) -> ggplot: ...
|
|
237
|
-
|
|
238
|
-
@overload
|
|
239
|
-
def __add__(self, rhs: ggplot | Compose) -> Compose: ...
|
|
240
|
-
|
|
241
233
|
def __add__(
|
|
242
234
|
self,
|
|
243
|
-
rhs: PlotAddable | list[PlotAddable] | None
|
|
244
|
-
) -> ggplot
|
|
235
|
+
rhs: PlotAddable | list[PlotAddable] | None,
|
|
236
|
+
) -> ggplot:
|
|
245
237
|
"""
|
|
246
238
|
Add to ggplot
|
|
247
239
|
|
|
@@ -251,11 +243,6 @@ class ggplot:
|
|
|
251
243
|
Either an object that knows how to "radd"
|
|
252
244
|
itself to a ggplot, or a list of such objects.
|
|
253
245
|
"""
|
|
254
|
-
from .plot_composition import ADD, Compose
|
|
255
|
-
|
|
256
|
-
if isinstance(rhs, (ggplot, Compose)):
|
|
257
|
-
return ADD([self, rhs])
|
|
258
|
-
|
|
259
246
|
self = deepcopy(self)
|
|
260
247
|
return self.__iadd__(rhs)
|
|
261
248
|
|
|
@@ -263,25 +250,25 @@ class ggplot:
|
|
|
263
250
|
"""
|
|
264
251
|
Compose 2 plots columnwise
|
|
265
252
|
"""
|
|
266
|
-
from .
|
|
253
|
+
from .composition import Beside
|
|
267
254
|
|
|
268
|
-
return
|
|
255
|
+
return Beside([self, rhs])
|
|
269
256
|
|
|
270
257
|
def __truediv__(self, rhs: ggplot | Compose) -> Compose:
|
|
271
258
|
"""
|
|
272
259
|
Compose 2 plots rowwise
|
|
273
260
|
"""
|
|
274
|
-
from .
|
|
261
|
+
from .composition import Stack
|
|
275
262
|
|
|
276
|
-
return
|
|
263
|
+
return Stack([self, rhs])
|
|
277
264
|
|
|
278
265
|
def __sub__(self, rhs: ggplot | Compose) -> Compose:
|
|
279
266
|
"""
|
|
280
267
|
Compose 2 plots columnwise
|
|
281
268
|
"""
|
|
282
|
-
from .
|
|
269
|
+
from .composition import Beside
|
|
283
270
|
|
|
284
|
-
return
|
|
271
|
+
return Beside([self, rhs])
|
|
285
272
|
|
|
286
273
|
def __rrshift__(self, other: DataLike) -> ggplot:
|
|
287
274
|
"""
|
|
@@ -315,10 +302,7 @@ class ggplot:
|
|
|
315
302
|
from ._mpl.layout_manager import PlotnineLayoutEngine
|
|
316
303
|
|
|
317
304
|
with plot_context(self, show=show):
|
|
318
|
-
|
|
319
|
-
self._create_figure()
|
|
320
|
-
figure = self.figure
|
|
321
|
-
|
|
305
|
+
figure = self._setup()
|
|
322
306
|
self._build()
|
|
323
307
|
|
|
324
308
|
# setup
|
|
@@ -341,6 +325,16 @@ class ggplot:
|
|
|
341
325
|
|
|
342
326
|
return figure
|
|
343
327
|
|
|
328
|
+
def _setup(self) -> Figure:
|
|
329
|
+
"""
|
|
330
|
+
Setup this instance for the building process
|
|
331
|
+
"""
|
|
332
|
+
if not hasattr(self, "figure"):
|
|
333
|
+
self._create_figure()
|
|
334
|
+
|
|
335
|
+
self.labels.add_defaults(self.mapping.labels)
|
|
336
|
+
return self.figure
|
|
337
|
+
|
|
344
338
|
def _create_figure(self):
|
|
345
339
|
"""
|
|
346
340
|
Create gridspec for the panels
|
|
@@ -562,21 +556,6 @@ class ggplot:
|
|
|
562
556
|
hash_token = abs(self.__hash__())
|
|
563
557
|
return Path(f"plotnine-save-{hash_token}.{ext}")
|
|
564
558
|
|
|
565
|
-
def _update_labels(self, layer: layer):
|
|
566
|
-
"""
|
|
567
|
-
Update label data for the ggplot
|
|
568
|
-
|
|
569
|
-
Parameters
|
|
570
|
-
----------
|
|
571
|
-
layer : layer
|
|
572
|
-
New layer that has just been added to the ggplot
|
|
573
|
-
object.
|
|
574
|
-
"""
|
|
575
|
-
mapping = make_labels(layer.mapping)
|
|
576
|
-
default = make_labels(layer.stat.DEFAULT_AES)
|
|
577
|
-
mapping.add_defaults(default)
|
|
578
|
-
self.labels.add_defaults(mapping)
|
|
579
|
-
|
|
580
559
|
def save_helper(
|
|
581
560
|
self: ggplot,
|
|
582
561
|
filename: Optional[str | Path | BytesIO] = None,
|
plotnine/guides/guide.py
CHANGED
|
@@ -23,13 +23,13 @@ if TYPE_CHECKING:
|
|
|
23
23
|
from plotnine.typing import (
|
|
24
24
|
LegendPosition,
|
|
25
25
|
Orientation,
|
|
26
|
-
|
|
26
|
+
Side,
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
from .guides import GuidesElements
|
|
30
30
|
|
|
31
31
|
AlignDict: TypeAlias = dict[
|
|
32
|
-
Literal["ha", "va"], dict[tuple[Orientation,
|
|
32
|
+
Literal["ha", "va"], dict[tuple[Orientation, Side], str]
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
|
|
@@ -122,10 +122,7 @@ class guide(ABC, metaclass=Register):
|
|
|
122
122
|
@property
|
|
123
123
|
def _resolved_position_justification(
|
|
124
124
|
self,
|
|
125
|
-
) ->
|
|
126
|
-
tuple[SidePosition, float]
|
|
127
|
-
| tuple[tuple[float, float], tuple[float, float]]
|
|
128
|
-
):
|
|
125
|
+
) -> tuple[Side, float] | tuple[tuple[float, float], tuple[float, float]]:
|
|
129
126
|
"""
|
|
130
127
|
Return the final position & justification to draw the guide
|
|
131
128
|
"""
|
|
@@ -213,7 +210,7 @@ class GuideElements:
|
|
|
213
210
|
)
|
|
214
211
|
|
|
215
212
|
@cached_property
|
|
216
|
-
def text_position(self) ->
|
|
213
|
+
def text_position(self) -> Side:
|
|
217
214
|
raise NotImplementedError
|
|
218
215
|
|
|
219
216
|
@cached_property
|
|
@@ -225,7 +222,7 @@ class GuideElements:
|
|
|
225
222
|
return getattr(_margin, _loc)
|
|
226
223
|
|
|
227
224
|
@cached_property
|
|
228
|
-
def title_position(self) ->
|
|
225
|
+
def title_position(self) -> Side:
|
|
229
226
|
if not (pos := self.theme.getp("legend_title_position")):
|
|
230
227
|
pos = "top" if self.is_vertical else "left"
|
|
231
228
|
return pos
|
|
@@ -244,7 +241,7 @@ class GuideElements:
|
|
|
244
241
|
return direction
|
|
245
242
|
|
|
246
243
|
@cached_property
|
|
247
|
-
def position(self) ->
|
|
244
|
+
def position(self) -> Side | tuple[float, float]:
|
|
248
245
|
if (guide_pos := self.guide.position) == "inside":
|
|
249
246
|
guide_pos = self._position_inside
|
|
250
247
|
|
|
@@ -256,7 +253,7 @@ class GuideElements:
|
|
|
256
253
|
return pos
|
|
257
254
|
|
|
258
255
|
@cached_property
|
|
259
|
-
def _position_inside(self) ->
|
|
256
|
+
def _position_inside(self) -> Side | tuple[float, float]:
|
|
260
257
|
pos = self.theme.getp("legend_position_inside")
|
|
261
258
|
if isinstance(pos, tuple):
|
|
262
259
|
return pos
|
|
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
|
|
|
27
27
|
|
|
28
28
|
from plotnine import theme
|
|
29
29
|
from plotnine.scales.scale import scale
|
|
30
|
-
from plotnine.typing import
|
|
30
|
+
from plotnine.typing import Side
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@dataclass
|
|
@@ -242,7 +242,7 @@ class guide_colorbar(guide):
|
|
|
242
242
|
targets.legend_frame = frame
|
|
243
243
|
|
|
244
244
|
# title + colorbar(with labels)
|
|
245
|
-
lookup: dict[
|
|
245
|
+
lookup: dict[Side, tuple[type[PackerBase], slice]] = {
|
|
246
246
|
"right": (HPacker, reverse),
|
|
247
247
|
"left": (HPacker, obverse),
|
|
248
248
|
"bottom": (VPacker, reverse),
|
|
@@ -495,7 +495,7 @@ class GuideElementsColorbar(GuideElements):
|
|
|
495
495
|
)
|
|
496
496
|
|
|
497
497
|
@cached_property
|
|
498
|
-
def text_position(self) ->
|
|
498
|
+
def text_position(self) -> Side:
|
|
499
499
|
if not (position := self.theme.getp("legend_text_position")):
|
|
500
500
|
position = "right" if self.is_vertical else "bottom"
|
|
501
501
|
|
plotnine/guides/guide_legend.py
CHANGED
|
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|
|
25
25
|
|
|
26
26
|
from plotnine.geoms.geom import geom
|
|
27
27
|
from plotnine.layer import layer
|
|
28
|
-
from plotnine.typing import
|
|
28
|
+
from plotnine.typing import Side
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
# See guides.py for terminology
|
|
@@ -281,7 +281,7 @@ class guide_legend(guide):
|
|
|
281
281
|
targets.legend_key = drawings
|
|
282
282
|
|
|
283
283
|
# Match Drawings with labels to create the entries
|
|
284
|
-
lookup: dict[
|
|
284
|
+
lookup: dict[Side, tuple[type[PackerBase], slice]] = {
|
|
285
285
|
"right": (HPacker, reverse),
|
|
286
286
|
"left": (HPacker, obverse),
|
|
287
287
|
"bottom": (VPacker, reverse),
|
|
@@ -380,7 +380,7 @@ class GuideElementsLegend(GuideElements):
|
|
|
380
380
|
)
|
|
381
381
|
|
|
382
382
|
@cached_property
|
|
383
|
-
def text_position(self) ->
|
|
383
|
+
def text_position(self) -> Side:
|
|
384
384
|
if not (pos := self.theme.getp("legend_text_position")):
|
|
385
385
|
pos = "right"
|
|
386
386
|
return pos
|
plotnine/guides/guides.py
CHANGED
|
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|
|
35
35
|
NoGuide,
|
|
36
36
|
Orientation,
|
|
37
37
|
ScaledAestheticsName,
|
|
38
|
-
|
|
38
|
+
Side,
|
|
39
39
|
TextJustification,
|
|
40
40
|
)
|
|
41
41
|
|
|
@@ -104,7 +104,7 @@ class guides:
|
|
|
104
104
|
raise ValueError("Got a guide for color and colour, choose one.")
|
|
105
105
|
rename_aesthetics(self)
|
|
106
106
|
|
|
107
|
-
def __radd__(self,
|
|
107
|
+
def __radd__(self, other: ggplot):
|
|
108
108
|
"""
|
|
109
109
|
Add guides to the plot
|
|
110
110
|
|
|
@@ -120,9 +120,9 @@ class guides:
|
|
|
120
120
|
"""
|
|
121
121
|
for f in fields(self):
|
|
122
122
|
if (g := getattr(self, f.name)) is not None:
|
|
123
|
-
setattr(
|
|
123
|
+
setattr(other.guides, f.name, g)
|
|
124
124
|
|
|
125
|
-
return
|
|
125
|
+
return other
|
|
126
126
|
|
|
127
127
|
def _build(self) -> Sequence[guide]:
|
|
128
128
|
"""
|
|
@@ -326,7 +326,7 @@ class guides:
|
|
|
326
326
|
|
|
327
327
|
# Group together guides for each position
|
|
328
328
|
groups: dict[
|
|
329
|
-
tuple[
|
|
329
|
+
tuple[Side, float]
|
|
330
330
|
| tuple[tuple[float, float], tuple[float, float]],
|
|
331
331
|
list[PackerBase],
|
|
332
332
|
] = defaultdict(list)
|
|
@@ -369,7 +369,7 @@ class guides:
|
|
|
369
369
|
# place the guides according to the guide.order
|
|
370
370
|
default = max(g.order for g in gdefs) + 1
|
|
371
371
|
orders = [default if g.order == 0 else g.order for g in gdefs]
|
|
372
|
-
idx
|
|
372
|
+
idx = cast("Sequence[int]", np.argsort(orders))
|
|
373
373
|
gdefs = [gdefs[i] for i in idx]
|
|
374
374
|
|
|
375
375
|
# Draw each guide into a box
|
plotnine/helpers.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from typing import Sequence
|
|
8
|
+
|
|
9
|
+
from plotnine import ggplot
|
|
10
|
+
|
|
11
|
+
__all__ = ("get_aesthetic_limits",)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_aesthetic_limits(
|
|
15
|
+
plot: ggplot,
|
|
16
|
+
ae: str,
|
|
17
|
+
) -> (
|
|
18
|
+
tuple[float, float]
|
|
19
|
+
| Sequence[str]
|
|
20
|
+
| list[tuple[float]]
|
|
21
|
+
| list[Sequence[str]]
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Get the limits of an aesthetic
|
|
25
|
+
|
|
26
|
+
These are the limits before they are expanded.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
plot :
|
|
31
|
+
ggplot object
|
|
32
|
+
|
|
33
|
+
ae :
|
|
34
|
+
Name of aesthetic
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
out :
|
|
39
|
+
The limits of the aesthetic. If the plot is facetted, (has many
|
|
40
|
+
panels), it is a sequence of limits, one for each panel.
|
|
41
|
+
"""
|
|
42
|
+
plot = deepcopy(plot)
|
|
43
|
+
plot._build()
|
|
44
|
+
limits = [
|
|
45
|
+
getattr(panel, ae).limits
|
|
46
|
+
for panel in plot._build_objs.layout.panel_params
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
return limits[0] if len(limits) == 1 else limits
|
plotnine/iapi.py
CHANGED
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import itertools
|
|
11
11
|
from copy import copy
|
|
12
12
|
from dataclasses import dataclass, field, fields
|
|
13
|
+
from functools import cached_property
|
|
13
14
|
from typing import TYPE_CHECKING
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
@@ -19,6 +20,7 @@ if TYPE_CHECKING:
|
|
|
19
20
|
from matplotlib.figure import Figure
|
|
20
21
|
|
|
21
22
|
from plotnine.scales.scale import scale
|
|
23
|
+
from plotnine.themes.elements.margin import margin
|
|
22
24
|
from plotnine.typing import (
|
|
23
25
|
CoordRange,
|
|
24
26
|
FloatArrayLike,
|
|
@@ -231,13 +233,27 @@ class strip_draw_info:
|
|
|
231
233
|
Information required to draw strips
|
|
232
234
|
"""
|
|
233
235
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
bg_x: float
|
|
237
|
+
"""Left of the strip background in transAxes"""
|
|
238
|
+
|
|
239
|
+
bg_y: float
|
|
240
|
+
"""Bottom of the strip background in transAxes"""
|
|
241
|
+
|
|
236
242
|
ha: HorizontalJustification | float
|
|
243
|
+
"""Horizontal justification of strip text within the background"""
|
|
244
|
+
|
|
237
245
|
va: VerticalJustification | float
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
246
|
+
"""Vertical justification of strip text within the background"""
|
|
247
|
+
|
|
248
|
+
bg_width: float
|
|
249
|
+
"""Width of the strip background in transAxes"""
|
|
250
|
+
|
|
251
|
+
bg_height: float
|
|
252
|
+
"""Height of the strip background in transAxes"""
|
|
253
|
+
|
|
254
|
+
margin: margin
|
|
255
|
+
"""Strip text margin with the units in lines"""
|
|
256
|
+
|
|
241
257
|
strip_align: float
|
|
242
258
|
position: StripPosition
|
|
243
259
|
label: str
|
|
@@ -245,6 +261,13 @@ class strip_draw_info:
|
|
|
245
261
|
rotation: float
|
|
246
262
|
layout: layout_details
|
|
247
263
|
|
|
264
|
+
@cached_property
|
|
265
|
+
def is_oneline(self) -> bool:
|
|
266
|
+
"""
|
|
267
|
+
Whether the strip text is a single line
|
|
268
|
+
"""
|
|
269
|
+
return len(self.label.split("\n")) == 1
|
|
270
|
+
|
|
248
271
|
|
|
249
272
|
@dataclass
|
|
250
273
|
class strip_label_details:
|
plotnine/labels.py
CHANGED
|
@@ -103,12 +103,12 @@ class labs:
|
|
|
103
103
|
}
|
|
104
104
|
self.labels = labels_view(**rename_aesthetics(kwargs))
|
|
105
105
|
|
|
106
|
-
def __radd__(self,
|
|
106
|
+
def __radd__(self, other: p9.ggplot) -> p9.ggplot:
|
|
107
107
|
"""
|
|
108
108
|
Add labels to ggplot object
|
|
109
109
|
"""
|
|
110
|
-
|
|
111
|
-
return
|
|
110
|
+
other.labels.update(self.labels)
|
|
111
|
+
return other
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
class xlab(labs):
|