plotnine 0.14.5__py3-none-any.whl → 0.15.0.dev2__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 +916 -0
- plotnine/_mpl/layout_manager/_layout_tree.py +625 -0
- plotnine/_mpl/layout_manager/_spaces.py +1007 -0
- plotnine/_mpl/patches.py +1 -1
- plotnine/_mpl/text.py +59 -24
- plotnine/_mpl/utils.py +78 -10
- plotnine/_utils/__init__.py +5 -5
- 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 +7 -9
- plotnine/geoms/geom_crossbar.py +2 -3
- plotnine/geoms/geom_path.py +1 -1
- plotnine/ggplot.py +94 -65
- plotnine/guides/guide.py +12 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +12 -13
- plotnine/guides/guides.py +3 -3
- plotnine/iapi.py +5 -2
- plotnine/labels.py +5 -0
- plotnine/mapping/aes.py +4 -3
- plotnine/options.py +14 -7
- plotnine/plot_composition/__init__.py +10 -0
- plotnine/plot_composition/_compose.py +436 -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 +35 -24
- plotnine/themes/elements/margin.py +73 -60
- plotnine/themes/targets.py +3 -1
- plotnine/themes/theme.py +13 -7
- plotnine/themes/theme_gray.py +28 -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 +290 -157
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/METADATA +4 -3
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/RECORD +61 -54
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.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.5.dist-info → plotnine-0.15.0.dev2.dist-info/licenses}/LICENSE +0 -0
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/top_level.txt +0 -0
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Routines to adjust subplot params so that subplots are
|
|
3
|
-
nicely fit in the figure. In doing so, only axis labels, tick labels, axes
|
|
4
|
-
titles and offsetboxes that are anchored to axes are currently considered.
|
|
5
|
-
|
|
6
|
-
Internally, this module assumes that the margins (left margin, etc.) which are
|
|
7
|
-
differences between `Axes.get_tightbbox` and `Axes.bbox` are independent of
|
|
8
|
-
Axes position. This may fail if `Axes.adjustable` is `datalim` as well as
|
|
9
|
-
such cases as when left or right margin are affected by xlabel.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import typing
|
|
15
|
-
from dataclasses import dataclass
|
|
16
|
-
|
|
17
|
-
from ._plot_side_space import LRTBSpaces, WHSpaceParts, calculate_panel_spacing
|
|
18
|
-
from .utils import bbox_in_figure_space, get_transPanels
|
|
19
|
-
|
|
20
|
-
if typing.TYPE_CHECKING:
|
|
21
|
-
from typing import Literal, TypeAlias
|
|
22
|
-
|
|
23
|
-
from matplotlib.figure import Figure
|
|
24
|
-
from matplotlib.text import Text
|
|
25
|
-
from matplotlib.transforms import Transform
|
|
26
|
-
|
|
27
|
-
from plotnine._mpl.offsetbox import FlexibleAnchoredOffsetbox
|
|
28
|
-
from plotnine.facets.facet import facet
|
|
29
|
-
from plotnine.iapi import legend_artists
|
|
30
|
-
|
|
31
|
-
from .layout_engine import LayoutPack
|
|
32
|
-
|
|
33
|
-
AxesLocation: TypeAlias = Literal[
|
|
34
|
-
"all", "first_row", "last_row", "first_col", "last_col"
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
@dataclass
|
|
39
|
-
class GridSpecParams:
|
|
40
|
-
"""
|
|
41
|
-
Gridspec Parameters
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
left: float
|
|
45
|
-
right: float
|
|
46
|
-
top: float
|
|
47
|
-
bottom: float
|
|
48
|
-
wspace: float
|
|
49
|
-
hspace: float
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@dataclass
|
|
53
|
-
class TightParams:
|
|
54
|
-
"""
|
|
55
|
-
All parameters computed for the plotnine tight layout engine
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
facet: facet
|
|
59
|
-
sides: LRTBSpaces
|
|
60
|
-
gullies: WHSpaceParts
|
|
61
|
-
|
|
62
|
-
def __post_init__(self):
|
|
63
|
-
self.params = GridSpecParams(
|
|
64
|
-
left=self.sides.left,
|
|
65
|
-
right=self.sides.right,
|
|
66
|
-
top=self.sides.top,
|
|
67
|
-
bottom=self.sides.bottom,
|
|
68
|
-
wspace=self.gullies.wspace,
|
|
69
|
-
hspace=self.gullies.hspace,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
if (ratio := self.facet._aspect_ratio()) is not None:
|
|
73
|
-
current_ratio = self.gullies.aspect_ratio
|
|
74
|
-
if ratio > current_ratio:
|
|
75
|
-
# Increase aspect ratio, taller panels
|
|
76
|
-
self._reduce_width(ratio)
|
|
77
|
-
elif ratio < current_ratio:
|
|
78
|
-
# Increase aspect ratio, wider panels
|
|
79
|
-
self._reduce_height(ratio)
|
|
80
|
-
|
|
81
|
-
def _reduce_height(self, ratio: float):
|
|
82
|
-
"""
|
|
83
|
-
Reduce the height of axes to get the aspect ratio
|
|
84
|
-
"""
|
|
85
|
-
parts = self.gullies
|
|
86
|
-
|
|
87
|
-
# New height w.r.t figure height
|
|
88
|
-
h1 = ratio * parts.w * (parts.W / parts.H)
|
|
89
|
-
|
|
90
|
-
# Half of the total vertical reduction w.r.t figure height
|
|
91
|
-
dh = (parts.h - h1) * self.facet.nrow / 2
|
|
92
|
-
|
|
93
|
-
# Reduce plot area height
|
|
94
|
-
self.params.top -= dh
|
|
95
|
-
self.params.bottom += dh
|
|
96
|
-
self.params.hspace = parts.sh / h1
|
|
97
|
-
|
|
98
|
-
# Add more vertical plot margin
|
|
99
|
-
self.sides.t.plot_margin += dh
|
|
100
|
-
self.sides.b.plot_margin += dh
|
|
101
|
-
|
|
102
|
-
def _reduce_width(self, ratio: float):
|
|
103
|
-
"""
|
|
104
|
-
Reduce the width of axes to get the aspect ratio
|
|
105
|
-
"""
|
|
106
|
-
parts = self.gullies
|
|
107
|
-
|
|
108
|
-
# New width w.r.t figure width
|
|
109
|
-
w1 = (parts.h * parts.H) / (ratio * parts.W)
|
|
110
|
-
|
|
111
|
-
# Half of the total horizontal reduction w.r.t figure width
|
|
112
|
-
dw = (parts.w - w1) * self.facet.ncol / 2
|
|
113
|
-
|
|
114
|
-
# Reduce width
|
|
115
|
-
self.params.left += dw
|
|
116
|
-
self.params.right -= dw
|
|
117
|
-
self.params.wspace = parts.sw / w1
|
|
118
|
-
|
|
119
|
-
# Add more horizontal margin
|
|
120
|
-
self.sides.l.plot_margin += dw
|
|
121
|
-
self.sides.r.plot_margin += dw
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def get_plotnine_tight_layout(pack: LayoutPack) -> TightParams:
|
|
125
|
-
"""
|
|
126
|
-
Compute tight layout parameters
|
|
127
|
-
"""
|
|
128
|
-
sides = LRTBSpaces(pack)
|
|
129
|
-
gullies = calculate_panel_spacing(pack, sides)
|
|
130
|
-
tight_params = TightParams(pack.facet, sides, gullies)
|
|
131
|
-
return tight_params
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def set_figure_artist_positions(
|
|
135
|
-
pack: LayoutPack,
|
|
136
|
-
tparams: TightParams,
|
|
137
|
-
):
|
|
138
|
-
"""
|
|
139
|
-
Set the x,y position of the artists around the panels
|
|
140
|
-
"""
|
|
141
|
-
theme = pack.theme
|
|
142
|
-
sides = tparams.sides
|
|
143
|
-
params = tparams.params
|
|
144
|
-
|
|
145
|
-
if pack.plot_title:
|
|
146
|
-
ha = theme.getp(("plot_title", "ha"))
|
|
147
|
-
pack.plot_title.set_y(sides.t.edge("plot_title"))
|
|
148
|
-
horizontally_align_text_with_panels(pack.plot_title, params, ha, pack)
|
|
149
|
-
|
|
150
|
-
if pack.plot_subtitle:
|
|
151
|
-
ha = theme.getp(("plot_subtitle", "ha"))
|
|
152
|
-
pack.plot_subtitle.set_y(sides.t.edge("plot_subtitle"))
|
|
153
|
-
horizontally_align_text_with_panels(
|
|
154
|
-
pack.plot_subtitle, params, ha, pack
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
if pack.plot_caption:
|
|
158
|
-
ha = theme.getp(("plot_caption", "ha"), "right")
|
|
159
|
-
pack.plot_caption.set_y(sides.b.edge("plot_caption"))
|
|
160
|
-
horizontally_align_text_with_panels(
|
|
161
|
-
pack.plot_caption, params, ha, pack
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if pack.axis_title_x:
|
|
165
|
-
ha = theme.getp(("axis_title_x", "ha"), "center")
|
|
166
|
-
pack.axis_title_x.set_y(sides.b.edge("axis_title_x"))
|
|
167
|
-
horizontally_align_text_with_panels(
|
|
168
|
-
pack.axis_title_x, params, ha, pack
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
if pack.axis_title_y:
|
|
172
|
-
va = theme.getp(("axis_title_y", "va"), "center")
|
|
173
|
-
pack.axis_title_y.set_x(sides.l.edge("axis_title_y"))
|
|
174
|
-
vertically_align_text_with_panels(pack.axis_title_y, params, va, pack)
|
|
175
|
-
|
|
176
|
-
if pack.legends:
|
|
177
|
-
set_legends_position(pack.legends, tparams, pack.figure)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def horizontally_align_text_with_panels(
|
|
181
|
-
text: Text, params: GridSpecParams, ha: str | float, pack: LayoutPack
|
|
182
|
-
):
|
|
183
|
-
"""
|
|
184
|
-
Horizontal justification
|
|
185
|
-
|
|
186
|
-
Reinterpret horizontal alignment to be justification about the panels.
|
|
187
|
-
"""
|
|
188
|
-
if isinstance(ha, str):
|
|
189
|
-
lookup = {
|
|
190
|
-
"left": 0.0,
|
|
191
|
-
"center": 0.5,
|
|
192
|
-
"right": 1.0,
|
|
193
|
-
}
|
|
194
|
-
f = lookup[ha]
|
|
195
|
-
else:
|
|
196
|
-
f = ha
|
|
197
|
-
|
|
198
|
-
box = bbox_in_figure_space(text, pack.figure, pack.renderer)
|
|
199
|
-
x = params.left * (1 - f) + (params.right - box.width) * f
|
|
200
|
-
text.set_x(x)
|
|
201
|
-
text.set_horizontalalignment("left")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def vertically_align_text_with_panels(
|
|
205
|
-
text: Text, params: GridSpecParams, va: str | float, pack: LayoutPack
|
|
206
|
-
):
|
|
207
|
-
"""
|
|
208
|
-
Vertical justification
|
|
209
|
-
|
|
210
|
-
Reinterpret vertical alignment to be justification about the panels.
|
|
211
|
-
"""
|
|
212
|
-
if isinstance(va, str):
|
|
213
|
-
lookup = {
|
|
214
|
-
"top": 1.0,
|
|
215
|
-
"center": 0.5,
|
|
216
|
-
"baseline": 0.5,
|
|
217
|
-
"center_baseline": 0.5,
|
|
218
|
-
"bottom": 0.0,
|
|
219
|
-
}
|
|
220
|
-
f = lookup[va]
|
|
221
|
-
else:
|
|
222
|
-
f = va
|
|
223
|
-
|
|
224
|
-
box = bbox_in_figure_space(text, pack.figure, pack.renderer)
|
|
225
|
-
y = params.bottom * (1 - f) + (params.top - box.height) * f
|
|
226
|
-
text.set_y(y)
|
|
227
|
-
text.set_verticalalignment("bottom")
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def set_legends_position(
|
|
231
|
-
legends: legend_artists,
|
|
232
|
-
tparams: TightParams,
|
|
233
|
-
fig: Figure,
|
|
234
|
-
):
|
|
235
|
-
"""
|
|
236
|
-
Place legend on the figure and justify is a required
|
|
237
|
-
"""
|
|
238
|
-
|
|
239
|
-
def set_position(
|
|
240
|
-
aob: FlexibleAnchoredOffsetbox,
|
|
241
|
-
anchor_point: tuple[float, float],
|
|
242
|
-
xy_loc: tuple[float, float],
|
|
243
|
-
transform: Transform = fig.transFigure,
|
|
244
|
-
):
|
|
245
|
-
"""
|
|
246
|
-
Place box (by the anchor point) at given xy location
|
|
247
|
-
|
|
248
|
-
Parameters
|
|
249
|
-
----------
|
|
250
|
-
aob :
|
|
251
|
-
Offsetbox to place
|
|
252
|
-
anchor_point :
|
|
253
|
-
Point on the Offsefbox.
|
|
254
|
-
xy_loc :
|
|
255
|
-
Point where to place the offsetbox.
|
|
256
|
-
transform :
|
|
257
|
-
Transformation
|
|
258
|
-
"""
|
|
259
|
-
aob.xy_loc = xy_loc
|
|
260
|
-
aob.set_bbox_to_anchor(anchor_point, transform) # type: ignore
|
|
261
|
-
|
|
262
|
-
sides = tparams.sides
|
|
263
|
-
params = fig.subplotpars
|
|
264
|
-
|
|
265
|
-
if legends.right:
|
|
266
|
-
j = legends.right.justification
|
|
267
|
-
y = params.bottom * (1 - j) + (params.top - sides.r._legend_height) * j
|
|
268
|
-
x = sides.r.edge("legend")
|
|
269
|
-
set_position(legends.right.box, (x, y), (1, 0))
|
|
270
|
-
|
|
271
|
-
if legends.left:
|
|
272
|
-
j = legends.left.justification
|
|
273
|
-
y = params.bottom * (1 - j) + (params.top - sides.l._legend_height) * j
|
|
274
|
-
x = sides.l.edge("legend")
|
|
275
|
-
set_position(legends.left.box, (x, y), (0, 0))
|
|
276
|
-
|
|
277
|
-
if legends.top:
|
|
278
|
-
j = legends.top.justification
|
|
279
|
-
x = params.left * (1 - j) + (params.right - sides.t._legend_width) * j
|
|
280
|
-
y = sides.t.edge("legend")
|
|
281
|
-
set_position(legends.top.box, (x, y), (0, 1))
|
|
282
|
-
|
|
283
|
-
if legends.bottom:
|
|
284
|
-
j = legends.bottom.justification
|
|
285
|
-
x = params.left * (1 - j) + (params.right - sides.b._legend_width) * j
|
|
286
|
-
y = sides.b.edge("legend")
|
|
287
|
-
set_position(legends.bottom.box, (x, y), (0, 0))
|
|
288
|
-
|
|
289
|
-
# Inside legends are placed using the panels coordinate system
|
|
290
|
-
if legends.inside:
|
|
291
|
-
transPanels = get_transPanels(fig)
|
|
292
|
-
for l in legends.inside:
|
|
293
|
-
set_position(l.box, l.position, l.justification, transPanels)
|
plotnine/_mpl/layout_engine.py
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import typing
|
|
4
|
-
from dataclasses import asdict, dataclass
|
|
5
|
-
|
|
6
|
-
from matplotlib.layout_engine import LayoutEngine
|
|
7
|
-
from matplotlib.text import Text
|
|
8
|
-
|
|
9
|
-
if typing.TYPE_CHECKING:
|
|
10
|
-
from typing import Any, Optional
|
|
11
|
-
|
|
12
|
-
from matplotlib.axes import Axes
|
|
13
|
-
from matplotlib.backend_bases import RendererBase
|
|
14
|
-
from matplotlib.figure import Figure
|
|
15
|
-
|
|
16
|
-
from plotnine import ggplot, theme
|
|
17
|
-
from plotnine._mpl.text import StripText
|
|
18
|
-
from plotnine.facets.facet import facet
|
|
19
|
-
from plotnine.iapi import legend_artists
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass
|
|
23
|
-
class LayoutPack:
|
|
24
|
-
"""
|
|
25
|
-
Objects required to compute the layout
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
axs: list[Axes]
|
|
29
|
-
figure: Figure
|
|
30
|
-
renderer: RendererBase
|
|
31
|
-
theme: theme
|
|
32
|
-
facet: facet
|
|
33
|
-
axis_title_x: Optional[Text] = None
|
|
34
|
-
axis_title_y: Optional[Text] = None
|
|
35
|
-
# The legends references the structure that contains the
|
|
36
|
-
# AnchoredOffsetboxes (groups of legends)
|
|
37
|
-
legends: Optional[legend_artists] = None
|
|
38
|
-
plot_caption: Optional[Text] = None
|
|
39
|
-
plot_subtitle: Optional[Text] = None
|
|
40
|
-
plot_title: Optional[Text] = None
|
|
41
|
-
strip_text_x: Optional[list[StripText]] = None
|
|
42
|
-
strip_text_y: Optional[list[StripText]] = None
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class PlotnineLayoutEngine(LayoutEngine):
|
|
46
|
-
"""
|
|
47
|
-
Implement geometry management for plotnine plots
|
|
48
|
-
|
|
49
|
-
This layout manager automatically adjusts the location of
|
|
50
|
-
objects placed around the plot panels and the subplot
|
|
51
|
-
spacing parameters so that the plot fits cleanly within
|
|
52
|
-
the figure area.
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
_adjust_compatible = True
|
|
56
|
-
_colorbar_gridspec = False
|
|
57
|
-
|
|
58
|
-
def __init__(self, plot: ggplot):
|
|
59
|
-
self.plot = plot
|
|
60
|
-
self.theme = plot.theme
|
|
61
|
-
|
|
62
|
-
def execute(self, fig: Figure):
|
|
63
|
-
from contextlib import nullcontext
|
|
64
|
-
|
|
65
|
-
from ._plotnine_tight_layout import (
|
|
66
|
-
get_plotnine_tight_layout,
|
|
67
|
-
set_figure_artist_positions,
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
pack = self.setup()
|
|
71
|
-
|
|
72
|
-
with getattr(pack.renderer, "_draw_disabled", nullcontext)():
|
|
73
|
-
tparams = get_plotnine_tight_layout(pack)
|
|
74
|
-
|
|
75
|
-
fig.subplots_adjust(**asdict(tparams.params))
|
|
76
|
-
set_figure_artist_positions(pack, tparams)
|
|
77
|
-
|
|
78
|
-
def setup(self) -> LayoutPack:
|
|
79
|
-
"""
|
|
80
|
-
Put together objects required to do the layout
|
|
81
|
-
"""
|
|
82
|
-
targets = self.theme.targets
|
|
83
|
-
|
|
84
|
-
def get_target(name: str) -> Any:
|
|
85
|
-
"""
|
|
86
|
-
Return themeable target or None
|
|
87
|
-
"""
|
|
88
|
-
if self.theme.T.is_blank(name):
|
|
89
|
-
return None
|
|
90
|
-
else:
|
|
91
|
-
t = getattr(targets, name)
|
|
92
|
-
if isinstance(t, Text) and t.get_text() == "":
|
|
93
|
-
return None
|
|
94
|
-
return t
|
|
95
|
-
|
|
96
|
-
return LayoutPack(
|
|
97
|
-
axs=self.plot.axs,
|
|
98
|
-
figure=self.plot.figure,
|
|
99
|
-
renderer=self.plot.figure._get_renderer(), # pyright: ignore
|
|
100
|
-
theme=self.theme,
|
|
101
|
-
facet=self.plot.facet,
|
|
102
|
-
axis_title_x=get_target("axis_title_x"),
|
|
103
|
-
axis_title_y=get_target("axis_title_y"),
|
|
104
|
-
legends=get_target("legends"),
|
|
105
|
-
plot_caption=get_target("plot_caption"),
|
|
106
|
-
plot_subtitle=get_target("plot_subtitle"),
|
|
107
|
-
plot_title=get_target("plot_title"),
|
|
108
|
-
strip_text_x=get_target("strip_text_x"),
|
|
109
|
-
strip_text_y=get_target("strip_text_y"),
|
|
110
|
-
)
|
|
File without changes
|
|
File without changes
|