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.
Files changed (140) hide show
  1. plotnine/__init__.py +2 -0
  2. plotnine/_mpl/layout_manager/_engine.py +1 -1
  3. plotnine/_mpl/layout_manager/_layout_items.py +128 -83
  4. plotnine/_mpl/layout_manager/_layout_tree.py +761 -310
  5. plotnine/_mpl/layout_manager/_spaces.py +320 -103
  6. plotnine/_mpl/patches.py +70 -34
  7. plotnine/_mpl/text.py +144 -63
  8. plotnine/_mpl/utils.py +1 -1
  9. plotnine/_utils/__init__.py +50 -107
  10. plotnine/_utils/context.py +78 -2
  11. plotnine/_utils/ipython.py +35 -51
  12. plotnine/_utils/quarto.py +21 -0
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/composition/__init__.py +11 -0
  15. plotnine/composition/_beside.py +55 -0
  16. plotnine/composition/_compose.py +471 -0
  17. plotnine/composition/_plot_spacer.py +60 -0
  18. plotnine/composition/_stack.py +55 -0
  19. plotnine/coords/coord.py +3 -3
  20. plotnine/data/__init__.py +31 -0
  21. plotnine/data/anscombe-quartet.csv +45 -0
  22. plotnine/doctools.py +4 -4
  23. plotnine/facets/facet.py +4 -4
  24. plotnine/facets/strips.py +17 -28
  25. plotnine/geoms/annotate.py +13 -13
  26. plotnine/geoms/annotation_logticks.py +7 -8
  27. plotnine/geoms/annotation_stripes.py +6 -6
  28. plotnine/geoms/geom.py +60 -27
  29. plotnine/geoms/geom_abline.py +3 -2
  30. plotnine/geoms/geom_area.py +2 -2
  31. plotnine/geoms/geom_bar.py +11 -2
  32. plotnine/geoms/geom_bin_2d.py +6 -2
  33. plotnine/geoms/geom_blank.py +0 -3
  34. plotnine/geoms/geom_boxplot.py +8 -4
  35. plotnine/geoms/geom_col.py +8 -2
  36. plotnine/geoms/geom_count.py +6 -2
  37. plotnine/geoms/geom_crossbar.py +3 -3
  38. plotnine/geoms/geom_density_2d.py +6 -2
  39. plotnine/geoms/geom_dotplot.py +2 -2
  40. plotnine/geoms/geom_errorbar.py +2 -2
  41. plotnine/geoms/geom_errorbarh.py +2 -2
  42. plotnine/geoms/geom_histogram.py +1 -1
  43. plotnine/geoms/geom_hline.py +3 -2
  44. plotnine/geoms/geom_linerange.py +2 -2
  45. plotnine/geoms/geom_map.py +5 -5
  46. plotnine/geoms/geom_path.py +11 -12
  47. plotnine/geoms/geom_point.py +4 -5
  48. plotnine/geoms/geom_pointdensity.py +4 -0
  49. plotnine/geoms/geom_pointrange.py +3 -5
  50. plotnine/geoms/geom_polygon.py +2 -3
  51. plotnine/geoms/geom_qq.py +4 -0
  52. plotnine/geoms/geom_qq_line.py +4 -0
  53. plotnine/geoms/geom_quantile.py +4 -0
  54. plotnine/geoms/geom_raster.py +4 -5
  55. plotnine/geoms/geom_rect.py +3 -4
  56. plotnine/geoms/geom_ribbon.py +7 -7
  57. plotnine/geoms/geom_rug.py +1 -1
  58. plotnine/geoms/geom_segment.py +2 -2
  59. plotnine/geoms/geom_sina.py +3 -3
  60. plotnine/geoms/geom_smooth.py +7 -3
  61. plotnine/geoms/geom_step.py +2 -2
  62. plotnine/geoms/geom_text.py +2 -3
  63. plotnine/geoms/geom_violin.py +28 -8
  64. plotnine/geoms/geom_vline.py +3 -2
  65. plotnine/ggplot.py +64 -85
  66. plotnine/guides/guide.py +7 -10
  67. plotnine/guides/guide_colorbar.py +3 -3
  68. plotnine/guides/guide_legend.py +3 -3
  69. plotnine/guides/guides.py +6 -6
  70. plotnine/helpers.py +49 -0
  71. plotnine/iapi.py +28 -5
  72. plotnine/labels.py +3 -3
  73. plotnine/layer.py +36 -19
  74. plotnine/mapping/_atomic.py +178 -0
  75. plotnine/mapping/_env.py +13 -2
  76. plotnine/mapping/_eval_environment.py +85 -0
  77. plotnine/mapping/aes.py +91 -72
  78. plotnine/mapping/evaluation.py +7 -65
  79. plotnine/scales/__init__.py +2 -0
  80. plotnine/scales/limits.py +7 -7
  81. plotnine/scales/scale.py +3 -3
  82. plotnine/scales/scale_color.py +82 -18
  83. plotnine/scales/scale_continuous.py +6 -4
  84. plotnine/scales/scale_datetime.py +28 -14
  85. plotnine/scales/scale_discrete.py +1 -1
  86. plotnine/scales/scale_identity.py +21 -2
  87. plotnine/scales/scale_manual.py +8 -2
  88. plotnine/scales/scale_xy.py +2 -2
  89. plotnine/stats/binning.py +4 -1
  90. plotnine/stats/smoothers.py +23 -36
  91. plotnine/stats/stat.py +20 -32
  92. plotnine/stats/stat_bin.py +6 -5
  93. plotnine/stats/stat_bin_2d.py +11 -9
  94. plotnine/stats/stat_bindot.py +13 -16
  95. plotnine/stats/stat_boxplot.py +6 -6
  96. plotnine/stats/stat_count.py +6 -9
  97. plotnine/stats/stat_density.py +7 -10
  98. plotnine/stats/stat_density_2d.py +12 -8
  99. plotnine/stats/stat_ecdf.py +7 -6
  100. plotnine/stats/stat_ellipse.py +9 -6
  101. plotnine/stats/stat_function.py +10 -8
  102. plotnine/stats/stat_hull.py +6 -3
  103. plotnine/stats/stat_identity.py +5 -2
  104. plotnine/stats/stat_pointdensity.py +5 -7
  105. plotnine/stats/stat_qq.py +46 -20
  106. plotnine/stats/stat_qq_line.py +16 -11
  107. plotnine/stats/stat_quantile.py +15 -9
  108. plotnine/stats/stat_sina.py +45 -14
  109. plotnine/stats/stat_smooth.py +8 -10
  110. plotnine/stats/stat_sum.py +5 -2
  111. plotnine/stats/stat_summary.py +7 -10
  112. plotnine/stats/stat_summary_bin.py +11 -14
  113. plotnine/stats/stat_unique.py +5 -2
  114. plotnine/stats/stat_ydensity.py +8 -11
  115. plotnine/themes/elements/__init__.py +2 -1
  116. plotnine/themes/elements/element_line.py +17 -9
  117. plotnine/themes/elements/margin.py +64 -1
  118. plotnine/themes/theme.py +9 -1
  119. plotnine/themes/theme_538.py +0 -1
  120. plotnine/themes/theme_bw.py +0 -1
  121. plotnine/themes/theme_dark.py +0 -1
  122. plotnine/themes/theme_gray.py +6 -5
  123. plotnine/themes/theme_light.py +1 -1
  124. plotnine/themes/theme_matplotlib.py +5 -5
  125. plotnine/themes/theme_seaborn.py +7 -4
  126. plotnine/themes/theme_void.py +9 -8
  127. plotnine/themes/theme_xkcd.py +0 -1
  128. plotnine/themes/themeable.py +110 -32
  129. plotnine/typing.py +17 -6
  130. plotnine/watermark.py +3 -3
  131. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/METADATA +13 -6
  132. plotnine-0.15.1.dist-info/RECORD +221 -0
  133. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/WHEEL +1 -1
  134. plotnine/plot_composition/__init__.py +0 -10
  135. plotnine/plot_composition/_compose.py +0 -436
  136. plotnine/plot_composition/_spacer.py +0 -32
  137. plotnine-0.15.0.dev2.dist-info/RECORD +0 -214
  138. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  139. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/licenses/LICENSE +0 -0
  140. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
