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.
Files changed (139) hide show
  1. plotnine/__init__.py +2 -0
  2. plotnine/_mpl/layout_manager/_engine.py +1 -1
  3. plotnine/_mpl/layout_manager/_layout_items.py +126 -41
  4. plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
  5. plotnine/_mpl/layout_manager/_spaces.py +305 -101
  6. plotnine/_mpl/patches.py +70 -34
  7. plotnine/_mpl/text.py +144 -63
  8. plotnine/_mpl/utils.py +1 -1
  9. plotnine/_utils/__init__.py +50 -107
  10. plotnine/_utils/context.py +78 -2
  11. plotnine/_utils/ipython.py +35 -51
  12. plotnine/_utils/quarto.py +26 -0
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/composition/__init__.py +11 -0
  15. plotnine/composition/_beside.py +55 -0
  16. plotnine/composition/_compose.py +471 -0
  17. plotnine/composition/_plot_spacer.py +60 -0
  18. plotnine/composition/_stack.py +55 -0
  19. plotnine/coords/coord.py +3 -3
  20. plotnine/data/__init__.py +31 -0
  21. plotnine/data/anscombe-quartet.csv +45 -0
  22. plotnine/doctools.py +4 -4
  23. plotnine/facets/facet.py +4 -4
  24. plotnine/facets/strips.py +17 -28
  25. plotnine/geoms/annotate.py +13 -13
  26. plotnine/geoms/annotation_logticks.py +7 -8
  27. plotnine/geoms/annotation_stripes.py +6 -6
  28. plotnine/geoms/geom.py +60 -27
  29. plotnine/geoms/geom_abline.py +3 -2
  30. plotnine/geoms/geom_area.py +2 -2
  31. plotnine/geoms/geom_bar.py +1 -0
  32. plotnine/geoms/geom_bin_2d.py +6 -2
  33. plotnine/geoms/geom_blank.py +0 -3
  34. plotnine/geoms/geom_boxplot.py +8 -4
  35. plotnine/geoms/geom_col.py +2 -2
  36. plotnine/geoms/geom_count.py +6 -2
  37. plotnine/geoms/geom_crossbar.py +3 -3
  38. plotnine/geoms/geom_density_2d.py +6 -2
  39. plotnine/geoms/geom_dotplot.py +2 -2
  40. plotnine/geoms/geom_errorbar.py +2 -2
  41. plotnine/geoms/geom_errorbarh.py +2 -2
  42. plotnine/geoms/geom_histogram.py +1 -1
  43. plotnine/geoms/geom_hline.py +3 -2
  44. plotnine/geoms/geom_linerange.py +2 -2
  45. plotnine/geoms/geom_map.py +5 -5
  46. plotnine/geoms/geom_path.py +11 -12
  47. plotnine/geoms/geom_point.py +4 -5
  48. plotnine/geoms/geom_pointdensity.py +4 -0
  49. plotnine/geoms/geom_pointrange.py +3 -5
  50. plotnine/geoms/geom_polygon.py +2 -3
  51. plotnine/geoms/geom_qq.py +4 -0
  52. plotnine/geoms/geom_qq_line.py +4 -0
  53. plotnine/geoms/geom_quantile.py +4 -0
  54. plotnine/geoms/geom_raster.py +4 -5
  55. plotnine/geoms/geom_rect.py +3 -4
  56. plotnine/geoms/geom_ribbon.py +7 -7
  57. plotnine/geoms/geom_rug.py +1 -1
  58. plotnine/geoms/geom_segment.py +2 -2
  59. plotnine/geoms/geom_sina.py +3 -3
  60. plotnine/geoms/geom_smooth.py +7 -3
  61. plotnine/geoms/geom_step.py +2 -2
  62. plotnine/geoms/geom_text.py +2 -3
  63. plotnine/geoms/geom_violin.py +8 -5
  64. plotnine/geoms/geom_vline.py +3 -2
  65. plotnine/ggplot.py +64 -85
  66. plotnine/guides/guide.py +7 -10
  67. plotnine/guides/guide_colorbar.py +3 -3
  68. plotnine/guides/guide_legend.py +3 -3
  69. plotnine/guides/guides.py +6 -6
  70. plotnine/helpers.py +49 -0
  71. plotnine/iapi.py +28 -5
  72. plotnine/labels.py +3 -3
  73. plotnine/layer.py +36 -19
  74. plotnine/mapping/_atomic.py +178 -0
  75. plotnine/mapping/_env.py +13 -2
  76. plotnine/mapping/_eval_environment.py +1 -1
  77. plotnine/mapping/aes.py +85 -49
  78. plotnine/scales/__init__.py +2 -0
  79. plotnine/scales/limits.py +7 -7
  80. plotnine/scales/scale.py +3 -3
  81. plotnine/scales/scale_color.py +82 -18
  82. plotnine/scales/scale_continuous.py +6 -4
  83. plotnine/scales/scale_datetime.py +28 -14
  84. plotnine/scales/scale_discrete.py +1 -1
  85. plotnine/scales/scale_identity.py +21 -2
  86. plotnine/scales/scale_manual.py +8 -2
  87. plotnine/scales/scale_xy.py +2 -2
  88. plotnine/stats/binning.py +4 -1
  89. plotnine/stats/smoothers.py +23 -36
  90. plotnine/stats/stat.py +20 -32
  91. plotnine/stats/stat_bin.py +6 -5
  92. plotnine/stats/stat_bin_2d.py +11 -9
  93. plotnine/stats/stat_bindot.py +13 -16
  94. plotnine/stats/stat_boxplot.py +6 -6
  95. plotnine/stats/stat_count.py +6 -9
  96. plotnine/stats/stat_density.py +7 -10
  97. plotnine/stats/stat_density_2d.py +12 -8
  98. plotnine/stats/stat_ecdf.py +7 -6
  99. plotnine/stats/stat_ellipse.py +9 -6
  100. plotnine/stats/stat_function.py +10 -8
  101. plotnine/stats/stat_hull.py +6 -3
  102. plotnine/stats/stat_identity.py +5 -2
  103. plotnine/stats/stat_pointdensity.py +5 -7
  104. plotnine/stats/stat_qq.py +46 -20
  105. plotnine/stats/stat_qq_line.py +16 -11
  106. plotnine/stats/stat_quantile.py +15 -9
  107. plotnine/stats/stat_sina.py +13 -15
  108. plotnine/stats/stat_smooth.py +8 -10
  109. plotnine/stats/stat_sum.py +5 -2
  110. plotnine/stats/stat_summary.py +7 -10
  111. plotnine/stats/stat_summary_bin.py +11 -14
  112. plotnine/stats/stat_unique.py +5 -2
  113. plotnine/stats/stat_ydensity.py +8 -11
  114. plotnine/themes/elements/__init__.py +2 -1
  115. plotnine/themes/elements/element_line.py +17 -9
  116. plotnine/themes/elements/margin.py +64 -1
  117. plotnine/themes/theme.py +9 -1
  118. plotnine/themes/theme_538.py +0 -1
  119. plotnine/themes/theme_bw.py +0 -1
  120. plotnine/themes/theme_dark.py +0 -1
  121. plotnine/themes/theme_gray.py +6 -5
  122. plotnine/themes/theme_light.py +1 -1
  123. plotnine/themes/theme_matplotlib.py +5 -5
  124. plotnine/themes/theme_seaborn.py +7 -4
  125. plotnine/themes/theme_void.py +9 -8
  126. plotnine/themes/theme_xkcd.py +0 -1
  127. plotnine/themes/themeable.py +109 -31
  128. plotnine/typing.py +17 -6
  129. plotnine/watermark.py +3 -3
  130. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
  131. plotnine-0.15.2.dist-info/RECORD +221 -0
  132. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
  133. plotnine/plot_composition/__init__.py +0 -10
  134. plotnine/plot_composition/_compose.py +0 -436
  135. plotnine/plot_composition/_spacer.py +0 -32
  136. plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
  137. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  138. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
  139. {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, make_labels
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.layer import layer
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, plot: ggplot) -> ggplot:
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 = make_labels(self.mapping)
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 _ipython_display_(self):
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
- Display plot in the output of the cell
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
- This method will always be called when a ggplot object is the
147
- last in the cell.
154
+ Notes
155
+ -----
156
+ - https://ipython.readthedocs.io/en/stable/config/integrating.html
148
157
  """
149
- self._display()
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
- Users should prefer this method instead of printing or repring
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
- # Take charge of the display because we have to make
165
- # adjustments for retina output.
166
- self._display()
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 | ggplot | Compose,
244
- ) -> ggplot | Compose:
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 .plot_composition import OR
253
+ from .composition import Beside
267
254
 
268
- return OR([self, rhs])
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 .plot_composition import DIV
261
+ from .composition import Stack
275
262
 
276
- return DIV([self, rhs])
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 .plot_composition import OR
269
+ from .composition import Beside
283
270
 
284
- return OR([self, rhs])
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
- if not hasattr(self, "figure"):
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
- SidePosition,
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, SidePosition], str]
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) -> SidePosition:
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) -> SidePosition:
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) -> SidePosition | tuple[float, float]:
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) -> SidePosition | tuple[float, float]:
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 SidePosition
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[SidePosition, tuple[type[PackerBase], slice]] = {
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) -> SidePosition:
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
 
@@ -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 SidePosition
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[SidePosition, tuple[type[PackerBase], slice]] = {
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) -> SidePosition:
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
- SidePosition,
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, plot: ggplot):
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(plot.guides, f.name, g)
123
+ setattr(other.guides, f.name, g)
124
124
 
125
- return plot
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[SidePosition, float]
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: list[int] = list(np.argsort(orders))
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
- x: float
235
- y: float
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
- box_width: float
239
- box_height: float
240
- strip_text_margin: float
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, plot: p9.ggplot) -> p9.ggplot:
106
+ def __radd__(self, other: p9.ggplot) -> p9.ggplot:
107
107
  """
108
108
  Add labels to ggplot object
109
109
  """
110
- plot.labels.update(self.labels)
111
- return plot
110
+ other.labels.update(self.labels)
111
+ return other
112
112
 
113
113
 
114
114
  class xlab(labs):