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.
- plotnine/__init__.py +31 -37
- plotnine/_mpl/gridspec.py +265 -0
- plotnine/_mpl/layout_manager/__init__.py +6 -0
- plotnine/_mpl/layout_manager/_engine.py +87 -0
- plotnine/_mpl/layout_manager/_layout_items.py +775 -0
- plotnine/_mpl/layout_manager/_layout_tree.py +625 -0
- plotnine/_mpl/layout_manager/_spaces.py +1007 -0
- plotnine/_mpl/utils.py +78 -10
- plotnine/_utils/__init__.py +4 -4
- plotnine/_utils/dev.py +45 -27
- plotnine/animation.py +1 -1
- plotnine/coords/coord_trans.py +1 -1
- plotnine/data/__init__.py +12 -8
- plotnine/doctools.py +1 -1
- plotnine/facets/facet.py +30 -39
- plotnine/facets/facet_grid.py +14 -6
- plotnine/facets/facet_wrap.py +3 -5
- plotnine/facets/strips.py +2 -7
- plotnine/geoms/geom_crossbar.py +2 -3
- plotnine/geoms/geom_path.py +1 -1
- plotnine/geoms/geom_text.py +3 -1
- plotnine/ggplot.py +94 -65
- plotnine/guides/guide.py +10 -8
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +5 -5
- plotnine/guides/guides.py +3 -3
- plotnine/iapi.py +1 -0
- plotnine/labels.py +5 -0
- plotnine/options.py +14 -7
- plotnine/plot_composition/__init__.py +10 -0
- plotnine/plot_composition/_compose.py +427 -0
- plotnine/plot_composition/_plotspec.py +50 -0
- plotnine/plot_composition/_spacer.py +32 -0
- plotnine/positions/position_dodge.py +1 -1
- plotnine/positions/position_dodge2.py +1 -1
- plotnine/positions/position_stack.py +1 -2
- plotnine/qplot.py +1 -2
- plotnine/scales/__init__.py +0 -6
- plotnine/scales/scale.py +1 -1
- plotnine/stats/binning.py +1 -1
- plotnine/stats/smoothers.py +3 -5
- plotnine/stats/stat_density.py +1 -1
- plotnine/stats/stat_qq_line.py +1 -1
- plotnine/stats/stat_sina.py +1 -1
- plotnine/themes/elements/__init__.py +2 -0
- plotnine/themes/elements/element_text.py +34 -24
- plotnine/themes/elements/margin.py +73 -60
- plotnine/themes/targets.py +2 -0
- plotnine/themes/theme.py +13 -7
- plotnine/themes/theme_gray.py +27 -31
- plotnine/themes/theme_matplotlib.py +25 -28
- plotnine/themes/theme_seaborn.py +31 -34
- plotnine/themes/theme_void.py +17 -26
- plotnine/themes/themeable.py +286 -153
- {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/METADATA +4 -3
- {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/RECORD +59 -52
- {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info}/WHEEL +1 -1
- plotnine/_mpl/_plot_side_space.py +0 -888
- plotnine/_mpl/_plotnine_tight_layout.py +0 -293
- plotnine/_mpl/layout_engine.py +0 -110
- {plotnine-0.14.4.dist-info → plotnine-0.15.0.dev1.dist-info/licenses}/LICENSE +0 -0
- {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.
|
|
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.
|
|
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
plotnine/scales/__init__.py
CHANGED
|
@@ -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'
|
|
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
|
plotnine/stats/smoothers.py
CHANGED
|
@@ -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
|
|
plotnine/stats/stat_density.py
CHANGED
|
@@ -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]))
|
plotnine/stats/stat_qq_line.py
CHANGED
|
@@ -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.
|
|
65
|
+
"Cannot fit line quantiles. 'line_p' must be of length 2"
|
|
66
66
|
)
|
|
67
67
|
return self.params
|
|
68
68
|
|
plotnine/stats/stat_sina.py
CHANGED
|
@@ -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
|
|
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
|
)
|