- from contextlib import suppress
5
4
  from dataclasses import dataclass
6
5
  from functools import cached_property
7
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, cast
8
7
 
9
8
  import numpy as np
10
9
 
11
- from plotnine.plot_composition import OR
10
+ from plotnine.composition import Beside
12
11
 
13
12
  from ._spaces import LayoutSpaces
14
13
 
@@ -17,7 +16,7 @@ if TYPE_CHECKING:
17
16
 
18
17
  from plotnine import ggplot
19
18
  from plotnine._mpl.gridspec import p9GridSpec
20
- from plotnine.plot_composition import Compose
19
+ from plotnine.composition import Compose
21
20
 
22
21
 
23
22
  @dataclass
@@ -81,169 +80,181 @@ class LayoutTree:
81
80
  represents.
82
81
  """
83
82
 
84
- @cached_property
85
- @abc.abstractmethod
86
- def lefts(self) -> Sequence[float]:
83
+ @staticmethod
84
+ def create(
85
+ cmp: Compose,
86
+ lookup_spaces: dict[ggplot, LayoutSpaces],
87
+ ) -> LayoutTree:
87
88
  """
88
- Left values [figure space] of nodes in this tree
89
+ Create a LayoutTree for this composition
90
+
91
+ Parameters
92
+ ----------
93
+ cmp :
94
+ Composition
95
+ lookup_spaces :
96
+ A table to lookup the LayoutSpaces for each plot.
97
+
98
+ Notes
99
+ -----
100
+ LayoutTree works by modifying the `.gridspec` of the compositions,
101
+ and the `LayoutSpaces` of the plots.
102
+ """
103
+ from plotnine import ggplot
104
+
105
+ nodes: list[LayoutSpaces | LayoutTree] = []
106
+ for item in cmp:
107
+ if isinstance(item, ggplot):
108
+ nodes.append(lookup_spaces[item])
109
+ else:
110
+ nodes.append(LayoutTree.create(item, lookup_spaces))
111
+
112
+ if isinstance(cmp, Beside):
113
+ return ColumnsTree(cmp.gridspec, nodes)
114
+ else:
115
+ return RowsTree(cmp.gridspec, nodes)
116
+
117
+ def harmonise(self):
89
118
  """
119
+ Align and resize plots in composition to look good
120
+ """
121
+ self.align_axis_titles()
122
+ self.align()
123
+ self.resize()
90
124
 
91
125
  @abc.abstractmethod
92
- def set_left_alignment_margin(self, value: float):
126
+ def align(self):
93
127
  """
94
- Set a margin to align the left of the panels in this composition
128
+ Align all the edges in this composition & contained compositions
95
129
 
96
- In figure dimenstions
130
+ This function mutates the layout spaces, specifically the
131
+ margin_alignments along the sides of the plot.
97
132
  """
98
133
 
99
- @cached_property
100
134
  @abc.abstractmethod
101
- def bottoms(self) -> Sequence[float]:
135
+ def resize(self):
102
136
  """
103
- Bottom values [figure space] of nodes in this tree
137
+ Resize panels and the entire plots
138
+
139
+ This function mutates the composition gridspecs; specifically the
140
+ width_ratios and height_ratios.
104
141
  """
105
142
 
143
+ def align_sub_compositions(self):
144
+ """
145
+ Align the compositions contained in this one
146
+ """
147
+ # Recurse into the contained compositions
148
+ for tree in self.sub_compositions:
149
+ tree.align()
150
+
106
151
  @abc.abstractmethod
107
- def set_bottom_alignment_margin(self, value: float):
152
+ def align_axis_titles(self):
108
153
  """
109
- Set a margin to align the bottom of the panels in this composition
154
+ Align the axis titles along the composing dimension
110
155
 
111
- In figure dimenstions
156
+ Since the alignment value used to for this purpose is one of
157
+ the fields in the _side_space, it affects the space created
158
+ for the panel.
159
+
160
+ We could align the titles within self.align but we would have
161
+ to store the value outside the _side_space and pick it up when
162
+ setting the position of the texts!
163
+ """
164
+
165
+ def resize_sub_compositions(self):
112
166
  """
167
+ Resize panels in the compositions contained in this one
168
+ """
169
+ for tree in self.sub_compositions:
170
+ tree.resize()
113
171
 
114
172
  @cached_property
115
- @abc.abstractmethod
116
- def tops(self) -> Sequence[float]:
173
+ def sub_compositions(self) -> list[LayoutTree]:
117
174
  """
118
- Top values [figure space] of nodes in this tree
175
+ LayoutTrees of the direct sub compositions of this one
119
176
  """
177
+ return [item for item in self.nodes if isinstance(item, LayoutTree)]
120
178
 
179
+ @cached_property
121
180
  @abc.abstractmethod
122
- def set_top_alignment_margin(self, value: float):
181
+ def panel_lefts(self) -> Sequence[float]:
123
182
  """
