plotnine 0.14.5__py3-none-any.whl → 0.15.0a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. plotnine/__init__.py +31 -37
  2. plotnine/_mpl/gridspec.py +265 -0
  3. plotnine/_mpl/layout_manager/__init__.py +6 -0
  4. plotnine/_mpl/layout_manager/_engine.py +87 -0
  5. plotnine/_mpl/layout_manager/_layout_items.py +957 -0
  6. plotnine/_mpl/layout_manager/_layout_tree.py +905 -0
  7. plotnine/_mpl/layout_manager/_spaces.py +1154 -0
  8. plotnine/_mpl/patches.py +70 -34
  9. plotnine/_mpl/text.py +159 -37
  10. plotnine/_mpl/utils.py +78 -10
  11. plotnine/_utils/__init__.py +35 -9
  12. plotnine/_utils/dev.py +45 -27
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/animation.py +1 -1
  15. plotnine/coords/coord.py +3 -3
  16. plotnine/coords/coord_trans.py +1 -1
  17. plotnine/data/__init__.py +43 -8
  18. plotnine/data/anscombe-quartet.csv +45 -0
  19. plotnine/doctools.py +2 -2
  20. plotnine/facets/facet.py +34 -43
  21. plotnine/facets/facet_grid.py +14 -6
  22. plotnine/facets/facet_wrap.py +3 -5
  23. plotnine/facets/strips.py +20 -33
  24. plotnine/geoms/annotate.py +3 -3
  25. plotnine/geoms/annotation_logticks.py +2 -0
  26. plotnine/geoms/annotation_stripes.py +2 -0
  27. plotnine/geoms/geom.py +3 -3
  28. plotnine/geoms/geom_bar.py +10 -2
  29. plotnine/geoms/geom_col.py +6 -0
  30. plotnine/geoms/geom_crossbar.py +2 -3
  31. plotnine/geoms/geom_path.py +2 -2
  32. plotnine/geoms/geom_violin.py +24 -7
  33. plotnine/ggplot.py +95 -66
  34. plotnine/guides/guide.py +19 -20
  35. plotnine/guides/guide_colorbar.py +6 -6
  36. plotnine/guides/guide_legend.py +15 -16
  37. plotnine/guides/guides.py +8 -8
  38. plotnine/helpers.py +49 -0
  39. plotnine/iapi.py +33 -7
  40. plotnine/labels.py +8 -3
  41. plotnine/layer.py +4 -4
  42. plotnine/mapping/_env.py +2 -2
  43. plotnine/mapping/_eval_environment.py +85 -0
  44. plotnine/mapping/aes.py +14 -30
  45. plotnine/mapping/evaluation.py +7 -65
  46. plotnine/options.py +14 -7
  47. plotnine/plot_composition/__init__.py +10 -0
  48. plotnine/plot_composition/_compose.py +462 -0
  49. plotnine/plot_composition/_plotspec.py +50 -0
  50. plotnine/plot_composition/_spacer.py +32 -0
  51. plotnine/positions/position_dodge.py +1 -1
  52. plotnine/positions/position_dodge2.py +1 -1
  53. plotnine/positions/position_stack.py +1 -2
  54. plotnine/qplot.py +1 -2
  55. plotnine/scales/__init__.py +0 -6
  56. plotnine/scales/limits.py +7 -7
  57. plotnine/scales/scale.py +4 -4
  58. plotnine/scales/scale_continuous.py +2 -1
  59. plotnine/scales/scale_identity.py +10 -2
  60. plotnine/scales/scale_manual.py +6 -2
  61. plotnine/stats/binning.py +5 -2
  62. plotnine/stats/smoothers.py +3 -5
  63. plotnine/stats/stat.py +3 -3
  64. plotnine/stats/stat_bindot.py +1 -3
  65. plotnine/stats/stat_density.py +2 -2
  66. plotnine/stats/stat_qq_line.py +1 -1
  67. plotnine/stats/stat_sina.py +34 -1
  68. plotnine/themes/elements/__init__.py +3 -0
  69. plotnine/themes/elements/element_text.py +35 -24
  70. plotnine/themes/elements/margin.py +137 -61
  71. plotnine/themes/targets.py +3 -1
  72. plotnine/themes/theme.py +21 -7
  73. plotnine/themes/theme_538.py +0 -1
  74. plotnine/themes/theme_bw.py +0 -1
  75. plotnine/themes/theme_dark.py +0 -1
  76. plotnine/themes/theme_gray.py +32 -34
  77. plotnine/themes/theme_light.py +1 -1
  78. plotnine/themes/theme_matplotlib.py +28 -31
  79. plotnine/themes/theme_seaborn.py +36 -36
  80. plotnine/themes/theme_void.py +25 -27
  81. plotnine/themes/theme_xkcd.py +0 -1
  82. plotnine/themes/themeable.py +369 -169
  83. plotnine/typing.py +3 -3
  84. plotnine/watermark.py +3 -3
  85. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/METADATA +8 -5
  86. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/RECORD +89 -78
  87. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/WHEEL +1 -1
  88. plotnine/_mpl/_plot_side_space.py +0 -888
  89. plotnine/_mpl/_plotnine_tight_layout.py +0 -293
  90. plotnine/_mpl/layout_engine.py +0 -110
  91. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info/licenses}/LICENSE +0 -0
  92. {plotnine-0.14.5.dist-info → plotnine-0.15.0a2.dist-info}/top_level.txt +0 -0
