plotnine 0.14.4__py3-none-any.whl → 0.15.0.dev1__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 (62) hide show
  1. plotnine/__init__.py +31 -37
  2. plotnine/_mpl/gridspec.py +265 -0
  3. plotnine/_mpl/layout_manager/__init__.py +6 -0
  4. plotnine/_mpl/layout_manager/_engine.py +87 -0
  5. plotnine/_mpl/layout_manager/_layout_items.py +775 -0
  6. plotnine/_mpl/layout_manager/_layout_tree.py +625 -0
  7. plotnine/_mpl/layout_manager/_spaces.py +1007 -0
  8. plotnine/_mpl/utils.py +78 -10
  9. plotnine/_utils/__init__.py +4 -4
  10. plotnine/_utils/dev.py +45 -27
  11. plotnine/animation.py +1 -1
  12. plotnine/coords/coord_trans.py +1 -1
  13. plotnine/data/__init__.py +12 -8
  14. plotnine/doctools.py +1 -1
  15. plotnine/facets/facet.py +30 -39
  16. plotnine/facets/facet_grid.py +14 -6
  17. plotnine/facets/facet_wrap.py +3 -5
  18. plotnine/facets/strips.py +2 -7
  19. plotnine/geoms/geom_crossbar.py +2 -3
  20. plotnine/geoms/geom_path.py +1 -1
  21. plotnine/geoms/geom_text.py +3 -1
  22. plotnine/ggplot.py +94 -65
  23. plotnine/guides/guide.py +10 -8
  24. plotnine/guides/guide_colorbar.py +3 -3
  25. plotnine/guides/guide_legend.py +5 -5
  26. plotnine/guides/guides.py +3 -3
  27. plotnine/iapi.py +1 -0
  28. plotnine/labels.py +5 -0
  29. plotnine/options.py +14 -7
  30. plotnine/plot_composition/__init__.py +10 -0
  31. plotnine/plot_composition/_compose.py +427 -0
  32. plotnine/plot_composition/_plotspec.py +50 -0
  33. plotnine/plot_composition/_spacer.py +32 -0
  34. plotnine/positions/position_dodge.py +1 -1
  35. plotnine/positions/position_dodge2.py +1 -1
  36. plotnine/positions/position_stack.py +1 -2
  37. plotnine/qplot.py +1 -2
  38. plotnine/scales/__init__.py +0 -6
  39. plotnine/scales/scale.py +1 -1
  40. plotnine/stats/binning.py +1 -1
  41. plotnine/stats/smoothers.py +3 -5
  42. plotnine/stats/stat_density.py +1 -1
  43. plotnine/stats/stat_qq_line.py +1 -1
  44. plotnine/stats/stat_sina.py +1 -1
  45. plotnine/themes/elements/__init__.py +2 -0
  46. plotnine/themes/elements/element_text.py +34 -24
  47. plotnine/themes/elements/margin.py +73 -60
  48. plotnine/themes/targets.py +2 -0
  49. plotnine/themes/theme.py +13 -7
  50. plotnine/themes/theme_gray.py +27 -31
  51. plotnine/themes/theme_matplotlib.py +25 -28
  52. plotnine/themes/theme_seaborn.py +31 -34
  53. plotnine/themes/theme_void.py +17 -26
  54. plotnine/themes/themeable.py +286 -153
  55. {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/METADATA +4 -3
  56. {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/RECORD +59 -52
  57. {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/WHEEL +1 -1
  58. plotnine/_mpl/_plot_side_space.py +0 -888
  59. plotnine/_mpl/_plotnine_tight_layout.py +0 -293
  60. plotnine/_mpl/layout_engine.py +0 -110
  61. {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info/licenses}/LICENSE +0 -0
  62. {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,427 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from dataclasses import dataclass
5
+ from io import BytesIO
6
+ from typing import TYPE_CHECKING
7
+
8
+ from .._utils.ipython import (
9
+ get_display_function,
10
+ get_ipython,
11
+ )
12
+ from ..options import get_option
13
+ from ._plotspec import plotspec
14
+
15
+ if TYPE_CHECKING:
16
+ from pathlib import Path
17
+ from typing import Generator, Iterator, Self
18
+
19
+ from matplotlib.figure import Figure
20
+
21
+ from plotnine._mpl.gridspec import p9GridSpec
22
+ from plotnine._utils.ipython import FigureFormat
23
+ from plotnine.ggplot import PlotAddable, ggplot
24
+
25
+
26
+ class Compose:
27
+ """
28
+ Arrange two or more plots
29
+
30
+ Parameters
31
+ ----------
32
+ operands:
33
+ The objects to be put together (composed).
34
+ """
35
+
36
+ def __init__(self, operands: list[ggplot | Compose]):
37
+ self.operands = operands
38
+
39
+ # These are created in the _create_figure method
40
+ self.figure: Figure
41
+ self.plotspecs: list[plotspec]
42
+ self.gridspec: p9GridSpec
43
+
44
+ def __add__(self, rhs: ggplot | Compose) -> Compose:
45
+ """
46
+ Add rhs to the composition
47
+
48
+ Parameters
49
+ ----------
50
+ rhs:
51
+ What to add to the composition
52
+ """
53
+ return self.__class__([*self, rhs])
54
+
55
+ def __sub__(self, rhs: ggplot | Compose) -> Compose:
56
+ """
57
+ Add the rhs besides the composition
58
+
59
+ Parameters
60
+ ----------
61
+ rhs:
62
+ What to place besides the composition
63
+ """
64
+ return self.__class__([self, rhs])
65
+
66
+ def __and__(self, rhs: PlotAddable) -> Compose:
67
+ """
68
+ Add rhs to all plots in the composition
69
+
70
+ Parameters
71
+ ----------
72
+ rhs:
73
+ What to add.
74
+ """
75
+ self = deepcopy(self)
76
+
77
+ def add_other(op: Compose):
78
+ for item in op:
79
+ if isinstance(item, Compose):
80
+ add_other(item)
81
+ else:
82
+ item += rhs
83
+
84
+ add_other(self)
85
+ return self
86
+
87
+ def __mul__(self, rhs: PlotAddable) -> Compose:
88
+ """
89
+ Add rhs to the outermost nesting level of the composition
90
+
91
+ Parameters
92
+ ----------
93
+ rhs:
94
+ What to add.
95
+ """
96
+ from plotnine import ggplot
97
+
98
+ self = deepcopy(self)
99
+
100
+ for item in self:
101
+ if isinstance(item, ggplot):
102
+ item += rhs
103
+ return self
104
+
105
+ def __len__(self) -> int:
106
+ """
107
+ Number of operand
108
+ """
109
+ return len(self.operands)
110
+
111
+ def __iter__(self) -> Iterator[ggplot | Compose]:
112
+ """
113
+ Return an iterable of all the operands
114
+ """
115
+ return iter(self.operands)
116
+
117
+ def _ipython_display_(self):
118
+ """
119
+ Display plot in the output of the cell
120
+ """
121
+ return self._display()
122
+
123
+ @property
124
+ def nrow(self) -> int:
125
+ """
126
+ Number of rows in the composition
127
+ """
128
+ return 0
129
+
130
+ @property
131
+ def ncol(self) -> int:
132
+ """
133
+ Number of cols in the composition
134
+ """
135
+ return 0
136
+
137
+ @property
138
+ def last_plot(self):
139
+ """
140
+ Last plot added to the composition
141
+ """
142
+ from plotnine import ggplot
143
+
144
+ last_operand = self.operands[-1]
145
+ if isinstance(last_operand, ggplot):
146
+ return last_operand
147
+ else:
148
+ return last_operand.last_plot
149
+
150
+ def __deepcopy__(self, memo):
151
+ """
152
+ Deep copy without copying the figure
153
+ """
154
+ cls = self.__class__
155
+ result = cls.__new__(cls)
156
+ memo[id(self)] = result
157
+ old = self.__dict__
158
+ new = result.__dict__
159
+
160
+ shallow = {"figure", "gridsspec", "__copy"}
161
+ for key, item in old.items():
162
+ if key in shallow:
163
+ new[key] = item
164
+ memo[id(new[key])] = new[key]
165
+ else:
166
+ new[key] = deepcopy(item, memo)
167
+
168
+ old["__copy"] = result
169
+
170
+ return result
171
+
172
+ def _to_retina(self):
173
+ from plotnine import ggplot
174
+
175
+ for item in self:
176
+ if isinstance(item, ggplot):
177
+ item.theme = item.theme.to_retina()
178
+ else:
179
+ item._to_retina()
180
+
181
+ def _create_gridspec(self, figure, nest_into):
182
+ """
183
+ Create the gridspec for this composition
184
+ """
185
+ from plotnine._mpl.gridspec import p9GridSpec
186
+
187
+ self.gridspec = p9GridSpec(
188
+ self.nrow, self.ncol, figure, nest_into=nest_into
189
+ )
190
+
191
+ def _create_figure(self):
192
+ import matplotlib.pyplot as plt
193
+
194
+ from plotnine import ggplot
195
+ from plotnine._mpl.gridspec import p9GridSpec
196
+
197
+ def _make_plotspecs(
198
+ cmp: Compose, parent_gridspec: p9GridSpec | None
199
+ ) -> Generator[plotspec]:
200
+ """
201
+ Return the plot specification for each subplot in the composition
202
+ """
203
+ # This gridspec contains a composition group e.g.
204
+ # (p2 | p3) of p1 | (p2 | p3)
205
+ ss_or_none = parent_gridspec[0] if parent_gridspec else None
206
+ cmp._create_gridspec(self.figure, ss_or_none)
207
+
208
+ # Each subplot in the composition will contain one of:
209
+ # 1. A plot
210
+ # 2. A plot composition
211
+ # 3. Nothing
212
+ # Iterating over the gridspec yields the SubplotSpecs for each
213
+ # "subplot" in the grid. The SubplotSpec is the handle that
214
+ # allows us to set it up for a plot or to nest another gridspec
215
+ # in it.
216
+ for item, subplot_spec in zip(cmp, cmp.gridspec): # pyright: ignore[reportArgumentType]
217
+ if isinstance(item, ggplot):
218
+ yield plotspec(
219
+ item,
220
+ self.figure,
221
+ cmp.gridspec,
222
+ subplot_spec,
223
+ p9GridSpec(1, 1, self.figure, nest_into=subplot_spec),
224
+ )
225
+ elif item:
226
+ yield from _make_plotspecs(
227
+ item,
228
+ p9GridSpec(1, 1, self.figure, nest_into=subplot_spec),
229
+ )
230
+
231
+ self.figure = plt.figure()
232
+ self.plotspecs = list(_make_plotspecs(self, None))
233
+
234
+ def _display(self):
235
+ """
236
+ Display plot in the cells output
237
+
238
+ This function is called for its side-effects.
239
+
240
+ It draws the plot to an io buffer then uses ipython display
241
+ methods to show the result.
242
+ """
243
+ ip = get_ipython()
244
+ format: FigureFormat = get_option(
245
+ "figure_format"
246
+ ) or ip.config.InlineBackend.get("figure_format", "retina")
247
+
248
+ if format == "retina":
249
+ self = deepcopy(self)
250
+ self._to_retina()
251
+
252
+ buf = BytesIO()
253
+ self.save(buf, "png" if format == "retina" else format)
254
+ figure_size_px = self.last_plot.theme._figure_size_px
255
+ display_func = get_display_function(format, figure_size_px)
256
+ display_func(buf.getvalue())
257
+
258
+ def draw(self, *, show: bool = False) -> Figure:
259
+ """
260
+ Render the composed plots
261
+
262
+ Parameters
263
+ ----------
264
+ show :
265
+ Whether to show the plot.
266
+
267
+ Returns
268
+ -------
269
+ :
270
+ Matplotlib figure
271
+ """
272
+ from .._mpl.layout_manager import PlotnineCompositionLayoutEngine
273
+
274
+ with plot_composition_context(self, show):
275
+ self._create_figure()
276
+ figure = self.figure
277
+
278
+ for ps in self.plotspecs:
279
+ ps.plot.draw()
280
+
281
+ self.figure.set_layout_engine(
282
+ PlotnineCompositionLayoutEngine(self)
283
+ )
284
+ return figure
285
+
286
+ def save(
287
+ self, filename: str | Path | BytesIO, save_format: str | None = None
288
+ ):
289
+ figure = self.draw()
290
+ figure.savefig(filename, format=save_format)
291
+
292
+
293
+ @dataclass
294
+ class plot_composition_context:
295
+ cmp: Compose
296
+ show: bool
297
+
298
+ def __post_init__(self):
299
+ import matplotlib as mpl
300
+
301
+ # The dpi is needed when the figure is created, either as
302
+ # a parameter to plt.figure() or an rcParam.
303
+ # https://github.com/matplotlib/matplotlib/issues/24644
304
+ # When drawing the Composition, the dpi themeable is infective
305
+ # because it sets the rcParam after this figure is created.
306
+ rcParams = {"figure.dpi": self.cmp.last_plot.theme.getp("dpi")}
307
+ self._rc_context = mpl.rc_context(rcParams)
308
+
309
+ def __enter__(self) -> Self:
310
+ """
311
+ Enclose in matplolib & pandas environments
312
+ """
313
+ self._rc_context.__enter__()
314
+ return self
315
+
316
+ def __exit__(self, exc_type, exc_value, exc_traceback):
317
+ import matplotlib.pyplot as plt
318
+
319
+ if exc_type is None:
320
+ if self.show:
321
+ plt.show()
322
+ else:
323
+ plt.close(self.cmp.figure)
324
+ else:
325
+ # There is an exception, close any figure
326
+ if hasattr(self.cmp, "figure"):
327
+ plt.close(self.cmp.figure)
328
+
329
+ self._rc_context.__exit__(exc_type, exc_value, exc_traceback)
330
+
331
+
332
+ class OR(Compose):
333
+ """
334
+ Compose by adding a column
335
+ """
336
+
337
+ @property
338
+ def nrow(self) -> int:
339
+ return 1
340
+
341
+ @property
342
+ def ncol(self) -> int:
343
+ return len(self)
344
+
345
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
346
+ """
347
+ Add rhs as a column
348
+ """
349
+ # This is an adjacent or i.e. (OR | rhs) so we collapse the
350
+ # operands into a single operation
351
+ return OR([*self, rhs])
352
+
353
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
354
+ """
355
+ Add rhs as a row
356
+ """
357
+ return DIV([self, rhs])
358
+
359
+
360
+ class DIV(Compose):
361
+ """
362
+ Compose by adding a row
363
+ """
364
+
365
+ @property
366
+ def nrow(self) -> int:
367
+ return len(self)
368
+
369
+ @property
370
+ def ncol(self) -> int:
371
+ return 1
372
+
373
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
374
+ """
375
+ Add rhs as a row
376
+ """
377
+ # This is an adjacent div i.e. (DIV | rhs) so we collapse the
378
+ # operands into a single operation
379
+ return DIV([*self, rhs])
380
+
381
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
382
+ """
383
+ Add rhs as a column
384
+ """
385
+ return OR([self, rhs])
386
+
387
+
388
+ class ADD(Compose):
389
+ """
390
+ Compose by adding
391
+ """
392
+
393
+ @property
394
+ def nrow(self) -> int:
395
+ from plotnine.facets.facet_wrap import wrap_dims
396
+
397
+ return wrap_dims(len(self))[0]
398
+
399
+ @property
400
+ def ncol(self) -> int:
401
+ from plotnine.facets.facet_wrap import wrap_dims
402
+
403
+ return wrap_dims(len(self))[1]
404
+
405
+ def __add__(self, rhs: ggplot | Compose) -> Compose:
406
+ """
407
+ Add rhs to the Composed group
408
+ """
409
+ return ADD([*self, rhs])
410
+
411
+ def __or__(self, rhs: ggplot | Compose) -> Compose:
412
+ """
413
+ Add rhs as a column
414
+ """
415
+ return OR([self, rhs])
416
+
417
+ def __truediv__(self, rhs: ggplot | Compose) -> Compose:
418
+ """
419
+ Add rhs as a row
420
+ """
421
+ return DIV([self, rhs])
422
+
423
+ def __sub__(self, rhs: ggplot | Compose) -> Compose:
424
+ """
425
+ Add rhs as a column
426
+ """
427
+ return OR([self, rhs])
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from matplotlib.figure import Figure
8
+ from matplotlib.gridspec import SubplotSpec
9
+
10
+ from plotnine._mpl.gridspec import p9GridSpec
11
+ from plotnine.ggplot import ggplot
12
+
13
+
14
+ @dataclass
15
+ class plotspec:
16
+ """
17
+ Plot Specification
18
+ """
19
+
20
+ plot: ggplot
21
+ """
22
+ Plot
23
+ """
24
+
25
+ figure: Figure
26
+ """
27
+ Figure in which the draw the plot
28
+ """
29
+
30
+ composition_gridspec: p9GridSpec
31
+ """
32
+ The gridspec of the innermost composition group that contains the plot
33
+ """
34
+
35
+ subplotspec: SubplotSpec
36
+ """
37
+ The subplotspec that contains the plot
38
+
39
+ This is the subplot within the composition gridspec and it will
40
+ contain the plot's gridspec.
41
+ """
42
+
43
+ plot_gridspec: p9GridSpec
44
+ """
45
+ The gridspec in which the plot is drawn
46
+ """
47
+
48
+ def __post_init__(self):
49
+ self.plot.figure = self.figure
50
+ self.plot._gridspec = self.plot_gridspec
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+
5
+ from plotnine import element_rect, ggplot, theme, theme_void
6
+
7
+
8
+ class spacer(ggplot):
9
+ """
10
+ An empty plot
11
+ """
12
+
13
+ def __init__(self):
14
+ super().__init__()
15
+ self.theme = theme_void()
16
+
17
+ def __add__(self, rhs) -> spacer: # pyright: ignore[reportIncompatibleMethodOverride]
18
+ """
19
+ Add to spacer
20
+
21
+ All added objects are no ops except the plot_background,
22
+ i.e.:
23
+
24
+ theme(plot_background=element_rect(fill="red"))
25
+ """
26
+ self = deepcopy(self)
27
+ if isinstance(rhs, theme):
28
+ fill = rhs.getp(("plot_background", "facecolor"))
29
+ self.theme += theme(
30
+ plot_background=element_rect(fill=fill),
31
+ )
32
+ return self
@@ -61,7 +61,7 @@ class position_dodge(position):
61
61
  and ("xmax" not in data)
62
62
  and (self.params["width"] is None)
63
63
  ):
64
- msg = "Width not defined. " "Set with `position_dodge(width = ?)`"
64
+ msg = "Width not defined. Set with `position_dodge(width = ?)`"
65
65
  raise PlotnineError(msg)
66
66
 
67
67
  params = copy(self.params)
@@ -64,7 +64,7 @@ class position_dodge2(position_dodge):
64
64
  and ("xmax" not in data)
65
65
  and (self.params["width"] is None)
66
66
  ):
67
- msg = "Width not defined. " "Set with `position_dodge2(width = ?)`"
67
+ msg = "Width not defined. Set with `position_dodge2(width = ?)`"
68
68
  raise PlotnineError(msg)
69
69
 
70
70
  params = copy(self.params)
@@ -39,8 +39,7 @@ class position_stack(position):
39
39
  if "ymax" in data:
40
40
  if any((data["ymin"] != 0) & (data["ymax"] != 0)):
41
41
  warn(
42
- "Stacking not well defined when not "
43
- "anchored on the axis.",
42
+ "Stacking not well defined when not anchored on the axis.",
44
43
  PlotnineWarning,
45
44
  )
46
45
  var = "ymax"
plotnine/qplot.py CHANGED
@@ -180,8 +180,7 @@ def qplot(
180
180
  return "wrap"
181
181
 
182
182
  warn(
183
- "Could not determine the type of faceting, "
184
- "therefore no faceting.",
183
+ "Could not determine the type of faceting, therefore no faceting.",
185
184
  PlotnineWarning,
186
185
  )
187
186
  return "null"
@@ -79,7 +79,6 @@ from .scale_identity import (
79
79
  # linetype
80
80
  from .scale_linetype import (
81
81
  scale_linetype,
82
- scale_linetype_continuous,
83
82
  scale_linetype_discrete,
84
83
  )
85
84
 
@@ -97,7 +96,6 @@ from .scale_manual import (
97
96
  # shape
98
97
  from .scale_shape import (
99
98
  scale_shape,
100
- scale_shape_continuous,
101
99
  scale_shape_discrete,
102
100
  )
103
101
 
@@ -116,7 +114,6 @@ from .scale_size import (
116
114
  from .scale_stroke import (
117
115
  scale_stroke,
118
116
  scale_stroke_continuous,
119
- scale_stroke_discrete,
120
117
  )
121
118
 
122
119
  # xy position and transforms
@@ -198,11 +195,9 @@ __all__ = (
198
195
  # linetype
199
196
  "scale_linetype",
200
197
  "scale_linetype_discrete",
201
- "scale_linetype_continuous",
202
198
  # shape
203
199
  "scale_shape",
204
200
  "scale_shape_discrete",
205
- "scale_shape_continuous",
206
201
  # size
207
202
  "scale_size",
208
203
  "scale_size_area",
@@ -214,7 +209,6 @@ __all__ = (
214
209
  # stroke
215
210
  "scale_stroke",
216
211
  "scale_stroke_continuous",
217
- "scale_stroke_discrete",
218
212
  # identity
219
213
  "scale_alpha_identity",
220
214
  "scale_color_identity",
plotnine/scales/scale.py CHANGED
@@ -243,7 +243,7 @@ class scale(
243
243
  if not (exp := self.expand):
244
244
  m1, m2 = mult if isinstance(mult, (tuple, list)) else (mult, mult)
245
245
  a1, a2 = cast(
246
- tuple[float, float],
246
+ "tuple[float, float]",
247
247
  (add if isinstance(add, (tuple, list)) else (add, add)),
248
248
  )
249
249
  exp = (m1, a1, m2, a2)
plotnine/stats/binning.py CHANGED
@@ -73,7 +73,7 @@ def breaks_from_binwidth(
73
73
 
74
74
  if boundary is not None and center is not None:
75
75
  raise PlotnineError(
76
- "Only one of 'boundary' and 'center' " "may be specified."
76
+ "Only one of 'boundary' and 'center' may be specified."
77
77
  )
78
78
  elif boundary is None:
79
79
  # When center is None, put the min and max of data in outer
@@ -37,7 +37,7 @@ def predictdf(data, xseq, **params) -> pd.DataFrame:
37
37
  "gpr": gpr,
38
38
  }
39
39
 
40
- method = cast(str | Callable[..., pd.DataFrame], params["method"])
40
+ method = cast("str | Callable[..., pd.DataFrame]", params["method"])
41
41
 
42
42
  if isinstance(method, str):
43
43
  try:
@@ -163,8 +163,7 @@ def rlm(data, xseq, **params) -> pd.DataFrame:
163
163
 
164
164
  if params["se"]:
165
165
  warnings.warn(
166
- "Confidence intervals are not yet implemented"
167
- " for RLM smoothing.",
166
+ "Confidence intervals are not yet implemented for RLM smoothing.",
168
167
  PlotnineWarning,
169
168
  )
170
169
 
@@ -190,8 +189,7 @@ def rlm_formula(data, xseq, **params) -> pd.DataFrame:
190
189
 
191
190
  if params["se"]:
192
191
  warnings.warn(
193
- "Confidence intervals are not yet implemented"
194
- " for RLM smoothing.",
192
+ "Confidence intervals are not yet implemented for RLM smoothing.",
195
193
  PlotnineWarning,
196
194
  )
197
195
 
@@ -171,7 +171,7 @@ def compute_density(x, weight, range, **params):
171
171
  x = np.asarray(x, dtype=float)
172
172
  not_nan = ~np.isnan(x)
173
173
  x = x[not_nan]
174
- bw = cast(str | float, params["bw"])
174
+ bw = cast("str | float", params["bw"])
175
175
  kernel = params["kernel"]
176
176
  bounds = params["bounds"]
177
177
  has_bounds = not (np.isneginf(bounds[0]) and np.isposinf(bounds[1]))
@@ -62,7 +62,7 @@ class stat_qq_line(stat):
62
62
  def setup_params(self, data):
63
63
  if len(self.params["line_p"]) != 2:
64
64
  raise PlotnineError(
65
- "Cannot fit line quantiles. " "'line_p' must be of length 2"
65
+ "Cannot fit line quantiles. 'line_p' must be of length 2"
66
66
  )
67
67
  return self.params
68
68
 
@@ -101,7 +101,7 @@ class stat_sina(stat):
101
101
  and (data["x"] != data["x"].iloc[0]).any()
102
102
  ):
103
103
  raise TypeError(
104
- "Continuous x aesthetic -- did you forget " "aes(group=...)?"
104
+ "Continuous x aesthetic -- did you forget aes(group=...)?"
105
105
  )
106
106
  return data
107
107
 
@@ -2,10 +2,12 @@ from .element_blank import element_blank
2
2
  from .element_line import element_line
3
3
  from .element_rect import element_rect
4
4
  from .element_text import element_text
5
+ from .margin import margin
5
6
 
6
7
  __all__ = (
7
8
  "element_blank",
8
9
  "element_line",
9
10
  "element_rect",
10
11
  "element_text",
12
+ "margin",
11
13
  )