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.
- plotnine/__init__.py +2 -0
- plotnine/_mpl/layout_manager/_engine.py +1 -1
- plotnine/_mpl/layout_manager/_layout_items.py +126 -41
- plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
- plotnine/_mpl/layout_manager/_spaces.py +305 -101
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +144 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +50 -107
- plotnine/_utils/context.py +78 -2
- plotnine/_utils/ipython.py +35 -51
- plotnine/_utils/quarto.py +26 -0
- plotnine/_utils/yippie.py +115 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/composition/_beside.py +55 -0
- plotnine/composition/_compose.py +471 -0
- plotnine/composition/_plot_spacer.py +60 -0
- plotnine/composition/_stack.py +55 -0
- plotnine/coords/coord.py +3 -3
- plotnine/data/__init__.py +31 -0
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +4 -4
- plotnine/facets/facet.py +4 -4
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotate.py +13 -13
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +60 -27
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_area.py +2 -2
- plotnine/geoms/geom_bar.py +1 -0
- plotnine/geoms/geom_bin_2d.py +6 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +8 -4
- plotnine/geoms/geom_col.py +2 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +2 -2
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +5 -5
- plotnine/geoms/geom_path.py +11 -12
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointdensity.py +4 -0
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_qq.py +4 -0
- plotnine/geoms/geom_qq_line.py +4 -0
- plotnine/geoms/geom_quantile.py +4 -0
- plotnine/geoms/geom_raster.py +4 -5
- plotnine/geoms/geom_rect.py +3 -4
- plotnine/geoms/geom_ribbon.py +7 -7
- plotnine/geoms/geom_rug.py +1 -1
- plotnine/geoms/geom_segment.py +2 -2
- plotnine/geoms/geom_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +7 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +8 -5
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/ggplot.py +64 -85
- plotnine/guides/guide.py +7 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +3 -3
- plotnine/guides/guides.py +6 -6
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/labels.py +3 -3
- plotnine/layer.py +36 -19
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/_env.py +13 -2
- plotnine/mapping/_eval_environment.py +1 -1
- plotnine/mapping/aes.py +85 -49
- plotnine/scales/__init__.py +2 -0
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +3 -3
- plotnine/scales/scale_color.py +82 -18
- plotnine/scales/scale_continuous.py +6 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/scales/scale_identity.py +21 -2
- plotnine/scales/scale_manual.py +8 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +23 -36
- plotnine/stats/stat.py +20 -32
- plotnine/stats/stat_bin.py +6 -5
- plotnine/stats/stat_bin_2d.py +11 -9
- plotnine/stats/stat_bindot.py +13 -16
- plotnine/stats/stat_boxplot.py +6 -6
- plotnine/stats/stat_count.py +6 -9
- plotnine/stats/stat_density.py +7 -10
- plotnine/stats/stat_density_2d.py +12 -8
- plotnine/stats/stat_ecdf.py +7 -6
- plotnine/stats/stat_ellipse.py +9 -6
- plotnine/stats/stat_function.py +10 -8
- plotnine/stats/stat_hull.py +6 -3
- plotnine/stats/stat_identity.py +5 -2
- plotnine/stats/stat_pointdensity.py +5 -7
- plotnine/stats/stat_qq.py +46 -20
- plotnine/stats/stat_qq_line.py +16 -11
- plotnine/stats/stat_quantile.py +15 -9
- plotnine/stats/stat_sina.py +13 -15
- plotnine/stats/stat_smooth.py +8 -10
- plotnine/stats/stat_sum.py +5 -2
- plotnine/stats/stat_summary.py +7 -10
- plotnine/stats/stat_summary_bin.py +11 -14
- plotnine/stats/stat_unique.py +5 -2
- plotnine/stats/stat_ydensity.py +8 -11
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme.py +9 -1
- plotnine/themes/theme_538.py +0 -1
- plotnine/themes/theme_bw.py +0 -1
- plotnine/themes/theme_dark.py +0 -1
- plotnine/themes/theme_gray.py +6 -5
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +5 -5
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +9 -8
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +109 -31
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
- plotnine-0.15.2.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_compose.py +0 -436
- plotnine/plot_composition/_spacer.py +0 -32
- plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
|
@@ -14,8 +14,9 @@ from __future__ import annotations
|
|
|
14
14
|
from abc import ABC
|
|
15
15
|
from dataclasses import dataclass, field, fields
|
|
16
16
|
from functools import cached_property
|
|
17
|
-
from typing import TYPE_CHECKING
|
|
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
|
|
@@ -26,7 +27,8 @@ if TYPE_CHECKING:
|
|
|
26
27
|
|
|
27
28
|
from plotnine import ggplot
|
|
28
29
|
from plotnine._mpl.gridspec import p9GridSpec
|
|
29
|
-
|
|
30
|
+
from plotnine.iapi import outside_legend
|
|
31
|
+
from plotnine.typing import Side
|
|
30
32
|
|
|
31
33
|
# Note
|
|
32
34
|
# Margins around the plot are specified in figure coordinates
|
|
@@ -69,16 +71,12 @@ class _side_spaces(ABC):
|
|
|
69
71
|
"""
|
|
70
72
|
|
|
71
73
|
items: LayoutItems
|
|
72
|
-
alignment_margin: float = 0
|
|
73
|
-
"""
|
|
74
|
-
A margin added to align this plot with others in a composition
|
|
75
|
-
|
|
76
|
-
This value is calculated during the layout process in a tree structure
|
|
77
|
-
that has convenient access to the sides/edges of the panels in the
|
|
78
|
-
composition.
|
|
79
|
-
"""
|
|
80
74
|
|
|
81
75
|
def __post_init__(self):
|
|
76
|
+
self.side: Side = cast("Side", self.__class__.__name__[:-7])
|
|
77
|
+
"""
|
|
78
|
+
Side of the panel(s) that this class applies to
|
|
79
|
+
"""
|
|
82
80
|
self._calculate()
|
|
83
81
|
|
|
84
82
|
def _calculate(self):
|
|
@@ -132,17 +130,21 @@ class _side_spaces(ABC):
|
|
|
132
130
|
values e.g. 0.2, instead of just left, right, top, bottom &
|
|
133
131
|
center.
|
|
134
132
|
"""
|
|
135
|
-
|
|
133
|
+
if not self.has_legend:
|
|
134
|
+
return (0, 0)
|
|
135
|
+
|
|
136
|
+
ol: outside_legend = getattr(self.items.legends, self.side)
|
|
137
|
+
return self.items.calc.size(ol.box)
|
|
136
138
|
|
|
137
139
|
@cached_property
|
|
138
|
-
def
|
|
140
|
+
def legend_width(self) -> float:
|
|
139
141
|
"""
|
|
140
142
|
Return width of legend in figure coordinates
|
|
141
143
|
"""
|
|
142
144
|
return self._legend_size[0]
|
|
143
145
|
|
|
144
146
|
@cached_property
|
|
145
|
-
def
|
|
147
|
+
def legend_height(self) -> float:
|
|
146
148
|
"""
|
|
147
149
|
Return height of legend in figure coordinates
|
|
148
150
|
"""
|
|
@@ -204,6 +206,81 @@ class _side_spaces(ABC):
|
|
|
204
206
|
"""
|
|
205
207
|
return self.offset + rel_value
|
|
206
208
|
|
|
209
|
+
@property
|
|
210
|
+
def has_tag(self) -> bool:
|
|
211
|
+
"""
|
|
212
|
+
Return True if the space/margin to this side of the panel has a tag
|
|
213
|
+
|
|
214
|
+
If it does, then it will be included in the layout
|
|
215
|
+
"""
|
|
216
|
+
getp = self.items.plot.theme.getp
|
|
217
|
+
return getp("plot_tag_location") == "margin" and self.side in getp(
|
|
218
|
+
"plot_tag_position"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def has_legend(self) -> bool:
|
|
223
|
+
"""
|
|
224
|
+
Return True if the space/margin to this side of the panel has a legend
|
|
225
|
+
|
|
226
|
+
If it does, then it will be included in the layout
|
|
227
|
+
"""
|
|
228
|
+
if not self.items.legends:
|
|
229
|
+
return False
|
|
230
|
+
return hasattr(self.items.legends, self.side)
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def tag_width(self) -> float:
|
|
234
|
+
"""
|
|
235
|
+
The width of the tag including the margins
|
|
236
|
+
|
|
237
|
+
The value is zero except if all these are true:
|
|
238
|
+
- The tag is in the margin `theme(plot_tag_position = "margin")`
|
|
239
|
+
- The tag at one one of the the following locations;
|
|
240
|
+
left, right, topleft, topright, bottomleft or bottomright
|
|
241
|
+
"""
|
|
242
|
+
return 0
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def tag_height(self) -> float:
|
|
246
|
+
"""
|
|
247
|
+
The height of the tag including the margins
|
|
248
|
+
|
|
249
|
+
The value is zero except if all these are true:
|
|
250
|
+
- The tag is in the margin `theme(plot_tag_position = "margin")`
|
|
251
|
+
- The tag at one one of the the following locations;
|
|
252
|
+
top, bottom, topleft, topright, bottomleft or bottomright
|
|
253
|
+
"""
|
|
254
|
+
return 0
|
|
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
|
+
|
|
207
284
|
|
|
208
285
|
@dataclass
|
|
209
286
|
class left_spaces(_side_spaces):
|
|
@@ -214,14 +291,63 @@ class left_spaces(_side_spaces):
|
|
|
214
291
|
"""
|
|
215
292
|
|
|
216
293
|
plot_margin: float = 0
|
|
294
|
+
tag_alignment: float = 0
|
|
295
|
+
"""
|
|
296
|
+
Space added to align the tag in this plot with others in a composition
|
|
297
|
+
|
|
298
|
+
This value is calculated during the layout process, and it ensures that
|
|
299
|
+
all tags on this side of the plot take up the same amount of space in
|
|
300
|
+
the margin. e.g. from
|
|
301
|
+
|
|
302
|
+
------------------------------------
|
|
303
|
+
| plot_margin | tag | artists |
|
|
304
|
+
|------------------------------------|
|
|
305
|
+
| plot_margin | A long tag | artists |
|
|
306
|
+
------------------------------------
|
|
307
|
+
|
|
308
|
+
to
|
|
309
|
+
|
|
310
|
+
------------------------------------
|
|
311
|
+
| plot_margin | tag | artists |
|
|
312
|
+
|------------------------------------|
|
|
313
|
+
| plot_margin | A long tag | artists |
|
|
314
|
+
------------------------------------
|
|
315
|
+
|
|
316
|
+
And the tag is justified within that space e.g if ha="left" we get
|
|
317
|
+
|
|
318
|
+
------------------------------------
|
|
319
|
+
| plot_margin | tag | artists |
|
|
320
|
+
|------------------------------------|
|
|
321
|
+
| plot_margin | A long tag | artists |
|
|
322
|
+
------------------------------------
|
|
323
|
+
|
|
324
|
+
So, contrary to the order in which the space items are laid out, the
|
|
325
|
+
tag_alignment does not necessarily come before the plot_tag.
|
|
326
|
+
"""
|
|
217
327
|
plot_tag_margin_left: float = 0
|
|
218
328
|
plot_tag: float = 0
|
|
219
329
|
plot_tag_margin_right: float = 0
|
|
330
|
+
margin_alignment: float = 0
|
|
331
|
+
"""
|
|
332
|
+
Space added to align this plot with others in a composition
|
|
333
|
+
|
|
334
|
+
This value is calculated during the layout process in a tree structure
|
|
335
|
+
that has convenient access to the sides/edges of the panels in the
|
|
336
|
+
composition.
|
|
337
|
+
"""
|
|
220
338
|
legend: float = 0
|
|
221
339
|
legend_box_spacing: float = 0
|
|
222
340
|
axis_title_y_margin_left: float = 0
|
|
223
341
|
axis_title_y: float = 0
|
|
224
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
|
+
"""
|
|
225
351
|
axis_text_y_margin_left: float = 0
|
|
226
352
|
axis_text_y: float = 0
|
|
227
353
|
axis_text_y_margin_right: float = 0
|
|
@@ -232,22 +358,16 @@ class left_spaces(_side_spaces):
|
|
|
232
358
|
calc = self.items.calc
|
|
233
359
|
items = self.items
|
|
234
360
|
|
|
235
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
236
|
-
# So we make space for it, including any margins it may have.
|
|
237
|
-
plot_tag_in_layout = theme.getp(
|
|
238
|
-
"plot_tag_location"
|
|
239
|
-
) == "margin" and "left" in theme.getp("plot_tag_position")
|
|
240
|
-
|
|
241
361
|
self.plot_margin = theme.getp("plot_margin_left")
|
|
242
362
|
|
|
243
|
-
if
|
|
363
|
+
if self.has_tag and items.plot_tag:
|
|
244
364
|
m = theme.get_margin("plot_tag").fig
|
|
245
365
|
self.plot_tag_margin_left = m.l
|
|
246
366
|
self.plot_tag = calc.width(items.plot_tag)
|
|
247
367
|
self.plot_tag_margin_right = m.r
|
|
248
368
|
|
|
249
369
|
if items.legends and items.legends.left:
|
|
250
|
-
self.legend = self.
|
|
370
|
+
self.legend = self.legend_width
|
|
251
371
|
self.legend_box_spacing = theme.getp("legend_box_spacing")
|
|
252
372
|
|
|
253
373
|
if items.axis_title_y:
|
|
@@ -273,13 +393,6 @@ class left_spaces(_side_spaces):
|
|
|
273
393
|
if adjustment > 0:
|
|
274
394
|
self.plot_margin += adjustment
|
|
275
395
|
|
|
276
|
-
@cached_property
|
|
277
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
278
|
-
if not (self.items.legends and self.items.legends.left):
|
|
279
|
-
return (0, 0)
|
|
280
|
-
|
|
281
|
-
return self.items.calc.size(self.items.legends.left.box)
|
|
282
|
-
|
|
283
396
|
@property
|
|
284
397
|
def offset(self) -> float:
|
|
285
398
|
"""
|
|
@@ -309,18 +422,18 @@ class left_spaces(_side_spaces):
|
|
|
309
422
|
return self.to_figure_space(self.sum_incl(item))
|
|
310
423
|
|
|
311
424
|
@property
|
|
312
|
-
def
|
|
425
|
+
def panel_left_relative(self):
|
|
313
426
|
"""
|
|
314
427
|
Left (relative to the gridspec) of the panels in figure dimensions
|
|
315
428
|
"""
|
|
316
429
|
return self.total
|
|
317
430
|
|
|
318
431
|
@property
|
|
319
|
-
def
|
|
432
|
+
def panel_left(self):
|
|
320
433
|
"""
|
|
321
434
|
Left of the panels in figure space
|
|
322
435
|
"""
|
|
323
|
-
return self.to_figure_space(self.
|
|
436
|
+
return self.to_figure_space(self.panel_left_relative)
|
|
324
437
|
|
|
325
438
|
@property
|
|
326
439
|
def plot_left(self):
|
|
@@ -329,6 +442,17 @@ class left_spaces(_side_spaces):
|
|
|
329
442
|
"""
|
|
330
443
|
return self.x1("legend")
|
|
331
444
|
|
|
445
|
+
@property
|
|
446
|
+
def tag_width(self):
|
|
447
|
+
"""
|
|
448
|
+
The width of the tag including the margins
|
|
449
|
+
"""
|
|
450
|
+
return (
|
|
451
|
+
self.plot_tag_margin_left
|
|
452
|
+
+ self.plot_tag
|
|
453
|
+
+ self.plot_tag_margin_right
|
|
454
|
+
)
|
|
455
|
+
|
|
332
456
|
|
|
333
457
|
@dataclass
|
|
334
458
|
class right_spaces(_side_spaces):
|
|
@@ -339,36 +463,33 @@ class right_spaces(_side_spaces):
|
|
|
339
463
|
"""
|
|
340
464
|
|
|
341
465
|
plot_margin: float = 0
|
|
466
|
+
tag_alignment: float = 0
|
|
342
467
|
plot_tag_margin_right: float = 0
|
|
343
468
|
plot_tag: float = 0
|
|
344
469
|
plot_tag_margin_left: float = 0
|
|
470
|
+
margin_alignment: float = 0
|
|
345
471
|
legend: float = 0
|
|
346
472
|
legend_box_spacing: float = 0
|
|
347
|
-
|
|
473
|
+
strip_text_y_extra_width: float = 0
|
|
348
474
|
|
|
349
475
|
def _calculate(self):
|
|
350
476
|
items = self.items
|
|
351
477
|
theme = self.items.plot.theme
|
|
352
478
|
calc = self.items.calc
|
|
353
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
354
|
-
# So we make space for it, including any margins it may have.
|
|
355
|
-
plot_tag_in_layout = theme.getp(
|
|
356
|
-
"plot_tag_location"
|
|
357
|
-
) == "margin" and "right" in theme.getp("plot_tag_position")
|
|
358
479
|
|
|
359
480
|
self.plot_margin = theme.getp("plot_margin_right")
|
|
360
481
|
|
|
361
|
-
if
|
|
482
|
+
if self.has_tag and items.plot_tag:
|
|
362
483
|
m = theme.get_margin("plot_tag").fig
|
|
363
484
|
self.plot_tag_margin_right = m.r
|
|
364
485
|
self.plot_tag = calc.width(items.plot_tag)
|
|
365
486
|
self.plot_tag_margin_left = m.l
|
|
366
487
|
|
|
367
488
|
if items.legends and items.legends.right:
|
|
368
|
-
self.legend = self.
|
|
489
|
+
self.legend = self.legend_width
|
|
369
490
|
self.legend_box_spacing = theme.getp("legend_box_spacing")
|
|
370
491
|
|
|
371
|
-
self.
|
|
492
|
+
self.strip_text_y_extra_width = items.strip_text_y_extra_width("right")
|
|
372
493
|
|
|
373
494
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
374
495
|
# beyond the axes
|
|
@@ -378,13 +499,6 @@ class right_spaces(_side_spaces):
|
|
|
378
499
|
if adjustment > 0:
|
|
379
500
|
self.plot_margin += adjustment
|
|
380
501
|
|
|
381
|
-
@cached_property
|
|
382
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
383
|
-
if not (self.items.legends and self.items.legends.right):
|
|
384
|
-
return (0, 0)
|
|
385
|
-
|
|
386
|
-
return self.items.calc.size(self.items.legends.right.box)
|
|
387
|
-
|
|
388
502
|
@property
|
|
389
503
|
def offset(self):
|
|
390
504
|
"""
|
|
@@ -414,18 +528,18 @@ class right_spaces(_side_spaces):
|
|
|
414
528
|
return self.to_figure_space(1 - self.sum_upto(item))
|
|
415
529
|
|
|
416
530
|
@property
|
|
417
|
-
def
|
|
531
|
+
def panel_right_relative(self):
|
|
418
532
|
"""
|
|
419
533
|
Right (relative to the gridspec) of the panels in figure dimensions
|
|
420
534
|
"""
|
|
421
535
|
return 1 - self.total
|
|
422
536
|
|
|
423
537
|
@property
|
|
424
|
-
def
|
|
538
|
+
def panel_right(self):
|
|
425
539
|
"""
|
|
426
540
|
Right of the panels in figure space
|
|
427
541
|
"""
|
|
428
|
-
return self.to_figure_space(self.
|
|
542
|
+
return self.to_figure_space(self.panel_right_relative)
|
|
429
543
|
|
|
430
544
|
@property
|
|
431
545
|
def plot_right(self):
|
|
@@ -434,6 +548,17 @@ class right_spaces(_side_spaces):
|
|
|
434
548
|
"""
|
|
435
549
|
return self.x2("legend")
|
|
436
550
|
|
|
551
|
+
@property
|
|
552
|
+
def tag_width(self):
|
|
553
|
+
"""
|
|
554
|
+
The width of the tag including the margins
|
|
555
|
+
"""
|
|
556
|
+
return (
|
|
557
|
+
self.plot_tag_margin_right
|
|
558
|
+
+ self.plot_tag
|
|
559
|
+
+ self.plot_tag_margin_left
|
|
560
|
+
)
|
|
561
|
+
|
|
437
562
|
|
|
438
563
|
@dataclass
|
|
439
564
|
class top_spaces(_side_spaces):
|
|
@@ -444,9 +569,11 @@ class top_spaces(_side_spaces):
|
|
|
444
569
|
"""
|
|
445
570
|
|
|
446
571
|
plot_margin: float = 0
|
|
572
|
+
tag_alignment: float = 0
|
|
447
573
|
plot_tag_margin_top: float = 0
|
|
448
574
|
plot_tag: float = 0
|
|
449
575
|
plot_tag_margin_bottom: float = 0
|
|
576
|
+
margin_alignment: float = 0
|
|
450
577
|
plot_title_margin_top: float = 0
|
|
451
578
|
plot_title: float = 0
|
|
452
579
|
plot_title_margin_bottom: float = 0
|
|
@@ -455,7 +582,7 @@ class top_spaces(_side_spaces):
|
|
|
455
582
|
plot_subtitle_margin_bottom: float = 0
|
|
456
583
|
legend: float = 0
|
|
457
584
|
legend_box_spacing: float = 0
|
|
458
|
-
|
|
585
|
+
strip_text_x_extra_height: float = 0
|
|
459
586
|
|
|
460
587
|
def _calculate(self):
|
|
461
588
|
items = self.items
|
|
@@ -463,15 +590,10 @@ class top_spaces(_side_spaces):
|
|
|
463
590
|
calc = self.items.calc
|
|
464
591
|
W, H = theme.getp("figure_size")
|
|
465
592
|
F = W / H
|
|
466
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
467
|
-
# So we make space for it, including any margins it may have.
|
|
468
|
-
plot_tag_in_layout = theme.getp(
|
|
469
|
-
"plot_tag_location"
|
|
470
|
-
) == "margin" and "top" in theme.getp("plot_tag_position")
|
|
471
593
|
|
|
472
594
|
self.plot_margin = theme.getp("plot_margin_top") * F
|
|
473
595
|
|
|
474
|
-
if
|
|
596
|
+
if self.has_tag and items.plot_tag:
|
|
475
597
|
m = theme.get_margin("plot_tag").fig
|
|
476
598
|
self.plot_tag_margin_top = m.t
|
|
477
599
|
self.plot_tag = calc.height(items.plot_tag)
|
|
@@ -490,10 +612,10 @@ class top_spaces(_side_spaces):
|
|
|
490
612
|
self.plot_subtitle_margin_bottom = m.b * F
|
|
491
613
|
|
|
492
614
|
if items.legends and items.legends.top:
|
|
493
|
-
self.legend = self.
|
|
615
|
+
self.legend = self.legend_height
|
|
494
616
|
self.legend_box_spacing = theme.getp("legend_box_spacing") * F
|
|
495
617
|
|
|
496
|
-
self.
|
|
618
|
+
self.strip_text_x_extra_height = items.strip_text_x_extra_height("top")
|
|
497
619
|
|
|
498
620
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
499
621
|
# beyond the axes
|
|
@@ -503,13 +625,6 @@ class top_spaces(_side_spaces):
|
|
|
503
625
|
if adjustment > 0:
|
|
504
626
|
self.plot_margin += adjustment
|
|
505
627
|
|
|
506
|
-
@cached_property
|
|
507
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
508
|
-
if not (self.items.legends and self.items.legends.top):
|
|
509
|
-
return (0, 0)
|
|
510
|
-
|
|
511
|
-
return self.items.calc.size(self.items.legends.top.box)
|
|
512
|
-
|
|
513
628
|
@property
|
|
514
629
|
def offset(self) -> float:
|
|
515
630
|
"""
|
|
@@ -542,18 +657,18 @@ class top_spaces(_side_spaces):
|
|
|
542
657
|
return self.to_figure_space(1 - self.sum_upto(item))
|
|
543
658
|
|
|
544
659
|
@property
|
|
545
|
-
def
|
|
660
|
+
def panel_top_relative(self):
|
|
546
661
|
"""
|
|
547
662
|
Top (relative to the gridspec) of the panels in figure dimensions
|
|
548
663
|
"""
|
|
549
664
|
return 1 - self.total
|
|
550
665
|
|
|
551
666
|
@property
|
|
552
|
-
def
|
|
667
|
+
def panel_top(self):
|
|
553
668
|
"""
|
|
554
669
|
Top of the panels in figure space
|
|
555
670
|
"""
|
|
556
|
-
return self.to_figure_space(self.
|
|
671
|
+
return self.to_figure_space(self.panel_top_relative)
|
|
557
672
|
|
|
558
673
|
@property
|
|
559
674
|
def plot_top(self):
|
|
@@ -562,6 +677,17 @@ class top_spaces(_side_spaces):
|
|
|
562
677
|
"""
|
|
563
678
|
return self.y2("legend")
|
|
564
679
|
|
|
680
|
+
@property
|
|
681
|
+
def tag_height(self):
|
|
682
|
+
"""
|
|
683
|
+
The height of the tag including the margins
|
|
684
|
+
"""
|
|
685
|
+
return (
|
|
686
|
+
self.plot_tag_margin_top
|
|
687
|
+
+ self.plot_tag
|
|
688
|
+
+ self.plot_tag_margin_bottom
|
|
689
|
+
)
|
|
690
|
+
|
|
565
691
|
|
|
566
692
|
@dataclass
|
|
567
693
|
class bottom_spaces(_side_spaces):
|
|
@@ -572,9 +698,11 @@ class bottom_spaces(_side_spaces):
|
|
|
572
698
|
"""
|
|
573
699
|
|
|
574
700
|
plot_margin: float = 0
|
|
701
|
+
tag_alignment: float = 0
|
|
575
702
|
plot_tag_margin_bottom: float = 0
|
|
576
703
|
plot_tag: float = 0
|
|
577
704
|
plot_tag_margin_top: float = 0
|
|
705
|
+
margin_alignment: float = 0
|
|
578
706
|
plot_caption_margin_bottom: float = 0
|
|
579
707
|
plot_caption: float = 0
|
|
580
708
|
plot_caption_margin_top: float = 0
|
|
@@ -583,6 +711,15 @@ class bottom_spaces(_side_spaces):
|
|
|
583
711
|
axis_title_x_margin_bottom: float = 0
|
|
584
712
|
axis_title_x: float = 0
|
|
585
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
|
+
"""
|
|
586
723
|
axis_text_x_margin_bottom: float = 0
|
|
587
724
|
axis_text_x: float = 0
|
|
588
725
|
axis_text_x_margin_top: float = 0
|
|
@@ -594,15 +731,10 @@ class bottom_spaces(_side_spaces):
|
|
|
594
731
|
calc = self.items.calc
|
|
595
732
|
W, H = theme.getp("figure_size")
|
|
596
733
|
F = W / H
|
|
597
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
598
|
-
# So we make space for it, including any margins it may have.
|
|
599
|
-
plot_tag_in_layout = theme.getp(
|
|
600
|
-
"plot_tag_location"
|
|
601
|
-
) == "margin" and "bottom" in theme.getp("plot_tag_position")
|
|
602
734
|
|
|
603
735
|
self.plot_margin = theme.getp("plot_margin_bottom") * F
|
|
604
736
|
|
|
605
|
-
if
|
|
737
|
+
if self.has_tag and items.plot_tag:
|
|
606
738
|
m = theme.get_margin("plot_tag").fig
|
|
607
739
|
self.plot_tag_margin_bottom = m.b
|
|
608
740
|
self.plot_tag = calc.height(items.plot_tag)
|
|
@@ -615,7 +747,7 @@ class bottom_spaces(_side_spaces):
|
|
|
615
747
|
self.plot_caption_margin_top = m.t * F
|
|
616
748
|
|
|
617
749
|
if items.legends and items.legends.bottom:
|
|
618
|
-
self.legend = self.
|
|
750
|
+
self.legend = self.legend_height
|
|
619
751
|
self.legend_box_spacing = theme.getp("legend_box_spacing") * F
|
|
620
752
|
|
|
621
753
|
if items.axis_title_x:
|
|
@@ -640,13 +772,6 @@ class bottom_spaces(_side_spaces):
|
|
|
640
772
|
if adjustment > 0:
|
|
641
773
|
self.plot_margin += adjustment
|
|
642
774
|
|
|
643
|
-
@cached_property
|
|
644
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
645
|
-
if not (self.items.legends and self.items.legends.bottom):
|
|
646
|
-
return (0, 0)
|
|
647
|
-
|
|
648
|
-
return self.items.calc.size(self.items.legends.bottom.box)
|
|
649
|
-
|
|
650
775
|
@property
|
|
651
776
|
def offset(self) -> float:
|
|
652
777
|
"""
|
|
@@ -679,18 +804,18 @@ class bottom_spaces(_side_spaces):
|
|
|
679
804
|
return self.to_figure_space(self.sum_incl(item))
|
|
680
805
|
|
|
681
806
|
@property
|
|
682
|
-
def
|
|
807
|
+
def panel_bottom_relative(self):
|
|
683
808
|
"""
|
|
684
809
|
Bottom (relative to the gridspec) of the panels in figure dimensions
|
|
685
810
|
"""
|
|
686
811
|
return self.total
|
|
687
812
|
|
|
688
813
|
@property
|
|
689
|
-
def
|
|
814
|
+
def panel_bottom(self):
|
|
690
815
|
"""
|
|
691
816
|
Bottom of the panels in figure space
|
|
692
817
|
"""
|
|
693
|
-
return self.to_figure_space(self.
|
|
818
|
+
return self.to_figure_space(self.panel_bottom_relative)
|
|
694
819
|
|
|
695
820
|
@property
|
|
696
821
|
def plot_bottom(self):
|
|
@@ -699,6 +824,17 @@ class bottom_spaces(_side_spaces):
|
|
|
699
824
|
"""
|
|
700
825
|
return self.y1("legend")
|
|
701
826
|
|
|
827
|
+
@property
|
|
828
|
+
def tag_height(self):
|
|
829
|
+
"""
|
|
830
|
+
The height of the tag including the margins
|
|
831
|
+
"""
|
|
832
|
+
return (
|
|
833
|
+
self.plot_tag_margin_bottom
|
|
834
|
+
+ self.plot_tag
|
|
835
|
+
+ self.plot_tag_margin_top
|
|
836
|
+
)
|
|
837
|
+
|
|
702
838
|
|
|
703
839
|
@dataclass
|
|
704
840
|
class LayoutSpaces:
|
|
@@ -802,14 +938,76 @@ class LayoutSpaces:
|
|
|
802
938
|
"""
|
|
803
939
|
Width [figure dimensions] of panels
|
|
804
940
|
"""
|
|
805
|
-
return self.r.
|
|
941
|
+
return self.r.panel_right - self.l.panel_left
|
|
806
942
|
|
|
807
943
|
@property
|
|
808
944
|
def panel_height(self) -> float:
|
|
809
945
|
"""
|
|
810
946
|
Height [figure dimensions] of panels
|
|
811
947
|
"""
|
|
812
|
-
return self.t.
|
|
948
|
+
return self.t.panel_top - self.b.panel_bottom
|
|
949
|
+
|
|
950
|
+
@property
|
|
951
|
+
def tag_width(self) -> float:
|
|
952
|
+
"""
|
|
953
|
+
Width [figure dimensions] of space taken up by the tag
|
|
954
|
+
"""
|
|
955
|
+
# Atleast one of these is zero
|
|
956
|
+
return max(self.l.tag_width, self.r.tag_width)
|
|
957
|
+
|
|
958
|
+
@property
|
|
959
|
+
def tag_height(self) -> float:
|
|
960
|
+
"""
|
|
961
|
+
Height [figure dimensions] of space taken up by the tag
|
|
962
|
+
"""
|
|
963
|
+
# Atleast one of these is zero
|
|
964
|
+
return max(self.t.tag_height, self.b.tag_height)
|
|
965
|
+
|
|
966
|
+
@property
|
|
967
|
+
def left_tag_width(self) -> float:
|
|
968
|
+
"""
|
|
969
|
+
Width [figure dimensions] of space taken up by a left tag
|
|
970
|
+
"""
|
|
971
|
+
return self.l.tag_width
|
|
972
|
+
|
|
973
|
+
@property
|
|
974
|
+
def right_tag_width(self) -> float:
|
|
975
|
+
"""
|
|
976
|
+
Width [figure dimensions] of space taken up by a right tag
|
|
977
|
+
"""
|
|
978
|
+
return self.r.tag_width
|
|
979
|
+
|
|
980
|
+
@property
|
|
981
|
+
def top_tag_height(self) -> float:
|
|
982
|
+
"""
|
|
983
|
+
Width [figure dimensions] of space taken up by a top tag
|
|
984
|
+
"""
|
|
985
|
+
return self.t.tag_height
|
|
986
|
+
|
|
987
|
+
@property
|
|
988
|
+
def bottom_tag_height(self) -> float:
|
|
989
|
+
"""
|
|
990
|
+
Height [figure dimensions] of space taken up by a bottom tag
|
|
991
|
+
"""
|
|
992
|
+
return self.b.tag_height
|
|
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
|
|
813
1011
|
|
|
814
1012
|
def increase_horizontal_plot_margin(self, dw: float):
|
|
815
1013
|
"""
|
|
@@ -847,8 +1045,8 @@ class LayoutSpaces:
|
|
|
847
1045
|
|
|
848
1046
|
This is the area in which the panels are drawn.
|
|
849
1047
|
"""
|
|
850
|
-
x1, x2 = self.l.
|
|
851
|
-
y1, y2 = self.b.
|
|
1048
|
+
x1, x2 = self.l.panel_left, self.r.panel_right
|
|
1049
|
+
y1, y2 = self.b.panel_bottom, self.t.panel_top
|
|
852
1050
|
return ((x1, y1), (x2, y2))
|
|
853
1051
|
|
|
854
1052
|
def _calculate_panel_spacing(self) -> GridSpecParams:
|
|
@@ -869,10 +1067,10 @@ class LayoutSpaces:
|
|
|
869
1067
|
raise TypeError(f"Unknown type of facet: {type(self.plot.facet)}")
|
|
870
1068
|
|
|
871
1069
|
return GridSpecParams(
|
|
872
|
-
self.l.
|
|
873
|
-
self.r.
|
|
874
|
-
self.t.
|
|
875
|
-
self.b.
|
|
1070
|
+
self.l.panel_left_relative,
|
|
1071
|
+
self.r.panel_right_relative,
|
|
1072
|
+
self.t.panel_top_relative,
|
|
1073
|
+
self.b.panel_bottom_relative,
|
|
876
1074
|
wspace,
|
|
877
1075
|
hspace,
|
|
878
1076
|
)
|
|
@@ -886,6 +1084,9 @@ class LayoutSpaces:
|
|
|
886
1084
|
ncol = self.plot.facet.ncol
|
|
887
1085
|
nrow = self.plot.facet.nrow
|
|
888
1086
|
|
|
1087
|
+
left, right = self.l.panel_left, self.r.panel_right
|
|
1088
|
+
top, bottom = self.t.panel_top, self.b.panel_bottom
|
|
1089
|
+
|
|
889
1090
|
# Both spacings are specified as fractions of the figure width
|
|
890
1091
|
# Multiply the vertical by (W/H) so that the gullies along both
|
|
891
1092
|
# directions are equally spaced.
|
|
@@ -893,8 +1094,8 @@ class LayoutSpaces:
|
|
|
893
1094
|
self.sh = theme.getp("panel_spacing_y") * self.W / self.H
|
|
894
1095
|
|
|
895
1096
|
# width and height of axes as fraction of figure width & height
|
|
896
|
-
self.w = ((
|
|
897
|
-
self.h = ((
|
|
1097
|
+
self.w = ((right - left) - self.sw * (ncol - 1)) / ncol
|
|
1098
|
+
self.h = ((top - bottom) - self.sh * (nrow - 1)) / nrow
|
|
898
1099
|
|
|
899
1100
|
# Spacing as fraction of axes width & height
|
|
900
1101
|
wspace = self.sw / self.w
|
|
@@ -911,6 +1112,9 @@ class LayoutSpaces:
|
|
|
911
1112
|
ncol = facet.ncol
|
|
912
1113
|
nrow = facet.nrow
|
|
913
1114
|
|
|
1115
|
+
left, right = self.l.panel_left, self.r.panel_right
|
|
1116
|
+
top, bottom = self.t.panel_top, self.b.panel_bottom
|
|
1117
|
+
|
|
914
1118
|
# Both spacings are specified as fractions of the figure width
|
|
915
1119
|
self.sw = theme.getp("panel_spacing_x")
|
|
916
1120
|
self.sh = theme.getp("panel_spacing_y") * self.W / self.H
|
|
@@ -927,7 +1131,7 @@ class LayoutSpaces:
|
|
|
927
1131
|
# Only interested in the proportion of the strip that
|
|
928
1132
|
# does not overlap with the panel
|
|
929
1133
|
if strip_align_x > -1:
|
|
930
|
-
self.sh += self.t.
|
|
1134
|
+
self.sh += self.t.strip_text_x_extra_height * (1 + strip_align_x)
|
|
931
1135
|
|
|
932
1136
|
if facet.free["x"]:
|
|
933
1137
|
self.sh += self.items.axis_text_x_max_height_at(
|
|
@@ -939,8 +1143,8 @@ class LayoutSpaces:
|
|
|
939
1143
|
) + self.items.axis_ticks_y_max_width_at("all")
|
|
940
1144
|
|
|
941
1145
|
# width and height of axes as fraction of figure width & height
|
|
942
|
-
self.w = ((
|
|
943
|
-
self.h = ((
|
|
1146
|
+
self.w = ((right - left) - self.sw * (ncol - 1)) / ncol
|
|
1147
|
+
self.h = ((top - bottom) - self.sh * (nrow - 1)) / nrow
|
|
944
1148
|
|
|
945
1149
|
# Spacing as fraction of axes width & height
|
|
946
1150
|
wspace = self.sw / self.w
|
|
@@ -951,8 +1155,8 @@ class LayoutSpaces:
|
|
|
951
1155
|
"""
|
|
952
1156
|
Calculate spacing parts for facet_null
|
|
953
1157
|
"""
|
|
954
|
-
self.w = self.r.
|
|
955
|
-
self.h = self.t.
|
|
1158
|
+
self.w = self.r.panel_right - self.l.panel_left
|
|
1159
|
+
self.h = self.t.panel_top - self.b.panel_bottom
|
|
956
1160
|
self.sw = 0
|
|
957
1161
|
self.sh = 0
|
|
958
1162
|
return 0, 0
|