plotnine 0.15.0a5__py3-none-any.whl → 0.15.0a7__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/_engine.py +2 -2
- plotnine/_mpl/layout_manager/_layout_items.py +6 -2
- plotnine/_mpl/layout_manager/_layout_tree.py +221 -60
- plotnine/_mpl/layout_manager/_spaces.py +98 -28
- plotnine/_mpl/text.py +1 -7
- plotnine/_utils/__init__.py +13 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/{plot_composition/_compose.py → composition/_arrange.py} +64 -119
- plotnine/composition/_beside.py +54 -0
- plotnine/composition/_plot_spacer.py +55 -0
- plotnine/composition/_stack.py +54 -0
- 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_boxplot.py +4 -0
- plotnine/geoms/geom_col.py +2 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +1 -1
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_map.py +2 -2
- plotnine/geoms/geom_pointdensity.py +4 -0
- 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_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +4 -0
- plotnine/geoms/geom_violin.py +4 -0
- plotnine/ggplot.py +12 -26
- plotnine/scales/scale_manual.py +2 -0
- plotnine/stats/stat_bin.py +4 -0
- plotnine/stats/stat_bin_2d.py +4 -0
- plotnine/stats/stat_bindot.py +1 -0
- plotnine/stats/stat_boxplot.py +1 -1
- plotnine/stats/stat_count.py +1 -0
- plotnine/stats/stat_density.py +1 -1
- plotnine/stats/stat_density_2d.py +1 -0
- plotnine/stats/stat_ecdf.py +1 -1
- plotnine/stats/stat_ellipse.py +4 -0
- plotnine/stats/stat_function.py +4 -0
- plotnine/stats/stat_hull.py +4 -0
- plotnine/stats/stat_identity.py +4 -0
- plotnine/stats/stat_pointdensity.py +1 -0
- plotnine/stats/stat_qq.py +1 -0
- plotnine/stats/stat_qq_line.py +1 -0
- plotnine/stats/stat_quantile.py +1 -1
- plotnine/stats/stat_sina.py +1 -1
- plotnine/stats/stat_smooth.py +1 -0
- plotnine/stats/stat_sum.py +4 -0
- plotnine/stats/stat_summary.py +1 -1
- plotnine/stats/stat_summary_bin.py +1 -1
- plotnine/stats/stat_unique.py +4 -0
- plotnine/stats/stat_ydensity.py +1 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/theme_void.py +1 -7
- plotnine/themes/themeable.py +24 -13
- {plotnine-0.15.0a5.dist-info → plotnine-0.15.0a7.dist-info}/METADATA +1 -1
- {plotnine-0.15.0a5.dist-info → plotnine-0.15.0a7.dist-info}/RECORD +62 -60
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_spacer.py +0 -32
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0a5.dist-info → plotnine-0.15.0a7.dist-info}/WHEEL +0 -0
- {plotnine-0.15.0a5.dist-info → plotnine-0.15.0a7.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0a5.dist-info → plotnine-0.15.0a7.dist-info}/top_level.txt +0 -0
|
@@ -16,6 +16,7 @@ from dataclasses import dataclass, field, fields
|
|
|
16
16
|
from functools import cached_property
|
|
17
17
|
from typing import TYPE_CHECKING, cast
|
|
18
18
|
|
|
19
|
+
from plotnine.exceptions import PlotnineError
|
|
19
20
|
from plotnine.facets import facet_grid, facet_null, facet_wrap
|
|
20
21
|
|
|
21
22
|
from ._layout_items import LayoutItems
|
|
@@ -233,7 +234,7 @@ class _side_spaces(ABC):
|
|
|
233
234
|
"""
|
|
234
235
|
The width of the tag including the margins
|
|
235
236
|
|
|
236
|
-
The value is zero
|
|
237
|
+
The value is zero except if all these are true:
|
|
237
238
|
- The tag is in the margin `theme(plot_tag_position = "margin")`
|
|
238
239
|
- The tag at one one of the the following locations;
|
|
239
240
|
left, right, topleft, topright, bottomleft or bottomright
|
|
@@ -245,13 +246,41 @@ class _side_spaces(ABC):
|
|
|
245
246
|
"""
|
|
246
247
|
The height of the tag including the margins
|
|
247
248
|
|
|
248
|
-
The value is zero
|
|
249
|
+
The value is zero except if all these are true:
|
|
249
250
|
- The tag is in the margin `theme(plot_tag_position = "margin")`
|
|
250
251
|
- The tag at one one of the the following locations;
|
|
251
252
|
top, bottom, topleft, topright, bottomleft or bottomright
|
|
252
253
|
"""
|
|
253
254
|
return 0
|
|
254
255
|
|
|
256
|
+
@property
|
|
257
|
+
def axis_title_clearance(self) -> float:
|
|
258
|
+
"""
|
|
259
|
+
The distance between the axis title and the panel
|
|
260
|
+
|
|
261
|
+
Figure
|
|
262
|
+
----------------------------
|
|
263
|
+
| Panel |
|
|
264
|
+
| ----------- |
|
|
265
|
+
| | | |
|
|
266
|
+
| | | |
|
|
267
|
+
| Y<--->| | |
|
|
268
|
+
| | | |
|
|
269
|
+
| | | |
|
|
270
|
+
| ----------- |
|
|
271
|
+
| |
|
|
272
|
+
----------------------------
|
|
273
|
+
|
|
274
|
+
We use this value to when aligning axis titles in a
|
|
275
|
+
plot composition.
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
return self.total - self.sum_upto("axis_title_alignment")
|
|
280
|
+
except AttributeError as err:
|
|
281
|
+
# There is probably an error in in the layout manager
|
|
282
|
+
raise PlotnineError("Side has no axis title") from err
|
|
283
|
+
|
|
255
284
|
|
|
256
285
|
@dataclass
|
|
257
286
|
class left_spaces(_side_spaces):
|
|
@@ -311,6 +340,14 @@ class left_spaces(_side_spaces):
|
|
|
311
340
|
axis_title_y_margin_left: float = 0
|
|
312
341
|
axis_title_y: float = 0
|
|
313
342
|
axis_title_y_margin_right: float = 0
|
|
343
|
+
axis_title_alignment: float = 0
|
|
344
|
+
"""
|
|
345
|
+
Space added to align the axis title with others in a composition
|
|
346
|
+
|
|
347
|
+
This value is calculated during the layout process. The amount is
|
|
348
|
+
the difference between the largest and smallest axis_title_clearance
|
|
349
|
+
among the items in the composition.
|
|
350
|
+
"""
|
|
314
351
|
axis_text_y_margin_left: float = 0
|
|
315
352
|
axis_text_y: float = 0
|
|
316
353
|
axis_text_y_margin_right: float = 0
|
|
@@ -385,18 +422,18 @@ class left_spaces(_side_spaces):
|
|
|
385
422
|
return self.to_figure_space(self.sum_incl(item))
|
|
386
423
|
|
|
387
424
|
@property
|
|
388
|
-
def
|
|
425
|
+
def panel_left_relative(self):
|
|
389
426
|
"""
|
|
390
427
|
Left (relative to the gridspec) of the panels in figure dimensions
|
|
391
428
|
"""
|
|
392
429
|
return self.total
|
|
393
430
|
|
|
394
431
|
@property
|
|
395
|
-
def
|
|
432
|
+
def panel_left(self):
|
|
396
433
|
"""
|
|
397
434
|
Left of the panels in figure space
|
|
398
435
|
"""
|
|
399
|
-
return self.to_figure_space(self.
|
|
436
|
+
return self.to_figure_space(self.panel_left_relative)
|
|
400
437
|
|
|
401
438
|
@property
|
|
402
439
|
def plot_left(self):
|
|
@@ -491,18 +528,18 @@ class right_spaces(_side_spaces):
|
|
|
491
528
|
return self.to_figure_space(1 - self.sum_upto(item))
|
|
492
529
|
|
|
493
530
|
@property
|
|
494
|
-
def
|
|
531
|
+
def panel_right_relative(self):
|
|
495
532
|
"""
|
|
496
533
|
Right (relative to the gridspec) of the panels in figure dimensions
|
|
497
534
|
"""
|
|
498
535
|
return 1 - self.total
|
|
499
536
|
|
|
500
537
|
@property
|
|
501
|
-
def
|
|
538
|
+
def panel_right(self):
|
|
502
539
|
"""
|
|
503
540
|
Right of the panels in figure space
|
|
504
541
|
"""
|
|
505
|
-
return self.to_figure_space(self.
|
|
542
|
+
return self.to_figure_space(self.panel_right_relative)
|
|
506
543
|
|
|
507
544
|
@property
|
|
508
545
|
def plot_right(self):
|
|
@@ -620,18 +657,18 @@ class top_spaces(_side_spaces):
|
|
|
620
657
|
return self.to_figure_space(1 - self.sum_upto(item))
|
|
621
658
|
|
|
622
659
|
@property
|
|
623
|
-
def
|
|
660
|
+
def panel_top_relative(self):
|
|
624
661
|
"""
|
|
625
662
|
Top (relative to the gridspec) of the panels in figure dimensions
|
|
626
663
|
"""
|
|
627
664
|
return 1 - self.total
|
|
628
665
|
|
|
629
666
|
@property
|
|
630
|
-
def
|
|
667
|
+
def panel_top(self):
|
|
631
668
|
"""
|
|
632
669
|
Top of the panels in figure space
|
|
633
670
|
"""
|
|
634
|
-
return self.to_figure_space(self.
|
|
671
|
+
return self.to_figure_space(self.panel_top_relative)
|
|
635
672
|
|
|
636
673
|
@property
|
|
637
674
|
def plot_top(self):
|
|
@@ -674,6 +711,15 @@ class bottom_spaces(_side_spaces):
|
|
|
674
711
|
axis_title_x_margin_bottom: float = 0
|
|
675
712
|
axis_title_x: float = 0
|
|
676
713
|
axis_title_x_margin_top: float = 0
|
|
714
|
+
axis_title_alignment: float = 0
|
|
715
|
+
"""
|
|
716
|
+
Space added to align the axis title with others in a composition
|
|
717
|
+
|
|
718
|
+
This value is calculated during the layout process in a tree structure
|
|
719
|
+
that has convenient access to the sides/edges of the panels in the
|
|
720
|
+
composition. It's amount is the difference in height between this axis
|
|
721
|
+
text (and it's margins) and the tallest axis text (and it's margin).
|
|
722
|
+
"""
|
|
677
723
|
axis_text_x_margin_bottom: float = 0
|
|
678
724
|
axis_text_x: float = 0
|
|
679
725
|
axis_text_x_margin_top: float = 0
|
|
@@ -758,18 +804,18 @@ class bottom_spaces(_side_spaces):
|
|
|
758
804
|
return self.to_figure_space(self.sum_incl(item))
|
|
759
805
|
|
|
760
806
|
@property
|
|
761
|
-
def
|
|
807
|
+
def panel_bottom_relative(self):
|
|
762
808
|
"""
|
|
763
809
|
Bottom (relative to the gridspec) of the panels in figure dimensions
|
|
764
810
|
"""
|
|
765
811
|
return self.total
|
|
766
812
|
|
|
767
813
|
@property
|
|
768
|
-
def
|
|
814
|
+
def panel_bottom(self):
|
|
769
815
|
"""
|
|
770
816
|
Bottom of the panels in figure space
|
|
771
817
|
"""
|
|
772
|
-
return self.to_figure_space(self.
|
|
818
|
+
return self.to_figure_space(self.panel_bottom_relative)
|
|
773
819
|
|
|
774
820
|
@property
|
|
775
821
|
def plot_bottom(self):
|
|
@@ -892,14 +938,14 @@ class LayoutSpaces:
|
|
|
892
938
|
"""
|
|
893
939
|
Width [figure dimensions] of panels
|
|
894
940
|
"""
|
|
895
|
-
return self.r.
|
|
941
|
+
return self.r.panel_right - self.l.panel_left
|
|
896
942
|
|
|
897
943
|
@property
|
|
898
944
|
def panel_height(self) -> float:
|
|
899
945
|
"""
|
|
900
946
|
Height [figure dimensions] of panels
|
|
901
947
|
"""
|
|
902
|
-
return self.t.
|
|
948
|
+
return self.t.panel_top - self.b.panel_bottom
|
|
903
949
|
|
|
904
950
|
@property
|
|
905
951
|
def tag_width(self) -> float:
|
|
@@ -945,6 +991,24 @@ class LayoutSpaces:
|
|
|
945
991
|
"""
|
|
946
992
|
return self.b.tag_height
|
|
947
993
|
|
|
994
|
+
@property
|
|
995
|
+
def left_axis_title_clearance(self) -> float:
|
|
996
|
+
"""
|
|
997
|
+
Distance between the left y-axis title and the panel
|
|
998
|
+
|
|
999
|
+
In figure dimensions.
|
|
1000
|
+
"""
|
|
1001
|
+
return self.l.axis_title_clearance
|
|
1002
|
+
|
|
1003
|
+
@property
|
|
1004
|
+
def bottom_axis_title_clearance(self) -> float:
|
|
1005
|
+
"""
|
|
1006
|
+
Distance between the bottom x-axis title and the panel
|
|
1007
|
+
|
|
1008
|
+
In figure dimensions.
|
|
1009
|
+
"""
|
|
1010
|
+
return self.b.axis_title_clearance
|
|
1011
|
+
|
|
948
1012
|
def increase_horizontal_plot_margin(self, dw: float):
|
|
949
1013
|
"""
|
|
950
1014
|
Increase the plot_margin to the right & left of the panels
|
|
@@ -981,8 +1045,8 @@ class LayoutSpaces:
|
|
|
981
1045
|
|
|
982
1046
|
This is the area in which the panels are drawn.
|
|
983
1047
|
"""
|
|
984
|
-
x1, x2 = self.l.
|
|
985
|
-
y1, y2 = self.b.
|
|
1048
|
+
x1, x2 = self.l.panel_left, self.r.panel_right
|
|
1049
|
+
y1, y2 = self.b.panel_bottom, self.t.panel_top
|
|
986
1050
|
return ((x1, y1), (x2, y2))
|
|
987
1051
|
|
|
988
1052
|
def _calculate_panel_spacing(self) -> GridSpecParams:
|
|
@@ -1003,10 +1067,10 @@ class LayoutSpaces:
|
|
|
1003
1067
|
raise TypeError(f"Unknown type of facet: {type(self.plot.facet)}")
|
|
1004
1068
|
|
|
1005
1069
|
return GridSpecParams(
|
|
1006
|
-
self.l.
|
|
1007
|
-
self.r.
|
|
1008
|
-
self.t.
|
|
1009
|
-
self.b.
|
|
1070
|
+
self.l.panel_left_relative,
|
|
1071
|
+
self.r.panel_right_relative,
|
|
1072
|
+
self.t.panel_top_relative,
|
|
1073
|
+
self.b.panel_bottom_relative,
|
|
1010
1074
|
wspace,
|
|
1011
1075
|
hspace,
|
|
1012
1076
|
)
|
|
@@ -1020,6 +1084,9 @@ class LayoutSpaces:
|
|
|
1020
1084
|
ncol = self.plot.facet.ncol
|
|
1021
1085
|
nrow = self.plot.facet.nrow
|
|
1022
1086
|
|
|
1087
|
+
left, right = self.l.panel_left, self.r.panel_right
|
|
1088
|
+
top, bottom = self.t.panel_top, self.b.panel_bottom
|
|
1089
|
+
|
|
1023
1090
|
# Both spacings are specified as fractions of the figure width
|
|
1024
1091
|
# Multiply the vertical by (W/H) so that the gullies along both
|
|
1025
1092
|
# directions are equally spaced.
|
|
@@ -1027,8 +1094,8 @@ class LayoutSpaces:
|
|
|
1027
1094
|
self.sh = theme.getp("panel_spacing_y") * self.W / self.H
|
|
1028
1095
|
|
|
1029
1096
|
# width and height of axes as fraction of figure width & height
|
|
1030
|
-
self.w = ((
|
|
1031
|
-
self.h = ((
|
|
1097
|
+
self.w = ((right - left) - self.sw * (ncol - 1)) / ncol
|
|
1098
|
+
self.h = ((top - bottom) - self.sh * (nrow - 1)) / nrow
|
|
1032
1099
|
|
|
1033
1100
|
# Spacing as fraction of axes width & height
|
|
1034
1101
|
wspace = self.sw / self.w
|
|
@@ -1045,6 +1112,9 @@ class LayoutSpaces:
|
|
|
1045
1112
|
ncol = facet.ncol
|
|
1046
1113
|
nrow = facet.nrow
|
|
1047
1114
|
|
|
1115
|
+
left, right = self.l.panel_left, self.r.panel_right
|
|
1116
|
+
top, bottom = self.t.panel_top, self.b.panel_bottom
|
|
1117
|
+
|
|
1048
1118
|
# Both spacings are specified as fractions of the figure width
|
|
1049
1119
|
self.sw = theme.getp("panel_spacing_x")
|
|
1050
1120
|
self.sh = theme.getp("panel_spacing_y") * self.W / self.H
|
|
@@ -1073,8 +1143,8 @@ class LayoutSpaces:
|
|
|
1073
1143
|
) + self.items.axis_ticks_y_max_width_at("all")
|
|
1074
1144
|
|
|
1075
1145
|
# width and height of axes as fraction of figure width & height
|
|
1076
|
-
self.w = ((
|
|
1077
|
-
self.h = ((
|
|
1146
|
+
self.w = ((right - left) - self.sw * (ncol - 1)) / ncol
|
|
1147
|
+
self.h = ((top - bottom) - self.sh * (nrow - 1)) / nrow
|
|
1078
1148
|
|
|
1079
1149
|
# Spacing as fraction of axes width & height
|
|
1080
1150
|
wspace = self.sw / self.w
|
|
@@ -1085,8 +1155,8 @@ class LayoutSpaces:
|
|
|
1085
1155
|
"""
|
|
1086
1156
|
Calculate spacing parts for facet_null
|
|
1087
1157
|
"""
|
|
1088
|
-
self.w = self.r.
|
|
1089
|
-
self.h = self.t.
|
|
1158
|
+
self.w = self.r.panel_right - self.l.panel_left
|
|
1159
|
+
self.h = self.t.panel_top - self.b.panel_bottom
|
|
1090
1160
|
self.sw = 0
|
|
1091
1161
|
self.sh = 0
|
|
1092
1162
|
return 0, 0
|
plotnine/_mpl/text.py
CHANGED
|
@@ -32,7 +32,7 @@ class StripText(Text):
|
|
|
32
32
|
"clip_on": False,
|
|
33
33
|
"zorder": 3.3,
|
|
34
34
|
# Since the text can be rotated, it is simpler to anchor it at
|
|
35
|
-
# the center, align it then do the rotation. Vertically,
|
|
35
|
+
# the center, align it, then do the rotation. Vertically,
|
|
36
36
|
# center_baseline places the text in the visual center, but
|
|
37
37
|
# only if it is one line. For multiline text, we are better
|
|
38
38
|
# off with plain center.
|
|
@@ -45,12 +45,6 @@ class StripText(Text):
|
|
|
45
45
|
self.draw_info = info
|
|
46
46
|
self.patch = StripTextPatch(self)
|
|
47
47
|
|
|
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
48
|
# TODO: This should really be part of the unit conversions in the
|
|
55
49
|
# margin class.
|
|
56
50
|
@lru_cache(2)
|
plotnine/_utils/__init__.py
CHANGED
|
@@ -1194,3 +1194,16 @@ def va_as_float(va: VerticalJustification | float) -> float:
|
|
|
1194
1194
|
"center_baseline": 0.5,
|
|
1195
1195
|
}
|
|
1196
1196
|
return lookup[va] if isinstance(va, str) else va
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def has_alpha_channel(c: str | tuple) -> bool:
|
|
1200
|
+
"""
|
|
1201
|
+
Return True if c a color with an alpha value
|
|
1202
|
+
|
|
1203
|
+
Either a 9 character hex string e.g. #AABBCC88 or
|
|
1204
|
+
an RGBA tuple e.g. (.6, .7, .8, .5)
|
|
1205
|
+
"""
|
|
1206
|
+
if isinstance(c, str):
|
|
1207
|
+
return c.startswith("#") and len(c) == 9
|
|
1208
|
+
else:
|
|
1209
|
+
return color_utils.is_color_tuple(c) and len(c) == 4
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
4
|
from copy import deepcopy
|
|
5
|
-
from dataclasses import dataclass
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
6
|
from io import BytesIO
|
|
7
7
|
from typing import TYPE_CHECKING
|
|
8
8
|
|
|
@@ -24,37 +24,57 @@ if TYPE_CHECKING:
|
|
|
24
24
|
from plotnine.ggplot import PlotAddable, ggplot
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
@dataclass
|
|
28
|
+
class Arrange:
|
|
28
29
|
"""
|
|
29
|
-
|
|
30
|
+
Base class for those that create plot compositions
|
|
31
|
+
|
|
32
|
+
As a user, you will never directly work with this class, except
|
|
33
|
+
the operators [`|`](`plotnine.composition.Beside`) and
|
|
34
|
+
[`/`](`plotnine.composition.Stack`) that are powered by subclasses
|
|
35
|
+
of this class.
|
|
30
36
|
|
|
31
37
|
Parameters
|
|
32
38
|
----------
|
|
33
39
|
operands:
|
|
34
40
|
The objects to be put together (composed).
|
|
41
|
+
|
|
42
|
+
See Also
|
|
43
|
+
--------
|
|
44
|
+
plotnine.composition.Beside : To arrange plots side by side
|
|
45
|
+
plotnine.composition.Stack : To arrange plots vertically
|
|
46
|
+
plotnine.composition.plot_spacer : To add a blank space between plots
|
|
35
47
|
"""
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
self.operands = operands
|
|
49
|
+
operands: list[ggplot | Arrange]
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
# These are created in the _create_figure method
|
|
52
|
+
figure: Figure = field(init=False, repr=False)
|
|
53
|
+
plotspecs: list[plotspec] = field(init=False, repr=False)
|
|
54
|
+
gridspec: p9GridSpec = field(init=False, repr=False)
|
|
55
|
+
|
|
56
|
+
def __post_init__(self):
|
|
57
|
+
# The way we handle the plots has consequences that would
|
|
58
|
+
# prevent having a duplicate plot in the composition.
|
|
59
|
+
# Using copies prevents this.
|
|
60
|
+
self.operands = [
|
|
61
|
+
op if isinstance(op, Arrange) else deepcopy(op)
|
|
62
|
+
for op in self.operands
|
|
63
|
+
]
|
|
44
64
|
|
|
45
65
|
@abc.abstractmethod
|
|
46
|
-
def __or__(self, rhs: ggplot |
|
|
66
|
+
def __or__(self, rhs: ggplot | Arrange) -> Arrange:
|
|
47
67
|
"""
|
|
48
68
|
Add rhs as a column
|
|
49
69
|
"""
|
|
50
70
|
|
|
51
71
|
@abc.abstractmethod
|
|
52
|
-
def __truediv__(self, rhs: ggplot |
|
|
72
|
+
def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
|
|
53
73
|
"""
|
|
54
74
|
Add rhs as a row
|
|
55
75
|
"""
|
|
56
76
|
|
|
57
|
-
def __add__(self, rhs: ggplot |
|
|
77
|
+
def __add__(self, rhs: ggplot | Arrange | PlotAddable) -> Arrange:
|
|
58
78
|
"""
|
|
59
79
|
Add rhs to the composition
|
|
60
80
|
|
|
@@ -65,15 +85,15 @@ class Compose:
|
|
|
65
85
|
"""
|
|
66
86
|
from plotnine import ggplot
|
|
67
87
|
|
|
68
|
-
if not isinstance(rhs, (ggplot,
|
|
88
|
+
if not isinstance(rhs, (ggplot, Arrange)):
|
|
69
89
|
cmp = deepcopy(self)
|
|
70
90
|
cmp.last_plot = cmp.last_plot + rhs
|
|
71
91
|
return cmp
|
|
72
92
|
return self.__class__([*self, rhs])
|
|
73
93
|
|
|
74
|
-
def __sub__(self, rhs: ggplot |
|
|
94
|
+
def __sub__(self, rhs: ggplot | Arrange) -> Arrange:
|
|
75
95
|
"""
|
|
76
|
-
Add the rhs
|
|
96
|
+
Add the rhs onto the composition
|
|
77
97
|
|
|
78
98
|
Parameters
|
|
79
99
|
----------
|
|
@@ -82,7 +102,7 @@ class Compose:
|
|
|
82
102
|
"""
|
|
83
103
|
return self.__class__([self, rhs])
|
|
84
104
|
|
|
85
|
-
def __and__(self, rhs: PlotAddable) ->
|
|
105
|
+
def __and__(self, rhs: PlotAddable) -> Arrange:
|
|
86
106
|
"""
|
|
87
107
|
Add rhs to all plots in the composition
|
|
88
108
|
|
|
@@ -93,9 +113,9 @@ class Compose:
|
|
|
93
113
|
"""
|
|
94
114
|
self = deepcopy(self)
|
|
95
115
|
|
|
96
|
-
def add_other(op:
|
|
116
|
+
def add_other(op: Arrange):
|
|
97
117
|
for item in op:
|
|
98
|
-
if isinstance(item,
|
|
118
|
+
if isinstance(item, Arrange):
|
|
99
119
|
add_other(item)
|
|
100
120
|
else:
|
|
101
121
|
item += rhs
|
|
@@ -103,7 +123,7 @@ class Compose:
|
|
|
103
123
|
add_other(self)
|
|
104
124
|
return self
|
|
105
125
|
|
|
106
|
-
def __mul__(self, rhs: PlotAddable) ->
|
|
126
|
+
def __mul__(self, rhs: PlotAddable) -> Arrange:
|
|
107
127
|
"""
|
|
108
128
|
Add rhs to the outermost nesting level of the composition
|
|
109
129
|
|
|
@@ -127,7 +147,7 @@ class Compose:
|
|
|
127
147
|
"""
|
|
128
148
|
return len(self.operands)
|
|
129
149
|
|
|
130
|
-
def __iter__(self) -> Iterator[ggplot |
|
|
150
|
+
def __iter__(self) -> Iterator[ggplot | Arrange]:
|
|
131
151
|
"""
|
|
132
152
|
Return an iterable of all the operands
|
|
133
153
|
"""
|
|
@@ -227,7 +247,7 @@ class Compose:
|
|
|
227
247
|
from plotnine._mpl.gridspec import p9GridSpec
|
|
228
248
|
|
|
229
249
|
def _make_plotspecs(
|
|
230
|
-
cmp:
|
|
250
|
+
cmp: Arrange, parent_gridspec: p9GridSpec | None
|
|
231
251
|
) -> Generator[plotspec]:
|
|
232
252
|
"""
|
|
233
253
|
Return the plot specification for each subplot in the composition
|
|
@@ -289,7 +309,7 @@ class Compose:
|
|
|
289
309
|
|
|
290
310
|
def draw(self, *, show: bool = False) -> Figure:
|
|
291
311
|
"""
|
|
292
|
-
Render the
|
|
312
|
+
Render the arranged plots
|
|
293
313
|
|
|
294
314
|
Parameters
|
|
295
315
|
----------
|
|
@@ -315,9 +335,15 @@ class Compose:
|
|
|
315
335
|
)
|
|
316
336
|
return figure
|
|
317
337
|
|
|
318
|
-
def save(
|
|
338
|
+
def save(
|
|
339
|
+
self,
|
|
340
|
+
filename: str | Path | BytesIO,
|
|
341
|
+
format: str | None = None,
|
|
342
|
+
dpi: int | None = None,
|
|
343
|
+
**kwargs,
|
|
344
|
+
):
|
|
319
345
|
"""
|
|
320
|
-
Save a
|
|
346
|
+
Save a composition as an image file
|
|
321
347
|
|
|
322
348
|
Parameters
|
|
323
349
|
----------
|
|
@@ -326,14 +352,25 @@ class Compose:
|
|
|
326
352
|
format :
|
|
327
353
|
Image format to use, automatically extract from
|
|
328
354
|
file name extension.
|
|
329
|
-
|
|
330
|
-
|
|
355
|
+
dpi :
|
|
356
|
+
DPI to use for raster graphics. If None, defaults to using
|
|
357
|
+
the `dpi` of theme to the first plot.
|
|
358
|
+
kwargs :
|
|
359
|
+
These are ignored. Here to "softly" match the API of
|
|
360
|
+
`ggplot.save()`.
|
|
361
|
+
"""
|
|
362
|
+
from plotnine import theme
|
|
363
|
+
|
|
364
|
+
# To set the dpi, we only need to change the dpi of
|
|
365
|
+
# the last plot and theme gets added to the last plot
|
|
366
|
+
plot = (self + theme(dpi=dpi)) if dpi else self
|
|
367
|
+
figure = plot.draw()
|
|
331
368
|
figure.savefig(filename, format=format)
|
|
332
369
|
|
|
333
370
|
|
|
334
371
|
@dataclass
|
|
335
372
|
class plot_composition_context:
|
|
336
|
-
cmp:
|
|
373
|
+
cmp: Arrange
|
|
337
374
|
show: bool
|
|
338
375
|
|
|
339
376
|
def __post_init__(self):
|
|
@@ -368,95 +405,3 @@ class plot_composition_context:
|
|
|
368
405
|
plt.close(self.cmp.figure)
|
|
369
406
|
|
|
370
407
|
self._rc_context.__exit__(exc_type, exc_value, exc_traceback)
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
class OR(Compose):
|
|
374
|
-
"""
|
|
375
|
-
Compose by adding a column
|
|
376
|
-
"""
|
|
377
|
-
|
|
378
|
-
@property
|
|
379
|
-
def nrow(self) -> int:
|
|
380
|
-
return 1
|
|
381
|
-
|
|
382
|
-
@property
|
|
383
|
-
def ncol(self) -> int:
|
|
384
|
-
return len(self)
|
|
385
|
-
|
|
386
|
-
def __or__(self, rhs: ggplot | Compose) -> Compose:
|
|
387
|
-
"""
|
|
388
|
-
Add rhs as a column
|
|
389
|
-
"""
|
|
390
|
-
# This is adjacent or i.e. (OR | rhs) so we collapse the
|
|
391
|
-
# operands into a single operation
|
|
392
|
-
return OR([*self, rhs])
|
|
393
|
-
|
|
394
|
-
def __truediv__(self, rhs: ggplot | Compose) -> Compose:
|
|
395
|
-
"""
|
|
396
|
-
Add rhs as a row
|
|
397
|
-
"""
|
|
398
|
-
return DIV([self, rhs])
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
class DIV(Compose):
|
|
402
|
-
"""
|
|
403
|
-
Compose by adding a row
|
|
404
|
-
"""
|
|
405
|
-
|
|
406
|
-
@property
|
|
407
|
-
def nrow(self) -> int:
|
|
408
|
-
return len(self)
|
|
409
|
-
|
|
410
|
-
@property
|
|
411
|
-
def ncol(self) -> int:
|
|
412
|
-
return 1
|
|
413
|
-
|
|
414
|
-
def __truediv__(self, rhs: ggplot | Compose) -> Compose:
|
|
415
|
-
"""
|
|
416
|
-
Add rhs as a row
|
|
417
|
-
"""
|
|
418
|
-
# This is an adjacent div i.e. (DIV | rhs) so we collapse the
|
|
419
|
-
# operands into a single operation
|
|
420
|
-
return DIV([*self, rhs])
|
|
421
|
-
|
|
422
|
-
def __or__(self, rhs: ggplot | Compose) -> Compose:
|
|
423
|
-
"""
|
|
424
|
-
Add rhs as a column
|
|
425
|
-
"""
|
|
426
|
-
return OR([self, rhs])
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
class ADD(Compose):
|
|
430
|
-
"""
|
|
431
|
-
Compose by adding
|
|
432
|
-
"""
|
|
433
|
-
|
|
434
|
-
@property
|
|
435
|
-
def nrow(self) -> int:
|
|
436
|
-
from plotnine.facets.facet_wrap import wrap_dims
|
|
437
|
-
|
|
438
|
-
return wrap_dims(len(self))[0]
|
|
439
|
-
|
|
440
|
-
@property
|
|
441
|
-
def ncol(self) -> int:
|
|
442
|
-
from plotnine.facets.facet_wrap import wrap_dims
|
|
443
|
-
|
|
444
|
-
return wrap_dims(len(self))[1]
|
|
445
|
-
|
|
446
|
-
def __or__(self, rhs: ggplot | Compose) -> Compose:
|
|
447
|
-
"""
|
|
448
|
-
Add rhs as a column
|
|
449
|
-
"""
|
|
450
|
-
return OR([self, rhs])
|
|
451
|
-
|
|
452
|
-
def __truediv__(self, rhs: ggplot | Compose) -> Compose:
|
|
453
|
-
"""
|
|
454
|
-
Add rhs as a row
|
|
455
|
-
"""
|
|
456
|
-
return DIV([self, rhs])
|
|
457
|
-
|
|
458
|
-
def __sub__(self, rhs: ggplot | Compose) -> Compose:
|
|
459
|
-
"""
|
|
460
|
-
Add rhs as a column
|
|
461
|
-
"""
|
|
462
|
-
return OR([self, rhs])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from ._arrange import Arrange
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from plotnine.ggplot import ggplot
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Beside(Arrange):
|
|
14
|
+
"""
|
|
15
|
+
Place plots or compositions side by side
|
|
16
|
+
|
|
17
|
+
**Usage**
|
|
18
|
+
|
|
19
|
+
plot | plot
|
|
20
|
+
plot | composition
|
|
21
|
+
composition | plot
|
|
22
|
+
composition | composition
|
|
23
|
+
|
|
24
|
+
Typically, you will use this class through the `|` operator.
|
|
25
|
+
|
|
26
|
+
See Also
|
|
27
|
+
--------
|
|
28
|
+
plotnine.composition.Stack : To arrange plots vertically
|
|
29
|
+
plotnine.composition.plot_spacer : To add a blank space between plots
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def nrow(self) -> int:
|
|
34
|
+
return 1
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def ncol(self) -> int:
|
|
38
|
+
return len(self)
|
|
39
|
+
|
|
40
|
+
def __or__(self, rhs: ggplot | Arrange) -> Arrange:
|
|
41
|
+
"""
|
|
42
|
+
Add rhs as a column
|
|
43
|
+
"""
|
|
44
|
+
# This is adjacent or i.e. (OR | rhs) so we collapse the
|
|
45
|
+
# operands into a single operation
|
|
46
|
+
return Beside([*self, rhs])
|
|
47
|
+
|
|
48
|
+
def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
|
|
49
|
+
"""
|
|
50
|
+
Add rhs as a row
|
|
51
|
+
"""
|
|
52
|
+
from ._stack import Stack
|
|
53
|
+
|
|
54
|
+
return Stack([self, rhs])
|