124
- Set a margin to align the top of the panels in this composition
125
-
126
- In figure dimenstions
183
+ Left values [figure space] of nodes in this tree
127
184
  """
128
185
 
129
186
  @cached_property
130
187
  @abc.abstractmethod
131
- def rights(self) -> Sequence[float]:
188
+ def panel_rights(self) -> Sequence[float]:
132
189
  """
133
190
  Right values [figure space] of nodes in this tree
134
191
  """
135
192
 
193
+ @cached_property
136
194
  @abc.abstractmethod
137
- def set_right_alignment_margin(self, value: float):
195
+ def panel_bottoms(self) -> Sequence[float]:
138
196
  """
139
- Set a margin to align the right of the panels in this composition
140
-
141
- In figure dimenstions
197
+ Bottom values [figure space] of nodes in this tree
142
198
  """
143
199
 
144
- def align_lefts(self):
200
+ @cached_property
201
+ @abc.abstractmethod
202
+ def panel_tops(self) -> Sequence[float]:
145
203
  """
146
- Align the immediate left edges in this composition
147
-
148
- ----------- -----------
149
- |# | | # |
150
- |# | | # |
151
- |# | | # |
152
- |-----------| -> |-----------|
153
- | # | | # |
154
- | # | | # |
155
- | # | | # |
156
- ----------- -----------
204
+ Top values [figure space] of nodes in this tree
157
205
  """
158
206
 
159
- def align_bottoms(self):
207
+ @property
208
+ def panel_lefts_align(self) -> bool:
160
209
  """
161
- Align the immediate bottom edges this composition
162
-
163
- ----------- -----------
164
- | | | | | |
165
- | | | | | |
166
- | | | -> | | |
167
- | |#####| |#####|#####|
168
- |#####| | | | |
169
- ----------- -----------
210
+ Return True if panel lefts for the nodes are aligned
170
211
  """
212
+ arr = np.array(self.panel_lefts)
213
+ return all(arr == arr[0])
171
214
 
172
- def align_rights(self):
215
+ @property
216
+ def panel_rights_align(self) -> bool:
173
217
  """
174
- Align the immediate right edges in this composition
175
-
176
- ----------- -----------
177
- | # | | # |
178
- | # | | # |
179
- | # | | # |
180
- |-----------| -> |-----------|
181
- | #| | # |
182
- | #| | # |
183
- | #| | # |
184
- ----------- -----------
218
+ Return True if panel rights for the nodes are aligned
185
219
  """
220
+ arr = np.array(self.panel_rights)
221
+ return all(arr == arr[0])
186
222
 
187
- def align_tops(self):
223
+ @property
224
+ def panel_bottoms_align(self) -> bool:
188
225
  """
189
- Align the immediate top edges in this composition
190
-
191
- ----------- -----------
192
- |#####| | | | |
193
- | |#####| |#####|#####|
194
- | | | -> | | |
195
- | | | | | |
196
- | | | | | |
197
- ----------- -----------
226
+ Return True if panel bottoms for the nodes are aligned
198
227
  """
228
+ arr = np.array(self.panel_bottoms)
229
+ return all(arr == arr[0])
199
230
 
200
- def align(self):
231
+ @property
232
+ def panel_tops_align(self) -> bool:
201
233
  """
202
- Align all the edges in this composition & contained compositions
203
-
204
- This function mutates the layout spaces, specifically the
205
- alignment_margins along the sides of the plot.
234
+ Return True if panel tops for the nodes are aligned
206
235
  """
207
- self.align_lefts()
208
- self.align_bottoms()
209
- self.align_rights()
210
- self.align_tops()
211
-
212
- for item in self.nodes:
213
- if isinstance(item, LayoutTree):
214
- item.align()
215
-
216
- with suppress(AttributeError):
217
- del self.lefts
218
-
219
- with suppress(AttributeError):
220
- del self.bottoms
221
-
222
- with suppress(AttributeError):
223
- del self.rights
224
-
225
- with suppress(AttributeError):
226
- del self.tops
236
+ arr = np.array(self.panel_tops)
237
+ return all(arr == arr[0])
227
238
 
228
239
  @property
229
240
  @abc.abstractmethod
230
241
  def panel_width(self) -> float:
231
242
  """
232
- A representative for width for panels of the nodes
243
+ A representative width for panels of the nodes
233
244
  """
234
245
 
235
246
  @property
236
247
  @abc.abstractmethod
237
248
  def panel_height(self) -> float:
238
249
  """
239
- A representative for height for panels of the nodes
250
+ A representative height for panels of the nodes
240
251
  """
241
252
 
242
253
  @property
243
254
  @abc.abstractmethod
244
255
  def plot_width(self) -> float:
245
256
  """
246
- A representative for width for plots of the nodes
257
+ A representative width for plots of the nodes
247
258
  """
248
259
 
249
260
  @property
@@ -281,211 +292,494 @@ class LayoutTree:
281
292
  """
282
293
  return [node.panel_height for node in self.nodes]
283
294
 
284
- def resize(self):
295
+ @cached_property
296
+ @abc.abstractmethod
297
+ def left_tag_width(self) -> float:
285
298
  """
286
- Resize panels and the entire plots
287
-
288
- This function mutates the composition gridspecs; specifically the
289
- width_ratios and height_ratios.
299
+ A representative width [figure space] for the left tags of the nodes
290
300
  """
291
- self.resize_widths()
292
- self.resize_heights()
293
-
294
- for item in self.nodes:
295
- if isinstance(item, LayoutTree):
296
- item.resize()
297
301
 
298
- def resize_widths(self):
302
+ @cached_property
303
+ @abc.abstractmethod
304
+ def right_tag_width(self) -> float:
299
305
  """
300
- Resize the widths of gridspec so that panels have equal widths
306
+ A representative width [figure space] for the right tags of the nodes
301
307
  """
302
308
 
303
- def resize_heights(self):
309
+ @cached_property
310
+ @abc.abstractmethod
311
+ def bottom_tag_height(self) -> float:
304
312
  """
305
- Resize the heights of gridspec so that panels have equal heights
313
+ A representative height [figure space] for the top tags of the nodes
306
314
  """
307
315
 
308
- @staticmethod
309
- def create(
310
- cmp: Compose,
311
- lookup_spaces: dict[ggplot, LayoutSpaces],
312
- ) -> LayoutTree:
316
+ @cached_property
317
+ @abc.abstractmethod
318
+ def top_tag_height(self) -> float:
319
+ """
320
+ A representative height [figure space] for the top tags of the nodes
313
321
  """