@@ -1,888 +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
- from abc import ABC
15
- from dataclasses import dataclass, fields
16
- from functools import cached_property
17
- from itertools import chain
18
- from typing import TYPE_CHECKING, cast
19
-
20
- from matplotlib._tight_layout import get_subplotspec_list
21
-
22
- from ..facets import facet_grid, facet_null, facet_wrap
23
- from .utils import bbox_in_figure_space, tight_bbox_in_figure_space
24
-
25
- if TYPE_CHECKING:
26
- from dataclasses import Field
27
- from typing import (
28
- Generator,
29
- Iterator,
30
- Literal,
31
- Sequence,
32
- TypeAlias,
33
- )
34
-
35
- from matplotlib.artist import Artist
36
- from matplotlib.axes import Axes
37
- from matplotlib.axis import Tick
38
- from matplotlib.text import Text
39
-
40
- from .layout_engine import LayoutPack
41
-
42
- AxesLocation: TypeAlias = Literal[
43
- "all", "first_row", "last_row", "first_col", "last_col"
44
- ]
45
-
46
- # Note
47
- # Margins around the plot are specified in figure coordinates
48
- # We interpret that value to be a fraction of the width. So along
49
- # the vertical direction we multiply by W/H to get equal space
50
- # in both directions
51
-
52
-
53
- @dataclass
54
- class WHSpaceParts:
55
- """
56
- Width-Height Spaces
57
-
58
- We need these in one places for easy access
59
- """
60
-
61
- W: float # Figure width
62
- H: float # Figure height
63
- w: float # Axes width w.r.t figure in [0, 1]
64
- h: float # Axes height w.r.t figure in [0, 1]
65
- sw: float # horizontal spacing btn panels w.r.t figure
66
- sh: float # vertical spacing btn panels w.r.t figure
67
- wspace: float # mpl.subplotpars.wspace
68
- hspace: float # mpl.subplotpars.hspace
69
-
70
- @property
71
- def aspect_ratio(self) -> float:
72
- """
73
- Aspect ratio of the panels
74
- """
75
- return (self.h * self.H) / (self.w * self.W)
76
-
77
-
78
- @dataclass
79
- class _side_spaces(ABC):
80
- """
81
- Base class to for spaces
82
-
83
- A *_space class should track the size taken up by all the objects that
84
- may fall on that side of the panel. The same name may appear in multiple
85
- side classes (e.g. legend).
86
- """
87
-
88
- pack: LayoutPack
89
-
90
- def __post_init__(self):
91
- self._calculate()
92
-
93
- def _calculate(self):
94
- """
95
- Calculate the space taken up by each artist
96
- """
97
-
98
- @property
99
- def total(self) -> float:
100
- """
101
- Total space
102
- """
103
- return sum(getattr(self, f.name) for f in fields(self)[1:])
104
-
105
- def sum_upto(self, item: str) -> float:
106
- """
107
- Sum of space upto but not including item
108
-
109
- Sums starting at the edge of the figure i.e. the "plot_margin".
110
- """
111
-
112
- def _fields_upto(item: str) -> Generator[Field, None, None]:
113
- for f in fields(self)[1:]:
114
- if f.name == item:
115
- break
116
- yield f
117
-
118
- return sum(getattr(self, f.name) for f in _fields_upto(item))
119
-
120
- @cached_property
121
- def _legend_size(self) -> tuple[float, float]:
122
- """
123
- Return size of legend in figure coordinates
124
-
125
- We need this to accurately justify the legend by proportional
126
- values e.g. 0.2, instead of just left, right, top, bottom &
127
- center.
128
- """
129
- return (0, 0)
130
-
131
- @cached_property
132
- def _legend_width(self) -> float:
133
- """
134
- Return width of legend in figure coordinates
135
- """
136
- return self._legend_size[0]
137
-
138
- @cached_property
139
- def _legend_height(self) -> float:
140
- """
141
- Return height of legend in figure coordinates
142
- """
143
- return self._legend_size[1]
144
-
145
-
146
- @dataclass
147
- class left_spaces(_side_spaces):
148
- """
149
- Space in the figure for artists on the left of the panels
150
-
151
- Ordered from the edge of the figure and going inwards
152
- """
153
-
154
- plot_margin: float = 0
155
- legend: float = 0
156
- legend_box_spacing: float = 0
157
- axis_title_y: float = 0
158
- axis_title_y_margin_right: float = 0
159
- axis_ylabels: float = 0
160
- axis_yticks: float = 0
161
-
162
- def _calculate(self):
163
- theme = self.pack.theme
164
- pack = self.pack
165
-
166
- self.plot_margin = theme.getp("plot_margin_left")
167
- if pack.legends and pack.legends.left:
168
- self.legend = self._legend_width
169
- self.legend_box_spacing = theme.getp("legend_box_spacing")
170
-
171
- if pack.axis_title_y:
172
- self.axis_title_y_margin_right = theme.getp(
173
- ("axis_title_y", "margin")
174
- ).get_as("r", "fig")
175
- self.axis_title_y = bbox_in_figure_space(
176
- pack.axis_title_y, pack.figure, pack.renderer
177
- ).width
178
-
179
- # Account for the space consumed by the axis
180
- self.axis_ylabels = max_ylabels_width(pack, "first_col")
181
- self.axis_yticks = max_yticks_width(pack, "first_col")
182
-
183
- # Adjust plot_margin to make room for ylabels that protude well
184
- # beyond the axes
185
- # NOTE: This adjustment breaks down when the protrusion is large
186
- protrusion = max_xlabels_left_protrusion(pack)
187
- adjustment = protrusion - (self.total - self.plot_margin)
188
- if adjustment > 0:
189
- self.plot_margin += adjustment
190
-
191
- @cached_property
192
- def _legend_size(self) -> tuple[float, float]:
193
- if not (self.pack.legends and self.pack.legends.left):
194
- return (0, 0)
195
-
196
- bbox = bbox_in_figure_space(
197
- self.pack.legends.left.box, self.pack.figure, self.pack.renderer
198
- )
199
- return bbox.width, bbox.height
200
-
201
- def edge(self, item: str) -> float:
202
- """
203
- Distance w.r.t figure width from the left edge of the figure
204
- """
205
- return self.sum_upto(item)
206
-
207
-
208
- @dataclass
209
- class right_spaces(_side_spaces):
210
- """
211
- Space in the figure for artists on the right of the panels
212
-
213
- Ordered from the edge of the figure and going inwards
214
- """
215
-
216
- plot_margin: float = 0
217
- legend: float = 0
218
- legend_box_spacing: float = 0
219
- right_strip_width: float = 0
220
-
221
- def _calculate(self):
222
- pack = self.pack
223
- theme = self.pack.theme
224
-
225
- self.plot_margin = theme.getp("plot_margin_right")
226
- if pack.legends and pack.legends.right:
227
- self.legend = self._legend_width
228
- self.legend_box_spacing = theme.getp("legend_box_spacing")
229
-
230
- self.right_strip_width = get_right_strip_width(pack)
231
-
232
- # Adjust plot_margin to make room for ylabels that protude well
233
- # beyond the axes
234
- # NOTE: This adjustment breaks down when the protrusion is large
235
- protrusion = max_xlabels_right_protrusion(pack)
236
- adjustment = protrusion - (self.total - self.plot_margin)
237
- if adjustment > 0:
238
- self.plot_margin += adjustment
239
-
240
- @cached_property
241
- def _legend_size(self) -> tuple[float, float]:
242
- if not (self.pack.legends and self.pack.legends.right):
243
- return (0, 0)
244
-
245
- bbox = bbox_in_figure_space(
246
- self.pack.legends.right.box, self.pack.figure, self.pack.renderer
247
- )
248
- return bbox.width, bbox.height
249
-
250
- def edge(self, item: str) -> float:
251
- """
252
- Distance w.r.t figure width from the right edge of the figure
253
- """
254
- return 1 - self.sum_upto(item)
255
-
256
-
257
- @dataclass
258
- class top_spaces(_side_spaces):
259
- """
260
- Space in the figure for artists above the panels
261
-
262
- Ordered from the edge of the figure and going inwards
263
- """
264
-
265
- plot_margin: float = 0
266
- plot_title: float = 0
267
- plot_title_margin_bottom: float = 0
268
- plot_subtitle: float = 0
269
- plot_subtitle_margin_bottom: float = 0
270
- legend: float = 0
271
- legend_box_spacing: float = 0
272
- top_strip_height: float = 0
273
-
274
- def _calculate(self):
275
- pack = self.pack
276
- theme = self.pack.theme
277
- W, H = theme.getp("figure_size")
278
- F = W / H
279
-
280
- self.plot_margin = theme.getp("plot_margin_top") * F
281
- if pack.plot_title:
282
- self.plot_title = bbox_in_figure_space(
283
- pack.plot_title, pack.figure, pack.renderer
284
- ).height
285
- self.plot_title_margin_bottom = (
286
- theme.getp(("plot_title", "margin")).get_as("b", "fig") * F
287
- )
288
-
289
- if pack.plot_subtitle:
290
- self.plot_subtitle = bbox_in_figure_space(
291
- pack.plot_subtitle, pack.figure, pack.renderer
292
- ).height
293
- self.plot_subtitle_margin_bottom = (
294
- theme.getp(("plot_subtitle", "margin")).get_as("b", "fig") * F
295
- )
296
-
297
- if pack.legends and pack.legends.top:
298
- self.legend = self._legend_height
299
- self.legend_box_spacing = theme.getp("legend_box_spacing") * F
300
-
301
- self.top_strip_height = get_top_strip_height(pack)
302
-
303
- # Adjust plot_margin to make room for ylabels that protude well
304
- # beyond the axes
305
- # NOTE: This adjustment breaks down when the protrusion is large
306
- protrusion = max_ylabels_top_protrusion(pack)
307
- adjustment = protrusion - (self.total - self.plot_margin)
308
- if adjustment > 0:
309
- self.plot_margin += adjustment
310
-
311
- @cached_property
312
- def _legend_size(self) -> tuple[float, float]:
313
- if not (self.pack.legends and self.pack.legends.top):
314
- return (0, 0)
315
-
316
- bbox = bbox_in_figure_space(
317
- self.pack.legends.top.box, self.pack.figure, self.pack.renderer
318
- )
319
- return bbox.width, bbox.height
320
-
321
- def edge(self, item: str) -> float:
322
- """
323
- Distance w.r.t figure height from the top edge of the figure
324
- """
325
- return 1 - self.sum_upto(item)
326
-
327
-
328
- @dataclass
329
- class bottom_spaces(_side_spaces):
330
- """
331
- Space in the figure for artists below the panels
332
-
333
- Ordered from the edge of the figure and going inwards
334
- """
335
-
336
- plot_margin: float = 0
337
- plot_caption: float = 0
338
- plot_caption_margin_top: float = 0
339
- legend: float = 0
340
- legend_box_spacing: float = 0
341
- axis_title_x: float = 0
342
- axis_title_x_margin_top: float = 0
343
- axis_xlabels: float = 0
344
- axis_xticks: float = 0
345
-
346
- def _calculate(self):
347
- pack = self.pack
348
- theme = self.pack.theme
349
- W, H = theme.getp("figure_size")
350
- F = W / H
351
-
352
- self.plot_margin = theme.getp("plot_margin_bottom") * F
353
-
354
- if pack.plot_caption:
355
- self.plot_caption = bbox_in_figure_space(
356
- pack.plot_caption, pack.figure, pack.renderer
357
- ).height
358
- self.plot_caption_margin_top = (
359
- theme.getp(("plot_caption", "margin")).get_as("t", "fig") * F
360
- )
361
-
362
- if pack.legends and pack.legends.bottom:
363
- self.legend = self._legend_height
364
- self.legend_box_spacing = theme.getp("legend_box_spacing") * F
365
-
366
- if pack.axis_title_x:
367
- self.axis_title_x = bbox_in_figure_space(
368
- pack.axis_title_x, pack.figure, pack.renderer
369
- ).height
370
- self.axis_title_x_margin_top = (
371
- theme.getp(("axis_title_x", "margin")).get_as("t", "fig") * F
372
- )
373
-
374
- # Account for the space consumed by the axis
375
- self.axis_xticks = max_xticks_height(pack, "last_row")
376
- self.axis_xlabels = max_xlabels_height(pack, "last_row")
377
-
378
- # Adjust plot_margin to make room for ylabels that protude well
379
- # beyond the axes
380
- # NOTE: This adjustment breaks down when the protrusion is large
381
- protrusion = max_ylabels_bottom_protrusion(pack)
382
- adjustment = protrusion - (self.total - self.plot_margin)
383
- if adjustment > 0:
384
- self.plot_margin += adjustment
385
-
386
- @cached_property
387
- def _legend_size(self) -> tuple[float, float]:
388
- if not (self.pack.legends and self.pack.legends.bottom):
389
- return (0, 0)
390
-
391
- bbox = bbox_in_figure_space(
392
- self.pack.legends.bottom.box, self.pack.figure, self.pack.renderer
393
- )
394
- return bbox.width, bbox.height
395
-
396
- def edge(self, item: str) -> float:
397
- """
398
- Distance w.r.t figure height from the bottom edge of the figure
399
- """
400
- return self.sum_upto(item)
401
-
402
-
403
- @dataclass
404
- class LRTBSpaces:
405
- """
406
- Space for components in all directions around the panels
407
- """
408
-
409
- pack: LayoutPack
410
-
411
- def __post_init__(self):
412
- self.l = left_spaces(self.pack)
413
- self.r = right_spaces(self.pack)
414
- self.t = top_spaces(self.pack)
415
- self.b = bottom_spaces(self.pack)
416
-
417
- @property
418
- def left(self):
419
- """
420
- Left of the panels in figure space
421
- """
422
- return self.l.total
423
-
424
- @property
425
- def right(self):
426
- """
427
- Right of the panels in figure space
428
- """
429
- return 1 - self.r.total
430
-
431
- @property
432
- def top(self):
433
- """
434
- Top of the panels in figure space
435
- """
436
- return 1 - self.t.total
437
-
438
- @property
439
- def bottom(self):
440
- """
441
- Bottom of the panels in figure space
442
- """
443
- return self.b.total
444
-
445
-
446
- def calculate_panel_spacing(
447
- pack: LayoutPack, spaces: LRTBSpaces
448
- ) -> WHSpaceParts:
449
- """
450
- Spacing between the panels (wspace & hspace)
451
-
452
- Both spaces are calculated from a fraction of the width.
453
- This ensures that the same fraction gives equals space
454
- in both directions.
455
- """
456
- if isinstance(pack.facet, facet_wrap):
457
- return _calculate_panel_spacing_facet_wrap(pack, spaces)
458
- elif isinstance(pack.facet, facet_grid):
459
- return _calculate_panel_spacing_facet_grid(pack, spaces)
460
- elif isinstance(pack.facet, facet_null):
461
- return _calculate_panel_spacing_facet_null(pack, spaces)
462
- return WHSpaceParts(0, 0, 0, 0, 0, 0, 0, 0)
463
-
464
-
465
- def _calculate_panel_spacing_facet_grid(
466
- pack: LayoutPack, spaces: LRTBSpaces
467
- ) -> WHSpaceParts:
468
- """
469
- Calculate spacing parts for facet_grid
470
- """
471
- pack.facet = cast(facet_grid, pack.facet)
472
- theme = pack.theme
473
-
474
- ncol = pack.facet.ncol
475
- nrow = pack.facet.nrow
476
-
477
- W, H = theme.getp("figure_size")
478
-
479
- # Both spacings are specified as fractions of the figure width
480
- # Multiply the vertical by (W/H) so that the gullies along both
481
- # directions are equally spaced.
482
- sw = theme.getp("panel_spacing_x")
483
- sh = theme.getp("panel_spacing_y") * W / H
484
-
485
- # width and height of axes as fraction of figure width & height
486
- w = ((spaces.right - spaces.left) - sw * (ncol - 1)) / ncol
487
- h = ((spaces.top - spaces.bottom) - sh * (nrow - 1)) / nrow
488
-
489
- # Spacing as fraction of axes width & height
490
- wspace = sw / w
491
- hspace = sh / h
492
-
493
- return WHSpaceParts(W, H, w, h, sw, sh, wspace, hspace)
494
-
495
-
496
- def _calculate_panel_spacing_facet_wrap(
497
- pack: LayoutPack, spaces: LRTBSpaces
498
- ) -> WHSpaceParts:
499
- """
500
- Calculate spacing parts for facet_wrap
501
- """
502
- pack.facet = cast(facet_wrap, pack.facet)
503
- theme = pack.theme
504
-
505
- ncol = pack.facet.ncol
506
- nrow = pack.facet.nrow
507
-
508
- W, H = theme.getp("figure_size")
509
-
510
- # Both spacings are specified as fractions of the figure width
511
- sw = theme.getp("panel_spacing_x")
512
- sh = theme.getp("panel_spacing_y") * W / H
513
-
514
- # A fraction of the strip height
515
- # Effectively slides the strip
516
- # +ve: Away from the panel
517
- # 0: Top of the panel
518
- # -ve: Into the panel
519
- # Where values <= -1, put the strip completely into
520
- # the panel. We do not worry about larger -ves.
521
- strip_align_x = theme.getp("strip_align_x")
522
-
523
- # Only interested in the proportion of the strip that
524
- # does not overlap with the panel
525
- if strip_align_x > -1:
526
- sh += spaces.t.top_strip_height * (1 + strip_align_x)
527
-
528
- if pack.facet.free["x"]:
529
- sh += max_xlabels_height(pack)
530
- sh += max_xticks_height(pack)
531
- if pack.facet.free["y"]:
532
- sw += max_ylabels_width(pack)
533
- sw += max_yticks_width(pack)
534
-
535
- # width and height of axes as fraction of figure width & height
536
- w = ((spaces.right - spaces.left) - sw * (ncol - 1)) / ncol
537
- h = ((spaces.top - spaces.bottom) - sh * (nrow - 1)) / nrow
538
-
539
- # Spacing as fraction of axes width & height
540
- wspace = sw / w
541
- hspace = sh / h
542
-
543
- return WHSpaceParts(W, H, w, h, sw, sh, wspace, hspace)
544
-
545
-
546
- def _calculate_panel_spacing_facet_null(
547
- pack: LayoutPack, spaces: LRTBSpaces
548
- ) -> WHSpaceParts:
549
- """
550
- Calculate spacing parts for facet_null
551
- """
552
- W, H = pack.theme.getp("figure_size")
553
- w = spaces.right - spaces.left
554
- h = spaces.top - spaces.bottom
555
- return WHSpaceParts(W, H, w, h, 0, 0, 0, 0)
556
-
557
-
558
- def filter_axes(axs: list[Axes], get: AxesLocation = "all") -> list[Axes]:
559
- """
560
- Return subset of axes
561
- """
562
- if get == "all":
563
- return axs
564
-
565
- # e.g. is_first_row, is_last_row, ..
566
- pred_method = f"is_{get}"
567
- return [
568
- ax
569
- for spec, ax in zip(get_subplotspec_list(axs), axs)
570
- if getattr(spec, pred_method)()
571
- ]
572
-
573
-
574
- def max_width(pack: LayoutPack, artists: Sequence[Artist]) -> float:
575
- """
576
- Return the maximum width of list of artists
577
- """
578
- widths = [
579
- bbox_in_figure_space(a, pack.figure, pack.renderer).width
580
- for a in artists
581
- ]
582
- return max(widths) if len(widths) else 0
583
-
584
-
585
- def max_height(pack: LayoutPack, artists: Sequence[Artist]) -> float:
586
- """
587
- Return the maximum height of list of artists
588
- """
589
- heights = [
590
- bbox_in_figure_space(a, pack.figure, pack.renderer).height
591
- for a in artists
592
- ]
593
- return max(heights) if len(heights) else 0
594
-
595
-
596
- def get_top_strip_height(pack: LayoutPack) -> float:
597
- """
598
- Height taken up by the top strips
599
- """
600
- if not pack.strip_text_x:
601
- return 0
602
-
603
- artists = [
604
- st.patch if st.patch.get_visible() else st
605
- for st in pack.strip_text_x
606
- if st.patch.position == "top"
607
- ]
608
- return max_height(pack, artists)
609
-
610
-
611
- def get_right_strip_width(pack: LayoutPack) -> float:
612
- """
613
- Width taken up by the right strips
614
- """
615
- if not pack.strip_text_y:
616
- return 0
617
-
618
- artists = [
619
- st.patch if st.patch.get_visible() else st
620
- for st in pack.strip_text_y
621
- if st.patch.position == "right"
622
- ]
623
- return max_width(pack, artists)
624
-
625
-
626
- def get_xaxis_ticks(pack: LayoutPack, ax: Axes) -> Iterator[Tick]:
627
- """
628
- Return all XTicks that will be shown
629
- """
630
- is_blank = pack.theme.T.is_blank
631
- major, minor = [], []
632
-
633
- if not is_blank("axis_ticks_major_x"):
634
- major = ax.xaxis.get_major_ticks()
635
-
636
- if not is_blank("axis_ticks_minor_x"):
637
- minor = ax.xaxis.get_minor_ticks()
638
-
639
- return chain(major, minor)
640
-
641
-
642
- def get_yaxis_ticks(pack: LayoutPack, ax: Axes) -> Iterator[Tick]:
643
- """
644
- Return all YTicks that will be shown
645
- """
646
- is_blank = pack.theme.T.is_blank
647
- major, minor = [], []
648
-
649
- if not is_blank("axis_ticks_major_y"):
650
- major = ax.yaxis.get_major_ticks()
651
-
652
- if not is_blank("axis_ticks_minor_y"):
653
- minor = ax.yaxis.get_minor_ticks()
654
-
655
- return chain(major, minor)
656
-
657
-
658
- def get_xaxis_tick_pads(pack: LayoutPack, ax: Axes) -> Iterator[float]:
659
- """
660
- Return XTicks paddings
661
- """
662
- # In plotnine tick padding are specified as a margin to the
663
- # the axis_text.
664
- is_blank = pack.theme.T.is_blank
665
- major, minor = [], []
666
- if not is_blank("axis_text_y"):
667
- h = pack.figure.get_figheight() * 72
668
- major = [(t.get_pad() or 0) / h for t in ax.xaxis.get_major_ticks()]
669
- minor = [(t.get_pad() or 0) / h for t in ax.xaxis.get_minor_ticks()]
670
- return chain(major, minor)
671
-
672
-
673
- def get_yaxis_tick_pads(pack: LayoutPack, ax: Axes) -> Iterator[float]:
674
- """
675
- Return YTicks paddings
676
- """
677
- # In plotnine tick padding are specified as a margin to the
678
- # the axis_text.
679
- is_blank = pack.theme.T.is_blank
680
- major, minor = [], []
681
- if not is_blank("axis_text_y"):
682
- w = pack.figure.get_figwidth() * 72
683
- major = [(t.get_pad() or 0) / w for t in ax.yaxis.get_major_ticks()]
684
- minor = [(t.get_pad() or 0) / w for t in ax.yaxis.get_minor_ticks()]
685
- return chain(major, minor)
686
-
687
-
688
- def _text_is_visible(text: Text) -> bool:
689
- """
690
- Return True if text is visible and is not empty
691
- """
692
- return text.get_visible() and text._text # type: ignore
693
-
694
-
695
- def get_xaxis_labels(pack: LayoutPack, ax: Axes) -> Iterator[Text]:
696
- """
697
- Return all x-axis labels that will be shown
698
- """
699
- is_blank = pack.theme.T.is_blank
700
- major, minor = [], []
701
-
702
- if not is_blank("axis_text_x"):
703
- major = ax.xaxis.get_major_ticks()
704
- minor = ax.xaxis.get_minor_ticks()
705
-
706
- return (
707
- tick.label1
708
- for tick in chain(major, minor)
709
- if _text_is_visible(tick.label1)
710
- )
711
-
712
-
713
- def get_yaxis_labels(pack: LayoutPack, ax: Axes) -> Iterator[Text]:
714
- """
715
- Return all y-axis labels that will be shown
716
- """
717
- is_blank = pack.theme.T.is_blank
718
- major, minor = [], []
719
-
720
- if not is_blank("axis_text_y"):
721
- major = ax.yaxis.get_major_ticks()
722
- minor = ax.yaxis.get_minor_ticks()
723
-
724
- return (
725
- tick.label1
726
- for tick in chain(major, minor)
727
- if _text_is_visible(tick.label1)
728
- )
729
-
730
-
731
- def max_xticks_height(
732
- pack: LayoutPack,
733
- axes_loc: AxesLocation = "all",
734
- ) -> float:
735
- """
736
- Return maximum height[inches] of x ticks
737
- """
738
- heights = [
739
- tight_bbox_in_figure_space(
740
- tick.tick1line, pack.figure, pack.renderer
741
- ).height
742
- for ax in filter_axes(pack.axs, axes_loc)
743
- for tick in get_xaxis_ticks(pack, ax)
744
- ]
745
- return max(heights) if len(heights) else 0
746
-
747
-
748
- def max_xlabels_height(
749
- pack: LayoutPack,
750
- axes_loc: AxesLocation = "all",
751
- ) -> float:
752
- """
753
- Return maximum height[inches] of x tick labels
754
- """
755
- heights = [
756
- tight_bbox_in_figure_space(label, pack.figure, pack.renderer).height
757
- + pad
758
- for ax in filter_axes(pack.axs, axes_loc)
759
- for label, pad in zip(
760
- get_xaxis_labels(pack, ax), get_xaxis_tick_pads(pack, ax)
761
- )
762
- ]
763
- return max(heights) if len(heights) else 0
764
-
765
-
766
- def max_yticks_width(
767
- pack: LayoutPack,
768
- axes_loc: AxesLocation = "all",
769
- ) -> float:
770
- """
771
- Return maximum width[inches] of y ticks
772
- """
773
- widths = [
774
- tight_bbox_in_figure_space(
775
- tick.tick1line, pack.figure, pack.renderer
776
- ).width
777
- for ax in filter_axes(pack.axs, axes_loc)
778
- for tick in get_yaxis_ticks(pack, ax)
779
- ]
780
- return max(widths) if len(widths) else 0
781
-
782
-
783
- def max_ylabels_width(
784
- pack: LayoutPack,
785
- axes_loc: AxesLocation = "all",
786
- ) -> float:
787
- """
788
- Return maximum width[inches] of y tick labels
789
- """
790
- widths = [
791
- tight_bbox_in_figure_space(label, pack.figure, pack.renderer).width
792
- + pad
793
- for ax in filter_axes(pack.axs, axes_loc)
794
- for label, pad in zip(
795
- get_yaxis_labels(pack, ax), get_yaxis_tick_pads(pack, ax)
796
- )
797
- ]
798
- return max(widths) if len(widths) else 0
799
-
800
-
801
- def max_ylabels_top_protrusion(
802
- pack: LayoutPack,
803
- axes_loc: AxesLocation = "all",
804
- ) -> float:
805
- """
806
- Return maximum height[inches] above the axes of y tick labels
807
- """
808
-
809
- def get_artist_top_y(a: Artist) -> float:
810
- xy = bbox_in_figure_space(a, pack.figure, pack.renderer).max
811
- return xy[1]
812
-
813
- extras = []
814
- for ax in filter_axes(pack.axs, axes_loc):
815
- ax_top = get_artist_top_y(ax)
816
- for label in get_yaxis_labels(pack, ax):
817
- label_top = get_artist_top_y(label)
818
- extras.append(max(0, label_top - ax_top))
819
-
820
- return max(extras) if len(extras) else 0
821
-
822
-
823
- def max_ylabels_bottom_protrusion(
824
- pack: LayoutPack,
825
- axes_loc: AxesLocation = "all",
826
- ) -> float:
827
- """
828
- Return maximum height[inches] below the axes of y tick labels
829
- """
830
-
831
- def get_artist_bottom_y(a: Artist) -> float:
832
- xy = bbox_in_figure_space(a, pack.figure, pack.renderer).min
833
- return xy[1]
834
-
835
- extras = []
836
- for ax in filter_axes(pack.axs, axes_loc):
837
- ax_bottom = get_artist_bottom_y(ax)
838
- for label in get_yaxis_labels(pack, ax):
839
- label_bottom = get_artist_bottom_y(label)
840
- protrusion = abs(min(label_bottom - ax_bottom, 0))
841
- extras.append(protrusion)
842
-
843
- return max(extras) if len(extras) else 0
844
-
845
-
846
- def max_xlabels_left_protrusion(
847
- pack: LayoutPack,
848
- axes_loc: AxesLocation = "all",
849
- ) -> float:
850
- """
851
- Return maximum width[inches] of x tick labels to the left of the axes
852
- """
853
-
854
- def get_artist_left_x(a: Artist) -> float:
855
- xy = bbox_in_figure_space(a, pack.figure, pack.renderer).min
856
- return xy[0]
857
-
858
- extras = []
859
- for ax in filter_axes(pack.axs, axes_loc):
860
- ax_left = get_artist_left_x(ax)
861
- for label in get_xaxis_labels(pack, ax):
862
- label_left = get_artist_left_x(label)
863
- protrusion = abs(min(label_left - ax_left, 0))
864
- extras.append(protrusion)
865
-
866
- return max(extras) if len(extras) else 0
867
-
868
-
869
- def max_xlabels_right_protrusion(
870
- pack: LayoutPack,
871
- axes_loc: AxesLocation = "all",
872
- ) -> float:
873
- """
874
- Return maximum width[inches] of x tick labels to the right of the axes
875
- """
876
-
877
- def get_artist_right_x(a: Artist) -> float:
878
- xy = bbox_in_figure_space(a, pack.figure, pack.renderer).max
879
- return xy[0]
880
-
881
- extras = []
882
- for ax in filter_axes(pack.axs, axes_loc):
883
- ax_right = get_artist_right_x(ax)
884
- for label in get_xaxis_labels(pack, ax):
885
- label_right = get_artist_right_x(label)
886
- extras.append(max(0, label_right - ax_right))
887
-
888
- return max(extras) if len(extras) else 0