plotnine 0.15.0a6__py3-none-any.whl → 0.15.0a8__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.
Files changed (55) hide show
  1. plotnine/_mpl/layout_manager/_engine.py +2 -2
  2. plotnine/_mpl/layout_manager/_layout_items.py +6 -2
  3. plotnine/_mpl/layout_manager/_layout_tree.py +219 -58
  4. plotnine/_mpl/layout_manager/_spaces.py +98 -28
  5. plotnine/_mpl/text.py +1 -7
  6. plotnine/composition/__init__.py +2 -2
  7. plotnine/composition/_beside.py +12 -4
  8. plotnine/composition/{_arrange.py → _compose.py} +118 -38
  9. plotnine/composition/_plot_spacer.py +6 -0
  10. plotnine/composition/_stack.py +12 -4
  11. plotnine/geoms/geom_bar.py +1 -0
  12. plotnine/geoms/geom_bin_2d.py +4 -0
  13. plotnine/geoms/geom_boxplot.py +4 -0
  14. plotnine/geoms/geom_count.py +4 -0
  15. plotnine/geoms/geom_density_2d.py +4 -0
  16. plotnine/geoms/geom_dotplot.py +1 -1
  17. plotnine/geoms/geom_histogram.py +1 -1
  18. plotnine/geoms/geom_pointdensity.py +4 -0
  19. plotnine/geoms/geom_qq.py +4 -0
  20. plotnine/geoms/geom_qq_line.py +4 -0
  21. plotnine/geoms/geom_quantile.py +4 -0
  22. plotnine/geoms/geom_sina.py +1 -1
  23. plotnine/geoms/geom_smooth.py +4 -0
  24. plotnine/geoms/geom_violin.py +4 -0
  25. plotnine/ggplot.py +4 -4
  26. plotnine/stats/stat_bin.py +4 -0
  27. plotnine/stats/stat_bin_2d.py +4 -0
  28. plotnine/stats/stat_bindot.py +1 -0
  29. plotnine/stats/stat_boxplot.py +1 -1
  30. plotnine/stats/stat_count.py +1 -0
  31. plotnine/stats/stat_density.py +1 -1
  32. plotnine/stats/stat_density_2d.py +1 -0
  33. plotnine/stats/stat_ecdf.py +1 -1
  34. plotnine/stats/stat_ellipse.py +4 -0
  35. plotnine/stats/stat_function.py +4 -0
  36. plotnine/stats/stat_hull.py +4 -0
  37. plotnine/stats/stat_identity.py +4 -0
  38. plotnine/stats/stat_pointdensity.py +1 -0
  39. plotnine/stats/stat_qq.py +1 -0
  40. plotnine/stats/stat_qq_line.py +1 -0
  41. plotnine/stats/stat_quantile.py +1 -1
  42. plotnine/stats/stat_sina.py +1 -1
  43. plotnine/stats/stat_smooth.py +1 -0
  44. plotnine/stats/stat_sum.py +4 -0
  45. plotnine/stats/stat_summary.py +1 -1
  46. plotnine/stats/stat_summary_bin.py +1 -1
  47. plotnine/stats/stat_unique.py +4 -0
  48. plotnine/stats/stat_ydensity.py +1 -1
  49. plotnine/themes/theme.py +1 -1
  50. plotnine/themes/theme_void.py +1 -7
  51. {plotnine-0.15.0a6.dist-info → plotnine-0.15.0a8.dist-info}/METADATA +1 -1
  52. {plotnine-0.15.0a6.dist-info → plotnine-0.15.0a8.dist-info}/RECORD +55 -55
  53. {plotnine-0.15.0a6.dist-info → plotnine-0.15.0a8.dist-info}/WHEEL +0 -0
  54. {plotnine-0.15.0a6.dist-info → plotnine-0.15.0a8.dist-info}/licenses/LICENSE +0 -0
  55. {plotnine-0.15.0a6.dist-info → plotnine-0.15.0a8.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 expect if all these are true:
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 expect if all these are true:
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 left_relative(self):
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 left(self):
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.left_relative)
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 right_relative(self):
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 right(self):
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.right_relative)
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 top_relative(self):
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 top(self):
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.top_relative)
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 bottom_relative(self):
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 bottom(self):
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.bottom_relative)
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.right - self.l.left
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.top - self.b.bottom
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.left, self.r.right
985
- y1, y2 = self.b.bottom, self.t.top
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.left_relative,
1007
- self.r.right_relative,
1008
- self.t.top_relative,
1009
- self.b.bottom_relative,
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 = ((self.r.right - self.l.left) - self.sw * (ncol - 1)) / ncol
1031
- self.h = ((self.t.top - self.b.bottom) - self.sh * (nrow - 1)) / nrow
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 = ((self.r.right - self.l.left) - self.sw * (ncol - 1)) / ncol
1077
- self.h = ((self.t.top - self.b.bottom) - self.sh * (nrow - 1)) / nrow
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.right - self.l.left
1089
- self.h = self.t.top - self.b.bottom
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)
@@ -1,10 +1,10 @@
1
- from ._arrange import Arrange
2
1
  from ._beside import Beside