314
- Create a LayoutTree for this composition
315
322
 
316
- Parameters
317
- ----------
318
- cmp :
319
- Composition
320
- lookup_spaces :
321
- A table to lookup the LayoutSpaces for each plot.
323
+ @cached_property
324
+ def left_tag_widths(self) -> list[float]:
325
+ """
326
+ The widths of the left tags in this tree
327
+ """
328
+ return [node.left_tag_width for node in self.nodes]
322
329
 
323
- Notes
324
- -----
325
- LayoutTree works by modifying the `.gridspec` of the compositions,
326
- and the `LayoutSpaces` of the plots.
330
+ @cached_property
331
+ def right_tag_widths(self) -> list[float]:
327
332
  """
328
- from plotnine import ggplot
333
+ The widths of the right tags in this tree
334
+ """
335
+ return [node.right_tag_width for node in self.nodes]
329
336
 
330
- nodes: list[LayoutSpaces | LayoutTree] = []
331
- for item in cmp:
332
- if isinstance(item, ggplot):
333
- nodes.append(lookup_spaces[item])
334
- else:
335
- nodes.append(LayoutTree.create(item, lookup_spaces))
337
+ @cached_property
338
+ def bottom_tag_heights(self) -> list[float]:
339
+ """
340
+ The heights of the bottom tags in this tree
341
+ """
342
+ return [node.bottom_tag_height for node in self.nodes]
336
343
 
337
- if isinstance(cmp, OR):
338
- return ColumnsTree(cmp.gridspec, nodes)
339
- else:
340
- return RowsTree(cmp.gridspec, nodes)
344
+ @cached_property
345
+ def top_tag_heights(self) -> list[float]:
346
+ """
347
+ The heights of the top tags in this tree
348
+ """
349
+ return [node.top_tag_height for node in self.nodes]
341
350
 
342
- def harmonise(self):
351
+ @property
352
+ def left_tags_align(self) -> bool:
343
353
  """
344
- Align and resize plots in composition to look good
354
+ Return True if the left tags for the nodes are aligned
345
355
  """
346
- self.align()
347
- self.resize()
356
+ arr = np.array(self.left_tag_widths)
357
+ return all(arr == arr[0])
348
358
 
359
+ @property
360
+ def right_tags_align(self) -> bool:
361
+ """
362
+ Return True if the right tags for the nodes are aligned
363
+ """
364
+ arr = np.array(self.right_tag_widths)
365
+ return all(arr == arr[0])
349
366
 
350
- @dataclass
351
- class ColumnsTree(LayoutTree):
352
- """
353
- Tree with columns at the outermost level
367
+ @property
368
+ def bottom_tags_align(self) -> bool:
369
+ """
370
+ Return True if the bottom tags for the nodes are aligned
371
+ """
372
+ arr = np.array(self.bottom_tag_heights)
373
+ return all(arr == arr[0])
354
374
 
355
- e.g. p1 | (p2 / p3)
375
+ @property
376
+ def top_tags_align(self) -> bool:
377
+ """
378
+ Return True if the top tags for the nodes are aligned
379
+ """
380
+ arr = np.array(self.top_tag_heights)
381
+ return all(arr == arr[0])
356
382
 
383
+ @property
384
+ def left_axis_titles_align(self) -> bool:
385
+ """
386
+ Return True if the left axis titles align
387
+ """
388
+ arr = np.array(self.left_axis_title_clearances)
389
+ return all(arr == arr[0])
357
390
 
358
- -------------------
359
- | | |
360
- | | |
361
- | | |
362
- | | |
363
- | |---------|
364
- | | |
365
- | | |
366
- | | |
367
- | | |
368
- -------------------
369
- """
391
+ @property
392
+ def bottom_axis_titles_align(self) -> bool:
393
+ """
394
+ Return True if the bottom axis titles align
395
+ """
396
+ arr = np.array(self.bottom_axis_title_clearances)
397
+ return all(arr == arr[0])
370
398
 
371
399
  @cached_property
372
- def lefts(self):
373
- left_item = self.nodes[0]
374
- if isinstance(left_item, LayoutSpaces):
375
- return [left_item.l.left]
376
- else:
377
- return left_item.lefts
378
-
379
- def set_left_alignment_margin(self, value: float):
380
- left_item = self.nodes[0]
381
- if isinstance(left_item, LayoutSpaces):
382
- left_item.l.alignment_margin = value
383
- else:
384
- left_item.set_left_alignment_margin(value)
385
-
386
- def align_bottoms(self):
387
- values = max(self.bottoms) - np.array(self.bottoms)
388
- for item, value in zip(self.nodes, values):
389
- if isinstance(item, LayoutSpaces):
390
- item.b.alignment_margin = value
391
- else:
392
- item.set_bottom_alignment_margin(value)
400
+ @abc.abstractmethod
401
+ def left_axis_title_clearance(self) -> float:
402
+ """
403
+ Distance between the left y-axis title and the panel
404
+ """
393
405
 
394
406
  @cached_property
395
- def bottoms(self):
396
- values = []
397
- for item in self.nodes:
398
- if isinstance(item, LayoutSpaces):
399
- values.append(item.b.bottom)
400
- else:
401
- values.append(max(item.bottoms))
402
- return values
407
+ @abc.abstractmethod
408
+ def bottom_axis_title_clearance(self) -> float:
409
+ """
410
+ Distance between the left x-axis title and the panel
411
+ """
403
412
 
404
- def set_bottom_alignment_margin(self, value: float):
405
- for item in self.nodes:
406
- if isinstance(item, LayoutSpaces):
407
- item.b.alignment_margin = value
408
- else:
409
- item.set_bottom_alignment_margin(value)
413
+ @cached_property
414
+ def left_axis_title_clearances(self) -> list[float]:
415
+ """
416
+ Distances between the left y-axis titles and the panels
417
+ """
418
+ return [node.left_axis_title_clearance for node in self.nodes]
410
419
 
411
420
  @cached_property
412
- def rights(self):
413
- right_item = self.nodes[-1]
414
- if isinstance(right_item, LayoutSpaces):
415
- return [right_item.r.right]
416
- else:
417
- return right_item.rights
421
+ def bottom_axis_title_clearances(self) -> list[float]:
422
+ """
423
+ Distances between the bottom x-axis titles and the panels
424
+ """
425
+ return [node.bottom_axis_title_clearance for node in self.nodes]
418
426
 
