plotnine 0.15.0.dev2__py3-none-any.whl → 0.15.1__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 +128 -83
- plotnine/_mpl/layout_manager/_layout_tree.py +761 -310
- plotnine/_mpl/layout_manager/_spaces.py +320 -103
- 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 +21 -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 +11 -2
- 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 +8 -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 +28 -8
- 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 +85 -0
- plotnine/mapping/aes.py +91 -72
- plotnine/mapping/evaluation.py +7 -65
- 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 +45 -14
- 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 +110 -32
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/METADATA +13 -6
- plotnine-0.15.1.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.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.dev2.dist-info/RECORD +0 -214
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.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,15 +291,66 @@ 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
|
+
"""
|
|
351
|
+
axis_text_y_margin_left: float = 0
|
|
225
352
|
axis_text_y: float = 0
|
|
353
|
+
axis_text_y_margin_right: float = 0
|
|
226
354
|
axis_ticks_y: float = 0
|
|
227
355
|
|
|
228
356
|
def _calculate(self):
|
|
@@ -230,22 +358,16 @@ class left_spaces(_side_spaces):
|
|
|
230
358
|
calc = self.items.calc
|
|
231
359
|
items = self.items
|
|
232
360
|
|
|
233
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
234
|
-
# So we make space for it, including any margins it may have.
|
|
235
|
-
plot_tag_in_layout = theme.getp(
|
|
236
|
-
"plot_tag_location"
|
|
237
|
-
) == "margin" and "left" in theme.getp("plot_tag_position")
|
|
238
|
-
|
|
239
361
|
self.plot_margin = theme.getp("plot_margin_left")
|
|
240
362
|
|
|
241
|
-
if
|
|
363
|
+
if self.has_tag and items.plot_tag:
|
|
242
364
|
m = theme.get_margin("plot_tag").fig
|
|
243
365
|
self.plot_tag_margin_left = m.l
|
|
244
366
|
self.plot_tag = calc.width(items.plot_tag)
|
|
245
367
|
self.plot_tag_margin_right = m.r
|
|
246
368
|
|
|
247
369
|
if items.legends and items.legends.left:
|
|
248
|
-
self.legend = self.
|
|
370
|
+
self.legend = self.legend_width
|
|
249
371
|
self.legend_box_spacing = theme.getp("legend_box_spacing")
|
|
250
372
|
|
|
251
373
|
if items.axis_title_y:
|
|
@@ -256,6 +378,11 @@ class left_spaces(_side_spaces):
|
|
|
256
378
|
|
|
257
379
|
# Account for the space consumed by the axis
|
|
258
380
|
self.axis_text_y = items.axis_text_y_max_width_at("first_col")
|
|
381
|
+
if self.axis_text_y:
|
|
382
|
+
m = theme.get_margin("axis_text_y").fig
|
|
383
|
+
self.axis_text_y_margin_left = m.l
|
|
384
|
+
self.axis_text_y_margin_right = m.r
|
|
385
|
+
|
|
259
386
|
self.axis_ticks_y = items.axis_ticks_y_max_width_at("first_col")
|
|
260
387
|
|
|
261
388
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
@@ -266,13 +393,6 @@ class left_spaces(_side_spaces):
|
|
|
266
393
|
if adjustment > 0:
|
|
267
394
|
self.plot_margin += adjustment
|
|
268
395
|
|
|
269
|
-
@cached_property
|
|
270
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
271
|
-
if not (self.items.legends and self.items.legends.left):
|
|
272
|
-
return (0, 0)
|
|
273
|
-
|
|
274
|
-
return self.items.calc.size(self.items.legends.left.box)
|
|
275
|
-
|
|
276
396
|
@property
|
|
277
397
|
def offset(self) -> float:
|
|
278
398
|
"""
|
|
@@ -302,18 +422,18 @@ class left_spaces(_side_spaces):
|
|
|
302
422
|
return self.to_figure_space(self.sum_incl(item))
|
|
303
423
|
|
|
304
424
|
@property
|
|
305
|
-
def
|
|
425
|
+
def panel_left_relative(self):
|
|
306
426
|
"""
|
|
307
427
|
Left (relative to the gridspec) of the panels in figure dimensions
|
|
308
428
|
"""
|
|
309
429
|
return self.total
|
|
310
430
|
|
|
311
431
|
@property
|
|
312
|
-
def
|
|
432
|
+
def panel_left(self):
|
|
313
433
|
"""
|
|
314
434
|
Left of the panels in figure space
|
|
315
435
|
"""
|
|
316
|
-
return self.to_figure_space(self.
|
|
436
|
+
return self.to_figure_space(self.panel_left_relative)
|
|
317
437
|
|
|
318
438
|
@property
|
|
319
439
|
def plot_left(self):
|
|
@@ -322,6 +442,17 @@ class left_spaces(_side_spaces):
|
|
|
322
442
|
"""
|
|
323
443
|
return self.x1("legend")
|
|
324
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
|
+
|
|
325
456
|
|
|
326
457
|
@dataclass
|
|
327
458
|
class right_spaces(_side_spaces):
|
|
@@ -332,36 +463,33 @@ class right_spaces(_side_spaces):
|
|
|
332
463
|
"""
|
|
333
464
|
|
|
334
465
|
plot_margin: float = 0
|
|
466
|
+
tag_alignment: float = 0
|
|
335
467
|
plot_tag_margin_right: float = 0
|
|
336
468
|
plot_tag: float = 0
|
|
337
469
|
plot_tag_margin_left: float = 0
|
|
470
|
+
margin_alignment: float = 0
|
|
338
471
|
legend: float = 0
|
|
339
472
|
legend_box_spacing: float = 0
|
|
340
|
-
|
|
473
|
+
strip_text_y_extra_width: float = 0
|
|
341
474
|
|
|
342
475
|
def _calculate(self):
|
|
343
476
|
items = self.items
|
|
344
477
|
theme = self.items.plot.theme
|
|
345
478
|
calc = self.items.calc
|
|
346
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
347
|
-
# So we make space for it, including any margins it may have.
|
|
348
|
-
plot_tag_in_layout = theme.getp(
|
|
349
|
-
"plot_tag_location"
|
|
350
|
-
) == "margin" and "right" in theme.getp("plot_tag_position")
|
|
351
479
|
|
|
352
480
|
self.plot_margin = theme.getp("plot_margin_right")
|
|
353
481
|
|
|
354
|
-
if
|
|
482
|
+
if self.has_tag and items.plot_tag:
|
|
355
483
|
m = theme.get_margin("plot_tag").fig
|
|
356
484
|
self.plot_tag_margin_right = m.r
|
|
357
485
|
self.plot_tag = calc.width(items.plot_tag)
|
|
358
486
|
self.plot_tag_margin_left = m.l
|
|
359
487
|
|
|
360
488
|
if items.legends and items.legends.right:
|
|
361
|
-
self.legend = self.
|
|
489
|
+
self.legend = self.legend_width
|
|
362
490
|
self.legend_box_spacing = theme.getp("legend_box_spacing")
|
|
363
491
|
|
|
364
|
-
self.
|
|
492
|
+
self.strip_text_y_extra_width = items.strip_text_y_extra_width("right")
|
|
365
493
|
|
|
366
494
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
367
495
|
# beyond the axes
|
|
@@ -371,13 +499,6 @@ class right_spaces(_side_spaces):
|
|
|
371
499
|
if adjustment > 0:
|
|
372
500
|
self.plot_margin += adjustment
|
|
373
501
|
|
|
374
|
-
@cached_property
|
|
375
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
376
|
-
if not (self.items.legends and self.items.legends.right):
|
|
377
|
-
return (0, 0)
|
|
378
|
-
|
|
379
|
-
return self.items.calc.size(self.items.legends.right.box)
|
|
380
|
-
|
|
381
502
|
@property
|
|
382
503
|
def offset(self):
|
|
383
504
|
"""
|
|
@@ -407,18 +528,18 @@ class right_spaces(_side_spaces):
|
|
|
407
528
|
return self.to_figure_space(1 - self.sum_upto(item))
|
|
408
529
|
|
|
409
530
|
@property
|
|
410
|
-
def
|
|
531
|
+
def panel_right_relative(self):
|
|
411
532
|
"""
|
|
412
533
|
Right (relative to the gridspec) of the panels in figure dimensions
|
|
413
534
|
"""
|
|
414
535
|
return 1 - self.total
|
|
415
536
|
|
|
416
537
|
@property
|
|
417
|
-
def
|
|
538
|
+
def panel_right(self):
|
|
418
539
|
"""
|
|
419
540
|
Right of the panels in figure space
|
|
420
541
|
"""
|
|
421
|
-
return self.to_figure_space(self.
|
|
542
|
+
return self.to_figure_space(self.panel_right_relative)
|
|
422
543
|
|
|
423
544
|
@property
|
|
424
545
|
def plot_right(self):
|
|
@@ -427,6 +548,17 @@ class right_spaces(_side_spaces):
|
|
|
427
548
|
"""
|
|
428
549
|
return self.x2("legend")
|
|
429
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
|
+
|
|
430
562
|
|
|
431
563
|
@dataclass
|
|
432
564
|
class top_spaces(_side_spaces):
|
|
@@ -437,9 +569,11 @@ class top_spaces(_side_spaces):
|
|
|
437
569
|
"""
|
|
438
570
|
|
|
439
571
|
plot_margin: float = 0
|
|
572
|
+
tag_alignment: float = 0
|
|
440
573
|
plot_tag_margin_top: float = 0
|
|
441
574
|
plot_tag: float = 0
|
|
442
575
|
plot_tag_margin_bottom: float = 0
|
|
576
|
+
margin_alignment: float = 0
|
|
443
577
|
plot_title_margin_top: float = 0
|
|
444
578
|
plot_title: float = 0
|
|
445
579
|
plot_title_margin_bottom: float = 0
|
|
@@ -448,7 +582,7 @@ class top_spaces(_side_spaces):
|
|
|
448
582
|
plot_subtitle_margin_bottom: float = 0
|
|
449
583
|
legend: float = 0
|
|
450
584
|
legend_box_spacing: float = 0
|
|
451
|
-
|
|
585
|
+
strip_text_x_extra_height: float = 0
|
|
452
586
|
|
|
453
587
|
def _calculate(self):
|
|
454
588
|
items = self.items
|
|
@@ -456,15 +590,10 @@ class top_spaces(_side_spaces):
|
|
|
456
590
|
calc = self.items.calc
|
|
457
591
|
W, H = theme.getp("figure_size")
|
|
458
592
|
F = W / H
|
|
459
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
460
|
-
# So we make space for it, including any margins it may have.
|
|
461
|
-
plot_tag_in_layout = theme.getp(
|
|
462
|
-
"plot_tag_location"
|
|
463
|
-
) == "margin" and "top" in theme.getp("plot_tag_position")
|
|
464
593
|
|
|
465
594
|
self.plot_margin = theme.getp("plot_margin_top") * F
|
|
466
595
|
|
|
467
|
-
if
|
|
596
|
+
if self.has_tag and items.plot_tag:
|
|
468
597
|
m = theme.get_margin("plot_tag").fig
|
|
469
598
|
self.plot_tag_margin_top = m.t
|
|
470
599
|
self.plot_tag = calc.height(items.plot_tag)
|
|
@@ -483,10 +612,10 @@ class top_spaces(_side_spaces):
|
|
|
483
612
|
self.plot_subtitle_margin_bottom = m.b * F
|
|
484
613
|
|
|
485
614
|
if items.legends and items.legends.top:
|
|
486
|
-
self.legend = self.
|
|
615
|
+
self.legend = self.legend_height
|
|
487
616
|
self.legend_box_spacing = theme.getp("legend_box_spacing") * F
|
|
488
617
|
|
|
489
|
-
self.
|
|
618
|
+
self.strip_text_x_extra_height = items.strip_text_x_extra_height("top")
|
|
490
619
|
|
|
491
620
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
492
621
|
# beyond the axes
|
|
@@ -496,13 +625,6 @@ class top_spaces(_side_spaces):
|
|
|
496
625
|
if adjustment > 0:
|
|
497
626
|
self.plot_margin += adjustment
|
|
498
627
|
|
|
499
|
-
@cached_property
|
|
500
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
501
|
-
if not (self.items.legends and self.items.legends.top):
|
|
502
|
-
return (0, 0)
|
|
503
|
-
|
|
504
|
-
return self.items.calc.size(self.items.legends.top.box)
|
|
505
|
-
|
|
506
628
|
@property
|
|
507
629
|
def offset(self) -> float:
|
|
508
630
|
"""
|
|
@@ -535,18 +657,18 @@ class top_spaces(_side_spaces):
|
|
|
535
657
|
return self.to_figure_space(1 - self.sum_upto(item))
|
|
536
658
|
|
|
537
659
|
@property
|
|
538
|
-
def
|
|
660
|
+
def panel_top_relative(self):
|
|
539
661
|
"""
|
|
540
662
|
Top (relative to the gridspec) of the panels in figure dimensions
|
|
541
663
|
"""
|
|
542
664
|
return 1 - self.total
|
|
543
665
|
|
|
544
666
|
@property
|
|
545
|
-
def
|
|
667
|
+
def panel_top(self):
|
|
546
668
|
"""
|
|
547
669
|
Top of the panels in figure space
|
|
548
670
|
"""
|
|
549
|
-
return self.to_figure_space(self.
|
|
671
|
+
return self.to_figure_space(self.panel_top_relative)
|
|
550
672
|
|
|
551
673
|
@property
|
|
552
674
|
def plot_top(self):
|
|
@@ -555,6 +677,17 @@ class top_spaces(_side_spaces):
|
|
|
555
677
|
"""
|
|
556
678
|
return self.y2("legend")
|
|
557
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
|
+
|
|
558
691
|
|
|
559
692
|
@dataclass
|
|
560
693
|
class bottom_spaces(_side_spaces):
|
|
@@ -565,9 +698,11 @@ class bottom_spaces(_side_spaces):
|
|
|
565
698
|
"""
|
|
566
699
|
|
|
567
700
|
plot_margin: float = 0
|
|
701
|
+
tag_alignment: float = 0
|
|
568
702
|
plot_tag_margin_bottom: float = 0
|
|
569
703
|
plot_tag: float = 0
|
|
570
704
|
plot_tag_margin_top: float = 0
|
|
705
|
+
margin_alignment: float = 0
|
|
571
706
|
plot_caption_margin_bottom: float = 0
|
|
572
707
|
plot_caption: float = 0
|
|
573
708
|
plot_caption_margin_top: float = 0
|
|
@@ -576,7 +711,18 @@ class bottom_spaces(_side_spaces):
|
|
|
576
711
|
axis_title_x_margin_bottom: float = 0
|
|
577
712
|
axis_title_x: float = 0
|
|
578
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
|
+
"""
|
|
723
|
+
axis_text_x_margin_bottom: float = 0
|
|
579
724
|
axis_text_x: float = 0
|
|
725
|
+
axis_text_x_margin_top: float = 0
|
|
580
726
|
axis_ticks_x: float = 0
|
|
581
727
|
|
|
582
728
|
def _calculate(self):
|
|
@@ -585,15 +731,10 @@ class bottom_spaces(_side_spaces):
|
|
|
585
731
|
calc = self.items.calc
|
|
586
732
|
W, H = theme.getp("figure_size")
|
|
587
733
|
F = W / H
|
|
588
|
-
# If the plot_tag is in the margin, it is included in the layout.
|
|
589
|
-
# So we make space for it, including any margins it may have.
|
|
590
|
-
plot_tag_in_layout = theme.getp(
|
|
591
|
-
"plot_tag_location"
|
|
592
|
-
) == "margin" and "bottom" in theme.getp("plot_tag_position")
|
|
593
734
|
|
|
594
735
|
self.plot_margin = theme.getp("plot_margin_bottom") * F
|
|
595
736
|
|
|
596
|
-
if
|
|
737
|
+
if self.has_tag and items.plot_tag:
|
|
597
738
|
m = theme.get_margin("plot_tag").fig
|
|
598
739
|
self.plot_tag_margin_bottom = m.b
|
|
599
740
|
self.plot_tag = calc.height(items.plot_tag)
|
|
@@ -606,7 +747,7 @@ class bottom_spaces(_side_spaces):
|
|
|
606
747
|
self.plot_caption_margin_top = m.t * F
|
|
607
748
|
|
|
608
749
|
if items.legends and items.legends.bottom:
|
|
609
|
-
self.legend = self.
|
|
750
|
+
self.legend = self.legend_height
|
|
610
751
|
self.legend_box_spacing = theme.getp("legend_box_spacing") * F
|
|
611
752
|
|
|
612
753
|
if items.axis_title_x:
|
|
@@ -616,8 +757,12 @@ class bottom_spaces(_side_spaces):
|
|
|
616
757
|
self.axis_title_x_margin_top = m.t * F
|
|
617
758
|
|
|
618
759
|
# Account for the space consumed by the axis
|
|
619
|
-
self.axis_ticks_x = items.axis_ticks_x_max_height_at("last_row")
|
|
620
760
|
self.axis_text_x = items.axis_text_x_max_height_at("last_row")
|
|
761
|
+
if self.axis_text_x:
|
|
762
|
+
m = theme.get_margin("axis_text_x").fig
|
|
763
|
+
self.axis_text_x_margin_bottom = m.b
|
|
764
|
+
self.axis_text_x_margin_top = m.t
|
|
765
|
+
self.axis_ticks_x = items.axis_ticks_x_max_height_at("last_row")
|
|
621
766
|
|
|
622
767
|
# Adjust plot_margin to make room for ylabels that protude well
|
|
623
768
|
# beyond the axes
|
|
@@ -627,13 +772,6 @@ class bottom_spaces(_side_spaces):
|
|
|
627
772
|
if adjustment > 0:
|
|
628
773
|
self.plot_margin += adjustment
|
|
629
774
|
|
|
630
|
-
@cached_property
|
|
631
|
-
def _legend_size(self) -> tuple[float, float]:
|
|
632
|
-
if not (self.items.legends and self.items.legends.bottom):
|
|
633
|
-
return (0, 0)
|
|
634
|
-
|
|
635
|
-
return self.items.calc.size(self.items.legends.bottom.box)
|
|
636
|
-
|
|
637
775
|
@property
|
|
638
776
|
def offset(self) -> float:
|
|
639
777
|
"""
|
|
@@ -666,18 +804,18 @@ class bottom_spaces(_side_spaces):
|
|
|
666
804
|
return self.to_figure_space(self.sum_incl(item))
|
|
667
805
|
|
|
668
806
|
@property
|
|
669
|
-
def
|
|
807
|
+
def panel_bottom_relative(self):
|
|
670
808
|
"""
|
|
671
809
|
Bottom (relative to the gridspec) of the panels in figure dimensions
|
|
672
810
|
"""
|
|
673
811
|
return self.total
|
|
674
812
|
|
|
675
813
|
@property
|
|
676
|
-
def
|
|
814
|
+
def panel_bottom(self):
|
|
677
815
|
"""
|
|
678
816
|
Bottom of the panels in figure space
|
|
679
817
|
"""
|
|
680
|
-
return self.to_figure_space(self.
|
|
818
|
+
return self.to_figure_space(self.panel_bottom_relative)
|
|
681
819
|
|
|
682
820
|
@property
|
|
683
821
|
def plot_bottom(self):
|
|
@@ -686,6 +824,17 @@ class bottom_spaces(_side_spaces):
|
|
|
686
824
|
"""
|
|
687
825
|
return self.y1("legend")
|
|
688
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
|
+
|
|
689
838
|
|
|
690
839
|
@dataclass
|
|
691
840
|
class LayoutSpaces:
|
|
@@ -736,7 +885,7 @@ class LayoutSpaces:
|
|
|
736
885
|
sw: float = field(init=False, default=0)
|
|
737
886
|
"""vertical spacing btn panels w.r.t figure"""
|
|
738
887
|
|
|
739
|
-
gsparams: GridSpecParams = field(init=False)
|
|
888
|
+
gsparams: GridSpecParams = field(init=False, repr=False)
|
|
740
889
|
"""Grid spacing btn panels w.r.t figure"""
|
|
741
890
|
|
|
742
891
|
def __post_init__(self):
|
|
@@ -789,14 +938,76 @@ class LayoutSpaces:
|
|
|
789
938
|
"""
|
|
790
939
|
Width [figure dimensions] of panels
|
|
791
940
|
"""
|
|
792
|
-
return self.r.
|
|
941
|
+
return self.r.panel_right - self.l.panel_left
|
|
793
942
|
|
|
794
943
|
@property
|
|
795
944
|
def panel_height(self) -> float:
|
|
796
945
|
"""
|
|
797
946
|
Height [figure dimensions] of panels
|
|
798
947
|
"""
|
|
799
|
-
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
|
|
800
1011
|
|
|
801
1012
|
def increase_horizontal_plot_margin(self, dw: float):
|
|
802
1013
|
"""
|
|
@@ -834,8 +1045,8 @@ class LayoutSpaces:
|
|
|
834
1045
|
|
|
835
1046
|
This is the area in which the panels are drawn.
|
|
836
1047
|
"""
|
|
837
|
-
x1, x2 = self.l.
|
|
838
|
-
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
|
|
839
1050
|
return ((x1, y1), (x2, y2))
|
|
840
1051
|
|
|
841
1052
|
def _calculate_panel_spacing(self) -> GridSpecParams:
|
|
@@ -856,10 +1067,10 @@ class LayoutSpaces:
|
|
|
856
1067
|
raise TypeError(f"Unknown type of facet: {type(self.plot.facet)}")
|
|
857
1068
|
|
|
858
1069
|
return GridSpecParams(
|
|
859
|
-
self.l.
|
|
860
|
-
self.r.
|
|
861
|
-
self.t.
|
|
862
|
-
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,
|
|
863
1074
|
wspace,
|
|
864
1075
|
hspace,
|
|
865
1076
|
)
|
|
@@ -873,6 +1084,9 @@ class LayoutSpaces:
|
|
|
873
1084
|
ncol = self.plot.facet.ncol
|
|
874
1085
|
nrow = self.plot.facet.nrow
|
|
875
1086
|
|
|
1087
|
+
left, right = self.l.panel_left, self.r.panel_right
|
|
1088
|
+
top, bottom = self.t.panel_top, self.b.panel_bottom
|
|
1089
|
+
|
|
876
1090
|
# Both spacings are specified as fractions of the figure width
|
|
877
1091
|
# Multiply the vertical by (W/H) so that the gullies along both
|
|
878
1092
|
# directions are equally spaced.
|
|
@@ -880,8 +1094,8 @@ class LayoutSpaces:
|
|
|
880
1094
|
self.sh = theme.getp("panel_spacing_y") * self.W / self.H
|
|
881
1095
|
|
|
882
1096
|
# width and height of axes as fraction of figure width & height
|
|
883
|
-
self.w = ((
|
|
884
|
-
self.h = ((
|
|
1097
|
+
self.w = ((right - left) - self.sw * (ncol - 1)) / ncol
|
|
1098
|
+
self.h = ((top - bottom) - self.sh * (nrow - 1)) / nrow
|
|
885
1099
|
|
|
886
1100
|
# Spacing as fraction of axes width & height
|
|
887
1101
|
wspace = self.sw / self.w
|
|
@@ -898,6 +1112,9 @@ class LayoutSpaces:
|
|
|
898
1112
|
ncol = facet.ncol
|
|
899
1113
|
nrow = facet.nrow
|
|
900
1114
|
|
|
1115
|
+
left, right = self.l.panel_left, self.r.panel_right
|
|
1116
|
+
top, bottom = self.t.panel_top, self.b.panel_bottom
|
|
1117
|
+
|
|
901
1118
|
# Both spacings are specified as fractions of the figure width
|
|
902
1119
|
self.sw = theme.getp("panel_spacing_x")
|
|
903
1120
|
self.sh = theme.getp("panel_spacing_y") * self.W / self.H
|
|
@@ -914,7 +1131,7 @@ class LayoutSpaces:
|
|
|
914
1131
|
# Only interested in the proportion of the strip that
|
|
915
1132
|
# does not overlap with the panel
|
|
916
1133
|
if strip_align_x > -1:
|
|
917
|
-
self.sh += self.t.
|
|
1134
|
+
self.sh += self.t.strip_text_x_extra_height * (1 + strip_align_x)
|
|
918
1135
|
|
|
919
1136
|
if facet.free["x"]:
|
|
920
1137
|
self.sh += self.items.axis_text_x_max_height_at(
|
|
@@ -926,8 +1143,8 @@ class LayoutSpaces:
|
|
|
926
1143
|
) + self.items.axis_ticks_y_max_width_at("all")
|
|
927
1144
|
|
|
928
1145
|
# width and height of axes as fraction of figure width & height
|
|
929
|
-
self.w = ((
|
|
930
|
-
self.h = ((
|
|
1146
|
+
self.w = ((right - left) - self.sw * (ncol - 1)) / ncol
|
|
1147
|
+
self.h = ((top - bottom) - self.sh * (nrow - 1)) / nrow
|
|
931
1148
|
|
|
932
1149
|
# Spacing as fraction of axes width & height
|
|
933
1150
|
wspace = self.sw / self.w
|
|
@@ -938,8 +1155,8 @@ class LayoutSpaces:
|
|
|
938
1155
|
"""
|
|
939
1156
|
Calculate spacing parts for facet_null
|
|
940
1157
|
"""
|
|
941
|
-
self.w = self.r.
|
|
942
|
-
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
|
|
943
1160
|
self.sw = 0
|
|
944
1161
|
self.sh = 0
|
|
945
1162
|
return 0, 0
|