2
+ from ._compose import Compose
3
3
  from ._plot_spacer import plot_spacer
4
4
  from ._stack import Stack
5
5
 
6
6
  __all__ = (
7
- "Arrange",
7
+ "Compose",
8
8
  "Stack",
9
9
  "Beside",
10
10
  "plot_spacer",
@@ -1,14 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from dataclasses import dataclass
3
4
  from typing import TYPE_CHECKING
4
5
 
5
- from ._arrange import Arrange
6
+ from ._compose import Compose
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from plotnine.ggplot import ggplot
9
10
 
10
11
 
11
- class Beside(Arrange):
12
+ @dataclass
13
+ class Beside(Compose):
12
14
  """
13
15
  Place plots or compositions side by side
14
16
 
@@ -20,6 +22,12 @@ class Beside(Arrange):
20
22
  composition | composition
21
23
 
22
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
+ plotnine.composition.Compose : For more on composing plots
23
31
  """
24
32
 
25
33
  @property
@@ -30,7 +38,7 @@ class Beside(Arrange):
30
38
  def ncol(self) -> int:
31
39
  return len(self)
32
40
 
33
- def __or__(self, rhs: ggplot | Arrange) -> Arrange:
41
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
34
42
  """
35
43
  Add rhs as a column
36
44
  """
@@ -38,7 +46,7 @@ class Beside(Arrange):
38
46
  # operands into a single operation
39
47
  return Beside([*self, rhs])
40
48
 
41
- def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
49
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
42
50
  """
43
51
  Add rhs as a row
44
52
  """
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
- from copy import deepcopy
4
+ from copy import copy, deepcopy
5
5
  from dataclasses import dataclass, field
6
6
  from io import BytesIO
7
- from typing import TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, overload
8
8
 
9
9
  from .._utils.ipython import (
10
10
  get_display_function,
@@ -25,41 +25,93 @@ if TYPE_CHECKING:
25
25
 
26
26
 
27
27
  @dataclass
28
- class Arrange:
28
+ class Compose:
29
29
  """
30
30
  Base class for those that create plot compositions
31
31
 
32
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.
36
-
37
- Parameters
38
- ----------
39
- operands:
40
- The objects to be put together (composed).
33
+ through the operators that it makes possible.
34
+ The operators are of two kinds:
35
+
36
+ ### 1. Composing Operators
37
+
38
+ The combine plots or compositions into a single composition.
39
+ Both operands are either a plot or a composition.
40
+
41
+ `/`
42
+
43
+ : Arrange operands side by side.
44
+ Powered by the subclass [](`~plotnine.composition.Beside`).
45
+
46
+ `|`
47
+
48
+ : Arrange operands vertically.
49
+ Powered by the subclass [](`~plotnine.composition.Stack`).
50
+
51
+ `-`
52
+
53
+ : Arrange operands side by side _and_ at the same nesting level.
54
+ Also powered by the subclass [](`~plotnine.composition.Beside`).
55
+
56
+ ### 2. Plot Modifying Operators
57
+
58
+ The modify all or some of the plots in a composition.
59
+ The left operand is a composition and the right operand is a
60
+ _plotaddable_; any object that can be added to a `ggplot` object
61
+ e.g. _geoms_, _stats_, _themes_, _facets_, ... .
62
+
63
+ `&`
64
+
65
+ : Add right hand side to all plots in the composition.
66
+
67
+ `*`
68
+
69
+ : Add right hand side to all plots in the top-most nesting
70
+ level of the composition.
71
+
72
+ `+`
73
+
74
+ : Add right hand side to the last plot in the composition.
75
+
76
+ See Also
77
+ --------
78
+ plotnine.composition.Beside : To arrange plots side by side
79
+ plotnine.composition.Stack : To arrange plots vertically
80
+ plotnine.composition.plot_spacer : To add a blank space between plots
41
81
  """
42
82
 
43
- operands: list[ggplot | Arrange]
83
+ items: list[ggplot | Compose]
84
+ """
85
+ The objects to be arranged (composed).
86
+ """
44
87
 
45
88
  # These are created in the _create_figure method
46
89
  figure: Figure = field(init=False, repr=False)
47
90
  plotspecs: list[plotspec] = field(init=False, repr=False)
48
91
  gridspec: p9GridSpec = field(init=False, repr=False)
49
92
 
93
+ def __post_init__(self):
94
+ # The way we handle the plots has consequences that would
95
+ # prevent having a duplicate plot in the composition.
96
+ # Using copies prevents this.
97
+ self.items = [
98
+ op if isinstance(op, Compose) else deepcopy(op)
99
+ for op in self.items
100
+ ]
101
+
50
102
  @abc.abstractmethod
51
- def __or__(self, rhs: ggplot | Arrange) -> Arrange:
103
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
52
104
  """
53
105
  Add rhs as a column
54
106
  """
55
107
 
56
108
  @abc.abstractmethod
57
- def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
109
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
58
110
  """
59
111
  Add rhs as a row
60
112
  """
61
113
 
62
- def __add__(self, rhs: ggplot | Arrange | PlotAddable) -> Arrange:
114
+ def __add__(self, rhs: ggplot | Compose | PlotAddable) -> Compose:
63
115
  """
64
116
  Add rhs to the composition
65
117
 
@@ -70,24 +122,36 @@ class Arrange:
70
122
  """
71
123
  from plotnine import ggplot
72
124
 
73
- if not isinstance(rhs, (ggplot, Arrange)):
125
+ if not isinstance(rhs, (ggplot, Compose)):
74
126
  cmp = deepcopy(self)
75
127
  cmp.last_plot = cmp.last_plot + rhs
76
128
  return cmp
77
- return self.__class__([*self, rhs])
78
129
 
79
- def __sub__(self, rhs: ggplot | Arrange) -> Arrange:
130
+ t1, t2 = type(self).__name__, type(rhs).__name__
131
+ msg = f"unsupported operand type(s) for +: '{t1}' and '{t2}'"
132
+ raise TypeError(msg)
133
+
134
+ def __sub__(self, rhs: ggplot | Compose) -> Compose:
80
135
  """
81
- Add the rhs besides the composition
136
+ Add the rhs onto the composition
82
137
 
83
138
  Parameters
84
139
  ----------
85
140
  rhs:
86
141
  What to place besides the composition
87
142
  """
88
- return self.__class__([self, rhs])
143
+ from plotnine import ggplot
144
+
145
+ from . import Beside
146
+
147
+ if not isinstance(rhs, (ggplot, Compose)):
148
+ t1, t2 = type(self).__name__, type(rhs).__name__
149
+ msg = f"unsupported operand type(s) for -: '{t1}' and '{t2}'"
150
+ raise TypeError(msg)
89
151
 
90
- def __and__(self, rhs: PlotAddable) -> Arrange:
152
+ return Beside([self, rhs])
153
+
154
+ def __and__(self, rhs: PlotAddable) -> Compose:
91
155
  """
92
156
  Add rhs to all plots in the composition
93
157
 
@@ -98,17 +162,17 @@ class Arrange:
98
162
  """
99
163
  self = deepcopy(self)
100
164
 
101
- def add_other(op: Arrange):
102
- for item in op:
103
- if isinstance(item, Arrange):
165
+ def add_other(cmp: Compose):
166
+ for i, item in enumerate(cmp):
167
+ if isinstance(item, Compose):
104
168
  add_other(item)
105
169
  else:
106
- item += rhs
170
+ cmp[i] = item + copy(rhs)
107
171
 
108
172
  add_other(self)
109
173
  return self
110
174
 
111
- def __mul__(self, rhs: PlotAddable) -> Arrange:
175
+ def __mul__(self, rhs: PlotAddable) -> Compose:
112
176
  """
113
177
  Add rhs to the outermost nesting level of the composition
114
178
 
@@ -121,22 +185,38 @@ class Arrange:
121
185
 
122
186
  self = deepcopy(self)
123
187
 
124
- for item in self:
188
+ for i, item in enumerate(self):
125
189
  if isinstance(item, ggplot):
126
- item += rhs
190
+ self[i] = item + copy(rhs)
191
+
127
192
  return self
128
193
 
129
194
  def __len__(self) -> int:
130
195
  """
131
196
  Number of operand
132
197
  """
133
- return len(self.operands)
198
+ return len(self.items)
134
199
 
135
- def __iter__(self) -> Iterator[ggplot | Arrange]:
200
+ def __iter__(self) -> Iterator[ggplot | Compose]:
136
201
  """
137
- Return an iterable of all the operands
202
+ Return an iterable of all the items
138
203
  """
139
- return iter(self.operands)
204
+ return iter(self.items)
205
+
206
+ @overload
207
+ def __getitem__(self, index: int) -> ggplot | Compose: ...
208
+
209
+ @overload
210
+ def __getitem__(self, index: slice) -> list[ggplot | Compose]: ...
211
+
212
+ def __getitem__(
213
+ self,
214
+ index: int | slice,
215
+ ) -> ggplot | Compose | list[ggplot | Compose]:
216
+ return self.items[index]
217
+
218
+ def __setitem__(self, key, value):
219
+ self.items[key] = value
140
220
 
141
221
  def _ipython_display_(self):
142
222
  """
@@ -165,7 +245,7 @@ class Arrange:
165
245
  """
166
246
  from plotnine import ggplot
167
247
 
168
- last_operand = self.operands[-1]
248
+ last_operand = self.items[-1]
169
249
  if isinstance(last_operand, ggplot):
170
250
  return last_operand
171
251
  else:
@@ -178,9 +258,9 @@ class Arrange:
178
258
  """
179
259
  from plotnine import ggplot
180
260
 
181
- last_operand = self.operands[-1]
261
+ last_operand = self.items[-1]
182
262
  if isinstance(last_operand, ggplot):
183
- self.operands[-1] = plot
263
+ self.items[-1] = plot
184
264
  else:
185
265
  last_operand.last_plot = plot
186
266
 
@@ -232,7 +312,7 @@ class Arrange:
232
312
  from plotnine._mpl.gridspec import p9GridSpec
233
313
 
234
314
  def _make_plotspecs(
235
- cmp: Arrange, parent_gridspec: p9GridSpec | None
315
+ cmp: Compose, parent_gridspec: p9GridSpec | None
236
316
  ) -> Generator[plotspec]:
237
317
  """
238
318
  Return the plot specification for each subplot in the composition
@@ -340,7 +420,7 @@ class Arrange:
340
420
  dpi :
341
421
  DPI to use for raster graphics. If None, defaults to using
342
422
  the `dpi` of theme to the first plot.
343
- kwargs :
423
+ **kwargs :
344
424
  These are ignored. Here to "softly" match the API of
345
425
  `ggplot.save()`.
346
426
  """
@@ -355,7 +435,7 @@ class Arrange:
355
435
 
356
436
  @dataclass
357
437
  class plot_composition_context:
358
- cmp: Arrange
438
+ cmp: Compose
359
439
  show: bool
360
440
 
361
441
  def __post_init__(self):
@@ -17,6 +17,12 @@ class plot_spacer(ggplot):
17
17
 
18
18
  The color can also be modified by adding a [](`~plotnine.theme`)
19
19
  and setting the [](`~plotnine.themes.themeable.plot_background`).
20
+
21
+ See Also
22
+ --------
23
+ plotnine.composition.Beside : To arrange plots side by side
24
+ plotnine.composition.Stack : To arrange plots vertically
25
+ plotnine.composition.Compose : For more on composing plots
20
26
  """
21
27
 
22
28
  def __init__(