419
- def set_right_alignment_margin(self, value: float):
420
- right_item = self.nodes[-1]
421
- if isinstance(right_item, LayoutSpaces):
422
- right_item.r.alignment_margin = value
423
- else:
424
- right_item.set_right_alignment_margin(value)
427
+ @abc.abstractmethod
428
+ def set_left_margin_alignment(self, value: float):
429
+ """
430
+ Set a margin to align the left of the panels in this composition
431
+
432
+ In figure dimenstions
433
+ """
434
+
435
+ @abc.abstractmethod
436
+ def set_right_margin_alignment(self, value: float):
437
+ """
438
+ Set a margin to align the right of the panels in this composition
439
+
440
+ In figure dimenstions
441
+ """
442
+
443
+ @abc.abstractmethod
444
+ def set_bottom_margin_alignment(self, value: float):
445
+ """
446
+ Set a margin to align the bottom of the panels in this composition
447
+
448
+ In figure dimenstions
449
+ """
450
+
451
+ @abc.abstractmethod
452
+ def set_top_margin_alignment(self, value: float):
453
+ """
454
+ Set a margin to align the top of the panels in this composition
455
+
456
+ In figure dimenstions
457
+ """
458
+
459
+ @abc.abstractmethod
460
+ def set_left_tag_alignment(self, value: float):
461
+ """
462
+ Set the space to align the left tags in this composition
463
+
464
+ In figure dimenstions
465
+ """
425
466
 
426
- def align_tops(self):
427
- values = np.array(self.tops) - min(self.tops)
467
+ @abc.abstractmethod
468
+ def set_right_tag_alignment(self, value: float):
469
+ """
470
+ Set the space to align the right tags in this composition
471
+
472
+ In figure dimenstions
473
+ """
474
+
475
+ @abc.abstractmethod
476
+ def set_bottom_tag_alignment(self, value: float):
477
+ """
478
+ Set the space to align the bottom tags in this composition
479
+
480
+ In figure dimenstions
481
+ """
482
+
483
+ @abc.abstractmethod
484
+ def set_top_tag_alignment(self, value: float):
485
+ """
486
+ Set the space to align the top tags in this composition
487
+
488
+ In figure dimenstions
489
+ """
490
+
491
+ @abc.abstractmethod
492
+ def set_left_axis_title_alignment(self, value: float):
493
+ """
494
+ Set the space to align the left axis titles in this composition
495
+
496
+ In figure dimenstions
497
+ """
498
+
499
+ @abc.abstractmethod
500
+ def set_bottom_axis_title_alignment(self, value: float):
501
+ """
502
+ Set the space to align the bottom axis titles in this composition
503
+
504
+ In figure dimenstions
505
+ """
506
+
507
+
508
+ @dataclass
509
+ class ColumnsTree(LayoutTree):
510
+ """
511
+ Tree with columns at the outermost level
512
+
513
+ e.g. p1 | (p2 / p3)
514
+
515
+
516
+ -------------------
517
+ | | |
518
+ | | |
519
+ | | |
520
+ | | |
521
+ | |---------|
522
+ | | |
523
+ | | |
524
+ | | |
525
+ | | |
526
+ -------------------
527
+ """
528
+
529
+ def align(self):
530
+ self.align_top_tags()
531
+ self.align_bottom_tags()
532
+ self.align_panel_tops()
533
+ self.align_panel_bottoms()
534
+ self.align_sub_compositions()
535
+
536
+ def align_axis_titles(self):
537
+ self.align_bottom_axis_titles()
538
+ for tree in self.sub_compositions:
539
+ tree.align_axis_titles()
540
+
541
+ def resize(self):
542
+ """
543
+ Resize the widths of gridspec so that panels have equal widths
544
+ """
545
+ # The new width of each panel is the average width of all
546
+ # the panels plus all the space to the left and right
547
+ # of the panels.
548
+ plot_widths = np.array(self.plot_widths)
549
+ panel_widths = np.array(self.panel_widths)
550
+ non_panel_space = plot_widths - panel_widths
551
+ new_plot_widths = panel_widths.mean() + non_panel_space
552
+ width_ratios = new_plot_widths / new_plot_widths.min()
553
+ self.gridspec.set_width_ratios(width_ratios)
554
+ self.resize_sub_compositions()
555
+
556
+ def align_panel_bottoms(self):
557
+ """
558
+ Align the immediate bottom edges this composition
559
+
560
+ ----------- -----------
561
+ | | | | | |
562
+ | | | | | |
563
+ | | | -> | | |
564
+ | |#####| |#####|#####|
565
+ |#####| | | | |
566
+ ----------- -----------
567
+ """
568
+ # If panels are aligned and have a non-zero margin_alignment,
569
+ # aligning them again will set that value to zero and undoes
570
+ # the alignment.
571
+ if self.panel_bottoms_align:
572
+ return
573
+
574
+ values = max(self.panel_bottoms) - np.array(self.panel_bottoms)
575
+ for item, value in zip(self.nodes, values):
576
+ if isinstance(item, LayoutSpaces):
577
+ item.b.margin_alignment = value
578
+ else:
579
+ item.set_bottom_margin_alignment(value)
580
+
581
+ del self.panel_bottoms
582
+
583
+ def align_panel_tops(self):
584
+ """
585
+ Align the immediate top edges in this composition
586
+
587
+ ----------- -----------
588
+ |#####| | | | |
589
+ | |#####| |#####|#####|
590
+ | | | -> | | |
591
+ | | | | | |
592
+ | | | | | |
593
+ ----------- -----------
594
+ """
595
+ if self.panel_tops_align:
596
+ return
597
+
598
+ values = np.array(self.panel_tops) - min(self.panel_tops)
599
+ for item, value in zip(self.nodes, values):
600
+ if isinstance(item, LayoutSpaces):
601
+ item.t.margin_alignment = value
602
+ else:
603
+ item.set_top_margin_alignment(value)
604
+
605
+ del self.panel_tops
606
+
607
+ def align_bottom_tags(self):
608
+ if self.bottom_tags_align:
609
+ return
610
+
611
+ values = cast(
612
+ "Sequence[float]",
613
+ max(self.bottom_tag_heights) - np.array(self.bottom_tag_heights),
614
+ )
615
+ for item, value in zip(self.nodes, values):
616
+ if isinstance(item, LayoutSpaces):
617
+ item.l.tag_alignment = value
618
+ else:
619
+ item.set_bottom_tag_alignment(value)
620
+
621
+ def align_top_tags(self):
622
+ if self.top_tags_align:
623
+ return
624
+
625
+ values = cast(
626
+ "Sequence[float]",
627
+ max(self.top_tag_heights) - np.array(self.top_tag_heights),
628
+ )
428
629
  for item, value in zip(self.nodes, values):
429
630
  if isinstance(item, LayoutSpaces):
430
- item.t.alignment_margin = value
631
+ item.t.tag_alignment = value
431
632
  else:
432
- item.set_top_alignment_margin(value)
633
+ item.set_top_tag_alignment(value)
634
+
635
+ def align_bottom_axis_titles(self):
636
+ if self.bottom_axis_titles_align:
637
+ pass
638
+
639
+ values = max(self.bottom_axis_title_clearances) - np.array(
640
+ self.bottom_axis_title_clearances
641
+ )
642
+ # We ignore 0 values since they can undo values
643
+ # set to align this composition with an outer one.
644
+ for item, value in zip(self.nodes, values):
645
+ if value == 0:
646
+ continue
647
+ if isinstance(item, LayoutSpaces):
648
+ item.b.axis_title_alignment = value
649
+ else:
650
+ item.set_bottom_axis_title_alignment(value)
651
+
652
+ @cached_property
653
+ def panel_lefts(self):
654
+ left_item = self.nodes[0]
655
+ if isinstance(left_item, LayoutSpaces):
656
+ return [left_item.l.panel_left]
657
+ else:
658
+ return left_item.panel_lefts
659
+
660
+ @cached_property
661
+ def panel_rights(self):
662
+ right_item = self.nodes[-1]
663
+ if isinstance(right_item, LayoutSpaces):
664
+ return [right_item.r.panel_right]
665
+ else:
666
+ return right_item.panel_rights
433
667
 
434
668
  @cached_property
435
- def tops(self):
669
+ def panel_bottoms(self):
436
670
  values = []
437
671
  for item in self.nodes:
438
672
  if isinstance(item, LayoutSpaces):
439
- values.append(item.t.top)
673
+ values.append(item.b.panel_bottom)
440
674
  else:
441
- values.append(min(item.tops))
675
+ values.append(max(item.panel_bottoms))
442
676
  return values
443
677
 
444
- def set_top_alignment_margin(self, value: float):
678
+ @cached_property
679
+ def panel_tops(self):
680
+ values = []
445
681
  for item in self.nodes:
446
682
  if isinstance(item, LayoutSpaces):
447
- item.t.alignment_margin = value
683
+ values.append(item.t.panel_top)
448
684
  else:
449
- item.set_top_alignment_margin(value)
685
+ values.append(min(item.panel_tops))
686
+ return values
450
687
 
451
688
  @property
452
689
  def panel_width(self) -> float:
453
- """
454
- A representative for width for panels of the nodes
455
- """
456
690
  return sum(self.panel_widths)
457
691
 
458
692
  @property
459
693
  def panel_height(self) -> float:
460
- """
461
- A representative for height for panels of the nodes
462
- """
463
694
  return float(np.mean(self.panel_heights))
464
695
 
465
696
  @property
466
697
  def plot_width(self) -> float:
467
- """
468
- A representative for width for plots of the nodes
469
- """
470
698
  return sum(self.plot_widths)
471
699
 
472
700
  @property
473
701
  def plot_height(self) -> float:
474
- """
475
- A representative for height for plots of the nodes
476
- """
477
702
  return max(self.plot_heights)
478
703
 
479
- def resize_widths(self):
480
- # The new width of each panel is the average width of all
481
- # the panels plus all the space to the left and right
482
- # of the panels.
483
- plot_widths = np.array(self.plot_widths)
484
- panel_widths = np.array(self.panel_widths)
485
- non_panel_space = plot_widths - panel_widths
486
- new_plot_widths = panel_widths.mean() + non_panel_space
487
- width_ratios = new_plot_widths / new_plot_widths.min()
488
- self.gridspec.set_width_ratios(width_ratios)
704
+ @cached_property
705
+ def left_tag_width(self) -> float:
706
+ return self.left_tag_widths[0]
707
+
708
+ @cached_property
709
+ def right_tag_width(self) -> float:
710
+ return self.right_tag_widths[-1]
711
+
712
+ @cached_property
713
+ def bottom_tag_height(self) -> float:
714
+ return max(self.bottom_tag_heights)
715
+
716
+ @cached_property
717
+ def top_tag_height(self) -> float:
718
+ return max(self.top_tag_heights)
719
+
720
+ @cached_property
721
+ def left_axis_title_clearance(self) -> float:
722
+ return self.left_axis_title_clearances[0]
723
+
724
+ @cached_property
725
+ def bottom_axis_title_clearance(self) -> float:
726
+ return max(self.bottom_axis_title_clearances)
727
+
728
+ def set_left_margin_alignment(self, value: float):
729
+ left_item = self.nodes[0]
730
+ if isinstance(left_item, LayoutSpaces):
731
+ left_item.l.margin_alignment = value
732
+ else:
733
+ left_item.set_left_margin_alignment(value)
734
+
735
+ def set_right_margin_alignment(self, value: float):
736
+ right_item = self.nodes[-1]
737
+ if isinstance(right_item, LayoutSpaces):
738
+ right_item.r.margin_alignment = value
739
+ else:
740
+ right_item.set_right_margin_alignment(value)
741
+
742
+ def set_bottom_margin_alignment(self, value: float):
743
+ for item in self.nodes:
744
+ if isinstance(item, LayoutSpaces):
745
+ item.b.margin_alignment = value
746
+ else:
747
+ item.set_bottom_margin_alignment(value)
748
+
749
+ def set_top_margin_alignment(self, value: float):
750
+ for item in self.nodes:
751
+ if isinstance(item, LayoutSpaces):
752
+ item.t.margin_alignment = value
753
+ else:
754
+ item.set_top_margin_alignment(value)
755
+
756
+ def set_bottom_tag_alignment(self, value: float):
757
+ for item in self.nodes:
758
+ if isinstance(item, LayoutSpaces):
759
+ item.l.tag_alignment = value
760
+ else:
761
+ item.set_bottom_tag_alignment(value)
762
+
763
+ def set_top_tag_alignment(self, value: float):
764
+ for item in self.nodes:
765
+ if isinstance(item, LayoutSpaces):
766
+ item.t.tag_alignment = value
767
+ else:
768
+ item.set_top_tag_alignment(value)
769
+
770
+ def set_bottom_axis_title_alignment(self, value: float):
771
+ for item in self.nodes:
772
+ if isinstance(item, LayoutSpaces):
773
+ item.b.axis_title_alignment = value
774
+ else:
775
+ item.set_bottom_axis_title_alignment(value)
776
+
777
+ def set_left_axis_title_alignment(self, value: float):
778
+ left_item = self.nodes[0]
779
+ if isinstance(left_item, LayoutSpaces):
780
+ left_item.l.axis_title_alignment = value
781
+ else:
782
+ left_item.set_left_axis_title_alignment(value)
489
783
 
490
784
 
491
785
  @dataclass
@@ -506,120 +800,277 @@ class RowsTree(LayoutTree):
506
800
  -------------------
507
801
  """
508
802
 
509
- def align_lefts(self):
510
- values = max(self.lefts) - np.array(self.lefts)
803
+ def align(self):
804
+ self.align_left_tags()
805
+ self.align_right_tags()
806
+ self.align_panel_lefts()
807
+ self.align_panel_rights()
808
+ self.align_sub_compositions()
809
+
810
+ def align_axis_titles(self):
811
+ self.align_left_axis_titles()
812
+ for tree in self.sub_compositions:
813
+ tree.align_axis_titles()
814
+
815
+ def resize(self):
816
+ """
817
+ Resize the heights of gridspec so that panels have equal heights
818
+
819
+ This method resizes (recursively) the contained compositions
820
+ """
821
+ # The new height of each panel is the average width of all
822
+ # the panels plus all the space above and below the panels.
823
+ plot_heights = np.array(self.plot_heights)
824
+ panel_heights = np.array(self.panel_heights)
825
+ non_panel_space = plot_heights - panel_heights
826
+ new_plot_heights = panel_heights.mean() + non_panel_space
827
+ height_ratios = new_plot_heights / new_plot_heights.max()
828
+ self.gridspec.set_height_ratios(height_ratios)
829
+ self.resize_sub_compositions()
830
+
831
+ def align_panel_lefts(self):
832
+ """
833
+ Align the immediate left edges in this composition
834
+
835
+ ----------- -----------
836
+ |# | | # |
837
+ |# | | # |
838
+ |# | | # |
839
+ |-----------| -> |-----------|
840
+ | # | | # |
841
+ | # | | # |
842
+ | # | | # |
843
+ ----------- -----------
844
+ """
845
+ if self.panel_lefts_align:
846
+ return
847
+
848
+ values = max(self.panel_lefts) - np.array(self.panel_lefts)
511
849
  for item, value in zip(self.nodes, values):
512
850
  if isinstance(item, LayoutSpaces):
513
- item.l.alignment_margin = value
851
+ item.l.margin_alignment = value
514
852
  else:
515
- item.set_left_alignment_margin(value)
853
+ item.set_left_margin_alignment(value)
516
854
 
517
- @cached_property
518
- def lefts(self):
519
- values = []
520
- for item in self.nodes:
855
+ del self.panel_lefts
856
+
857
+ def align_panel_rights(self):
858
+ """
859
+ Align the immediate right edges in this composition
860
+
861
+ ----------- -----------
862
+ | # | | # |
863
+ | # | | # |
864
+ | # | | # |
865
+ |-----------| -> |-----------|
866
+ | #| | # |
867
+ | #| | # |
868
+ | #| | # |
869
+ ----------- -----------
870
+ """
871
+ if self.panel_rights_align:
872
+ return
873
+
874
+ values = np.array(self.panel_rights) - min(self.panel_rights)
875
+ for item, value in zip(self.nodes, values):
521
876
  if isinstance(item, LayoutSpaces):
522
- values.append(item.l.left)
877
+ item.r.margin_alignment = value
523
878
  else:
524
- values.append(max(item.lefts))
525
- return values
879
+ item.set_right_margin_alignment(value)
526
880
 
527
- def set_left_alignment_margin(self, value: float):
528
- for item in self.nodes:
881
+ del self.panel_rights
882
+
883
+ def align_left_tags(self):
884
+ """
885
+ Make all the left tags takeup the same amount of space
886
+
887
+
888
+ Given
889
+
890
+ V
891
+ ------------------------------------
892
+ | plot_margin | tag | artists |
893
+ |------------------------------------|
894
+ | plot_margin | A long tag | artists |
895
+ ------------------------------------
896
+
897
+ V
898
+ ------------------------------------
899
+ | plot_margin | #######tag | artists |
900
+ |------------------------------------|
901
+ | plot_margin | A long tag | artists |
902
+ ------------------------------------
903
+ """
904
+ if self.left_tags_align:
905
+ return
906
+
907
+ values = cast(
908
+ "Sequence[float]",
909
+ max(self.left_tag_widths) - np.array(self.left_tag_widths),
910
+ )
911
+ for item, value in zip(self.nodes, values):
529
912
  if isinstance(item, LayoutSpaces):
530
- item.l.alignment_margin = value
913
+ item.l.tag_alignment = value
531
914
  else:
532
- item.set_left_alignment_margin(value)
915
+ item.set_left_tag_alignment(value)
533
916
 
534
- @cached_property
535
- def bottoms(self):
536
- bottom_item = self.nodes[-1]
537
- if isinstance(bottom_item, LayoutSpaces):
538
- return [bottom_item.b.bottom]
539
- else:
540
- return bottom_item.bottoms
917
+ def align_right_tags(self):
918
+ if self.right_tags_align:
919
+ return
541
920
 
542
- def set_bottom_alignment_margin(self, value: float):
543
- bottom_item = self.nodes[-1]
544
- if isinstance(bottom_item, LayoutSpaces):
545
- bottom_item.b.alignment_margin = value
546
- else:
547
- bottom_item.set_bottom_alignment_margin(value)
921
+ values = cast(
922
+ "Sequence[float]",
923
+ max(self.right_tag_widths) - np.array(self.right_tag_widths),
924
+ )
925
+ for item, value in zip(self.nodes, values):
926
+ if isinstance(item, LayoutSpaces):
927
+ item.r.tag_alignment = value
928
+ else:
929
+ item.set_right_tag_alignment(value)
548
930
 
549
- def align_rights(self):
550
- values = np.array(self.rights) - min(self.rights)
931
+ def align_left_axis_titles(self):
932
+ if self.left_axis_titles_align:
933
+ pass
934
+
935
+ values = max(self.left_axis_title_clearances) - np.array(
936
+ self.left_axis_title_clearances
937
+ )
551
938
  for item, value in zip(self.nodes, values):
939
+ if value == 0:
940
+ continue
552
941
  if isinstance(item, LayoutSpaces):
553
- item.r.alignment_margin = value
942
+ item.l.axis_title_alignment = value
554
943
  else:
555
- item.set_right_alignment_margin(value)
944
+ item.set_left_axis_title_alignment(value)
556
945
 
557
946
  @cached_property
558
- def rights(self):
947
+ def panel_lefts(self):
559
948
  values = []
560
949
  for item in self.nodes:
561
950
  if isinstance(item, LayoutSpaces):
562
- values.append(item.r.right)
951
+ values.append(item.l.panel_left)
563
952
  else:
564
- values.append(min(item.rights))
953
+ values.append(max(item.panel_lefts))
565
954
  return values
566
955
 
567
- def set_right_alignment_margin(self, value: float):
956
+ @cached_property
957
+ def panel_rights(self):
958
+ values = []
568
959
  for item in self.nodes:
569
960
  if isinstance(item, LayoutSpaces):
570
- item.r.alignment_margin = value
961
+ values.append(item.r.panel_right)
571
962
  else:
572
- item.set_right_alignment_margin(value)
963
+ values.append(min(item.panel_rights))
964
+ return values
573
965
 
574
966
  @cached_property
575
- def tops(self):
576
- top_item = self.nodes[0]
577
- if isinstance(top_item, LayoutSpaces):
578
- return [top_item.t.top]
967
+ def panel_bottoms(self):
968
+ bottom_item = self.nodes[-1]
969
+ if isinstance(bottom_item, LayoutSpaces):
970
+ return [bottom_item.b.panel_bottom]
579
971
  else:
580
- return top_item.tops
972
+ return bottom_item.panel_bottoms
581
973
 
582
- def set_top_alignment_margin(self, value: float):
974
+ @cached_property
975
+ def panel_tops(self):
583
976
  top_item = self.nodes[0]
584
977
  if isinstance(top_item, LayoutSpaces):
585
- top_item.t.alignment_margin = value
978
+ return [top_item.t.panel_top]
586
979
  else:
587
- top_item.set_top_alignment_margin(value)
980
+ return top_item.panel_tops
588
981
 
589
982
  @property
590
983
  def panel_width(self) -> float:
591
- """
592
- A representative for width for panels of the nodes
593
- """
594
984
  return float(np.mean(self.panel_widths))
595
985
 
596
986
  @property
597
987
  def panel_height(self) -> float:
598
- """
599
- A representative for height for panels of the nodes
600
- """
601
988
  return sum(self.panel_heights)
602
989
 
603
990
  @property
604
991
  def plot_width(self) -> float:
605
- """
606
- A representative for width for plots of the nodes
607
- """
608
992
  return max(self.plot_widths)
609
993
 
610
994
  @property
611
995
  def plot_height(self) -> float:
612
- """
613
- A representative for height for plots of the nodes
614
- """
615
996
  return sum(self.plot_heights)
616
997
 
617
- def resize_heights(self):
618
- # The new width of each panel is the average width of all
619
- # the panels plus all the space above and below the panels.
620
- plot_heights = np.array(self.plot_heights)
621
- panel_heights = np.array(self.panel_heights)
622
- non_panel_space = plot_heights - panel_heights
623
- new_plot_heights = panel_heights.mean() + non_panel_space
624
- height_ratios = new_plot_heights / new_plot_heights.max()
625
- self.gridspec.set_height_ratios(height_ratios)
998
+ @cached_property
999
+ def left_tag_width(self) -> float:
1000
+ return max(self.left_tag_widths)
1001
+
1002
+ @cached_property
1003
+ def right_tag_width(self) -> float:
1004
+ return max(self.right_tag_widths)
1005
+
1006
+ @cached_property
1007
+ def top_tag_height(self) -> float:
1008
+ return self.top_tag_heights[0]
1009
+
1010
+ @cached_property
1011
+ def bottom_tag_height(self) -> float:
1012
+ return self.bottom_tag_heights[-1]
1013
+
1014
+ @cached_property
1015
+ def left_axis_title_clearance(self) -> float:
1016
+ return max(self.left_axis_title_clearances)
1017
+
1018
+ @cached_property
1019
+ def bottom_axis_title_clearance(self) -> float:
1020
+ return self.bottom_axis_title_clearances[-1]
1021
+
1022
+ def set_left_margin_alignment(self, value: float):
1023
+ for item in self.nodes:
1024
+ if isinstance(item, LayoutSpaces):
1025
+ item.l.margin_alignment = value
1026
+ else:
1027
+ item.set_left_margin_alignment(value)
1028
+
1029
+ def set_right_margin_alignment(self, value: float):
1030
+ for item in self.nodes:
1031
+ if isinstance(item, LayoutSpaces):
1032
+ item.r.margin_alignment = value
1033
+ else:
1034
+ item.set_right_margin_alignment(value)
1035
+
1036
+ def set_bottom_margin_alignment(self, value: float):
1037
+ bottom_item = self.nodes[-1]
1038
+ if isinstance(bottom_item, LayoutSpaces):
1039
+ bottom_item.b.margin_alignment = value
1040
+ else:
1041
+ bottom_item.set_bottom_margin_alignment(value)
1042
+
1043
+ def set_top_margin_alignment(self, value: float):
1044
+ top_item = self.nodes[0]
1045
+ if isinstance(top_item, LayoutSpaces):
1046
+ top_item.t.margin_alignment = value
1047
+ else:
1048
+ top_item.set_top_margin_alignment(value)
1049
+
1050
+ def set_left_tag_alignment(self, value: float):
1051
+ for item in self.nodes:
1052
+ if isinstance(item, LayoutSpaces):
1053
+ item.l.tag_alignment = value
1054
+ else:
1055
+ item.set_left_tag_alignment(value)
1056
+
1057
+ def set_right_tag_alignment(self, value: float):
1058
+ for item in self.nodes:
1059
+ if isinstance(item, LayoutSpaces):
1060
+ item.r.tag_alignment = value
1061
+ else:
1062
+ item.set_right_tag_alignment(value)
1063
+
1064
+ def set_left_axis_title_alignment(self, value: float):
1065
+ for item in self.nodes:
1066
+ if isinstance(item, LayoutSpaces):
1067
+ item.l.axis_title_alignment = value
1068
+ else:
1069
+ item.set_left_axis_title_alignment(value)
1070
+
1071
+ def set_bottom_axis_title_alignment(self, value: float):
1072
+ bottom_item = self.nodes[-1]
1073
+ if isinstance(bottom_item, LayoutSpaces):
1074
+ bottom_item.b.axis_title_alignment = value
1075
+ else:
1076
+ bottom_item.set_bottom_axis_title_alignment(value)