plotnine 0.15.0.dev3__py3-none-any.whl → 0.15.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plotnine/__init__.py +2 -0
- plotnine/_mpl/layout_manager/_engine.py +1 -1
- plotnine/_mpl/layout_manager/_layout_items.py +126 -41
- plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
- plotnine/_mpl/layout_manager/_spaces.py +305 -101
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +144 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +50 -107
- plotnine/_utils/context.py +78 -2
- plotnine/_utils/ipython.py +35 -51
- plotnine/_utils/quarto.py +26 -0
- plotnine/_utils/yippie.py +115 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/composition/_beside.py +55 -0
- plotnine/composition/_compose.py +471 -0
- plotnine/composition/_plot_spacer.py +60 -0
- plotnine/composition/_stack.py +55 -0
- plotnine/coords/coord.py +3 -3
- plotnine/data/__init__.py +31 -0
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +4 -4
- plotnine/facets/facet.py +4 -4
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotate.py +13 -13
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +60 -27
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_area.py +2 -2
- plotnine/geoms/geom_bar.py +1 -0
- plotnine/geoms/geom_bin_2d.py +6 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +8 -4
- plotnine/geoms/geom_col.py +2 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +2 -2
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +5 -5
- plotnine/geoms/geom_path.py +11 -12
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointdensity.py +4 -0
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_qq.py +4 -0
- plotnine/geoms/geom_qq_line.py +4 -0
- plotnine/geoms/geom_quantile.py +4 -0
- plotnine/geoms/geom_raster.py +4 -5
- plotnine/geoms/geom_rect.py +3 -4
- plotnine/geoms/geom_ribbon.py +7 -7
- plotnine/geoms/geom_rug.py +1 -1
- plotnine/geoms/geom_segment.py +2 -2
- plotnine/geoms/geom_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +7 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +8 -5
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/ggplot.py +64 -85
- plotnine/guides/guide.py +7 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +3 -3
- plotnine/guides/guides.py +6 -6
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/labels.py +3 -3
- plotnine/layer.py +36 -19
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/_env.py +13 -2
- plotnine/mapping/_eval_environment.py +1 -1
- plotnine/mapping/aes.py +85 -49
- plotnine/scales/__init__.py +2 -0
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +3 -3
- plotnine/scales/scale_color.py +82 -18
- plotnine/scales/scale_continuous.py +6 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/scales/scale_identity.py +21 -2
- plotnine/scales/scale_manual.py +8 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +23 -36
- plotnine/stats/stat.py +20 -32
- plotnine/stats/stat_bin.py +6 -5
- plotnine/stats/stat_bin_2d.py +11 -9
- plotnine/stats/stat_bindot.py +13 -16
- plotnine/stats/stat_boxplot.py +6 -6
- plotnine/stats/stat_count.py +6 -9
- plotnine/stats/stat_density.py +7 -10
- plotnine/stats/stat_density_2d.py +12 -8
- plotnine/stats/stat_ecdf.py +7 -6
- plotnine/stats/stat_ellipse.py +9 -6
- plotnine/stats/stat_function.py +10 -8
- plotnine/stats/stat_hull.py +6 -3
- plotnine/stats/stat_identity.py +5 -2
- plotnine/stats/stat_pointdensity.py +5 -7
- plotnine/stats/stat_qq.py +46 -20
- plotnine/stats/stat_qq_line.py +16 -11
- plotnine/stats/stat_quantile.py +15 -9
- plotnine/stats/stat_sina.py +13 -15
- plotnine/stats/stat_smooth.py +8 -10
- plotnine/stats/stat_sum.py +5 -2
- plotnine/stats/stat_summary.py +7 -10
- plotnine/stats/stat_summary_bin.py +11 -14
- plotnine/stats/stat_unique.py +5 -2
- plotnine/stats/stat_ydensity.py +8 -11
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme.py +9 -1
- plotnine/themes/theme_538.py +0 -1
- plotnine/themes/theme_bw.py +0 -1
- plotnine/themes/theme_dark.py +0 -1
- plotnine/themes/theme_gray.py +6 -5
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +5 -5
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +9 -8
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +109 -31
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
- plotnine-0.15.2.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_compose.py +0 -436
- plotnine/plot_composition/_spacer.py +0 -32
- plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
|
@@ -3,11 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
import abc
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from functools import cached_property
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING, cast
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
|
-
from plotnine.
|
|
10
|
+
from plotnine.composition import Beside
|
|
11
11
|
|
|
12
12
|
from ._spaces import LayoutSpaces
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
16
16
|
|
|
17
17
|
from plotnine import ggplot
|
|
18
18
|
from plotnine._mpl.gridspec import p9GridSpec
|
|
19
|
-
from plotnine.
|
|
19
|
+
from plotnine.composition import Compose
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
@@ -80,141 +80,181 @@ class LayoutTree:
|
|
|
80
80
|
represents.
|
|
81
81
|
"""
|
|
82
82
|
|
|
83
|
-
@
|
|
84
|
-
def
|
|
83
|
+
@staticmethod
|
|
84
|
+
def create(
|
|
85
|
+
cmp: Compose,
|
|
86
|
+
lookup_spaces: dict[ggplot, LayoutSpaces],
|
|
87
|
+
) -> LayoutTree:
|
|
85
88
|
"""
|
|
86
|
-
|
|
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.
|
|
87
102
|
"""
|
|
88
|
-
|
|
103
|
+
from plotnine import ggplot
|
|
89
104
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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):
|
|
93
118
|
"""
|
|
94
|
-
|
|
119
|
+
Align and resize plots in composition to look good
|
|
95
120
|
"""
|
|
121
|
+
self.align_axis_titles()
|
|
122
|
+
self.align()
|
|
123
|
+
self.resize()
|
|
96
124
|
|
|
97
125
|
@abc.abstractmethod
|
|
98
|
-
def
|
|
126
|
+
def align(self):
|
|
99
127
|
"""
|
|
100
|
-
|
|
128
|
+
Align all the edges in this composition & contained compositions
|
|
101
129
|
|
|
102
|
-
|
|
130
|
+
This function mutates the layout spaces, specifically the
|
|
131
|
+
margin_alignments along the sides of the plot.
|
|
103
132
|
"""
|
|
104
133
|
|
|
105
|
-
@cached_property
|
|
106
134
|
@abc.abstractmethod
|
|
107
|
-
def
|
|
108
|
-
"""
|
|
109
|
-
Bottom values [figure space] of nodes in this tree
|
|
135
|
+
def resize(self):
|
|
110
136
|
"""
|
|
137
|
+
Resize panels and the entire plots
|
|
111
138
|
|
|
112
|
-
|
|
113
|
-
|
|
139
|
+
This function mutates the composition gridspecs; specifically the
|
|
140
|
+
width_ratios and height_ratios.
|
|
114
141
|
"""
|
|
115
|
-
Set a margin to align the bottom of the panels in this composition
|
|
116
142
|
|
|
117
|
-
|
|
143
|
+
def align_sub_compositions(self):
|
|
144
|
+
"""
|
|
145
|
+
Align the compositions contained in this one
|
|
118
146
|
"""
|
|
147
|
+
# Recurse into the contained compositions
|
|
148
|
+
for tree in self.sub_compositions:
|
|
149
|
+
tree.align()
|
|
119
150
|
|
|
120
|
-
@cached_property
|
|
121
151
|
@abc.abstractmethod
|
|
122
|
-
def
|
|
152
|
+
def align_axis_titles(self):
|
|
123
153
|
"""
|
|
124
|
-
|
|
154
|
+
Align the axis titles along the composing dimension
|
|
155
|
+
|
|
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!
|
|
125
163
|
"""
|
|
126
164
|
|
|
127
|
-
|
|
128
|
-
def set_top_alignment_margin(self, value: float):
|
|
165
|
+
def resize_sub_compositions(self):
|
|
129
166
|
"""
|
|
130
|
-
|
|
167
|
+
Resize panels in the compositions contained in this one
|
|
168
|
+
"""
|
|
169
|
+
for tree in self.sub_compositions:
|
|
170
|
+
tree.resize()
|
|
131
171
|
|
|
132
|
-
|
|
172
|
+
@cached_property
|
|
173
|
+
def sub_compositions(self) -> list[LayoutTree]:
|
|
133
174
|
"""
|
|
175
|
+
LayoutTrees of the direct sub compositions of this one
|
|
176
|
+
"""
|
|
177
|
+
return [item for item in self.nodes if isinstance(item, LayoutTree)]
|
|
134
178
|
|
|
135
179
|
@cached_property
|
|
136
180
|
@abc.abstractmethod
|
|
137
|
-
def
|
|
181
|
+
def panel_lefts(self) -> Sequence[float]:
|
|
138
182
|
"""
|
|
139
|
-
|
|
183
|
+
Left values [figure space] of nodes in this tree
|
|
140
184
|
"""
|
|
141
185
|
|
|
186
|
+
@cached_property
|
|
142
187
|
@abc.abstractmethod
|
|
143
|
-
def
|
|
188
|
+
def panel_rights(self) -> Sequence[float]:
|
|
144
189
|
"""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
In figure dimenstions
|
|
190
|
+
Right values [figure space] of nodes in this tree
|
|
148
191
|
"""
|
|
149
192
|
|
|
193
|
+
@cached_property
|
|
150
194
|
@abc.abstractmethod
|
|
151
|
-
def
|
|
195
|
+
def panel_bottoms(self) -> Sequence[float]:
|
|
152
196
|
"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
This function mutates the layout spaces, specifically the
|
|
156
|
-
alignment_margins along the sides of the plot.
|
|
197
|
+
Bottom values [figure space] of nodes in this tree
|
|
157
198
|
"""
|
|
158
199
|
|
|
159
|
-
|
|
200
|
+
@cached_property
|
|
201
|
+
@abc.abstractmethod
|
|
202
|
+
def panel_tops(self) -> Sequence[float]:
|
|
160
203
|
"""
|
|
161
|
-
|
|
204
|
+
Top values [figure space] of nodes in this tree
|
|
162
205
|
"""
|
|
163
|
-
# Recurse into the contained compositions
|
|
164
|
-
for tree in self.sub_compositions:
|
|
165
|
-
tree.align()
|
|
166
206
|
|
|
167
207
|
@property
|
|
168
|
-
def
|
|
208
|
+
def panel_lefts_align(self) -> bool:
|
|
169
209
|
"""
|
|
170
|
-
Return True if panel
|
|
210
|
+
Return True if panel lefts for the nodes are aligned
|
|
171
211
|
"""
|
|
172
|
-
arr = np.array(self.
|
|
212
|
+
arr = np.array(self.panel_lefts)
|
|
173
213
|
return all(arr == arr[0])
|
|
174
214
|
|
|
175
215
|
@property
|
|
176
|
-
def
|
|
216
|
+
def panel_rights_align(self) -> bool:
|
|
177
217
|
"""
|
|
178
|
-
Return True if panel
|
|
218
|
+
Return True if panel rights for the nodes are aligned
|
|
179
219
|
"""
|
|
180
|
-
arr = np.array(self.
|
|
220
|
+
arr = np.array(self.panel_rights)
|
|
181
221
|
return all(arr == arr[0])
|
|
182
222
|
|
|
183
223
|
@property
|
|
184
|
-
def
|
|
224
|
+
def panel_bottoms_align(self) -> bool:
|
|
185
225
|
"""
|
|
186
|
-
Return True if panel
|
|
226
|
+
Return True if panel bottoms for the nodes are aligned
|
|
187
227
|
"""
|
|
188
|
-
arr = np.array(self.
|
|
228
|
+
arr = np.array(self.panel_bottoms)
|
|
189
229
|
return all(arr == arr[0])
|
|
190
230
|
|
|
191
231
|
@property
|
|
192
|
-
def
|
|
232
|
+
def panel_tops_align(self) -> bool:
|
|
193
233
|
"""
|
|
194
|
-
Return True if panel
|
|
234
|
+
Return True if panel tops for the nodes are aligned
|
|
195
235
|
"""
|
|
196
|
-
arr = np.array(self.
|
|
236
|
+
arr = np.array(self.panel_tops)
|
|
197
237
|
return all(arr == arr[0])
|
|
198
238
|
|
|
199
239
|
@property
|
|
200
240
|
@abc.abstractmethod
|
|
201
241
|
def panel_width(self) -> float:
|
|
202
242
|
"""
|
|
203
|
-
A representative
|
|
243
|
+
A representative width for panels of the nodes
|
|
204
244
|
"""
|
|
205
245
|
|
|
206
246
|
@property
|
|
207
247
|
@abc.abstractmethod
|
|
208
248
|
def panel_height(self) -> float:
|
|
209
249
|
"""
|
|
210
|
-
A representative
|
|
250
|
+
A representative height for panels of the nodes
|
|
211
251
|
"""
|
|
212
252
|
|
|
213
253
|
@property
|
|
214
254
|
@abc.abstractmethod
|
|
215
255
|
def plot_width(self) -> float:
|
|
216
256
|
"""
|
|
217
|
-
A representative
|
|
257
|
+
A representative width for plots of the nodes
|
|
218
258
|
"""
|
|
219
259
|
|
|
220
260
|
@property
|
|
@@ -252,174 +292,295 @@ class LayoutTree:
|
|
|
252
292
|
"""
|
|
253
293
|
return [node.panel_height for node in self.nodes]
|
|
254
294
|
|
|
255
|
-
|
|
295
|
+
@cached_property
|
|
296
|
+
@abc.abstractmethod
|
|
297
|
+
def left_tag_width(self) -> float:
|
|
298
|
+
"""
|
|
299
|
+
A representative width [figure space] for the left tags of the nodes
|
|
256
300
|
"""
|
|
257
|
-
Resize panels and the entire plots
|
|
258
301
|
|
|
259
|
-
|
|
260
|
-
|
|
302
|
+
@cached_property
|
|
303
|
+
@abc.abstractmethod
|
|
304
|
+
def right_tag_width(self) -> float:
|
|
305
|
+
"""
|
|
306
|
+
A representative width [figure space] for the right tags of the nodes
|
|
261
307
|
"""
|
|
262
308
|
|
|
263
|
-
|
|
309
|
+
@cached_property
|
|
310
|
+
@abc.abstractmethod
|
|
311
|
+
def bottom_tag_height(self) -> float:
|
|
264
312
|
"""
|
|
265
|
-
|
|
313
|
+
A representative height [figure space] for the top tags of the nodes
|
|
266
314
|
"""
|
|
267
|
-
for tree in self.sub_compositions:
|
|
268
|
-
tree.resize()
|
|
269
315
|
|
|
270
|
-
@
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
275
321
|
"""
|
|
276
|
-
Create a LayoutTree for this composition
|
|
277
322
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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]
|
|
284
329
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
LayoutTree works by modifying the `.gridspec` of the compositions,
|
|
288
|
-
and the `LayoutSpaces` of the plots.
|
|
330
|
+
@cached_property
|
|
331
|
+
def right_tag_widths(self) -> list[float]:
|
|
289
332
|
"""
|
|
290
|
-
|
|
333
|
+
The widths of the right tags in this tree
|
|
334
|
+
"""
|
|
335
|
+
return [node.right_tag_width for node in self.nodes]
|
|
291
336
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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]
|
|
298
343
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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]
|
|
303
350
|
|
|
304
|
-
|
|
351
|
+
@property
|
|
352
|
+
def left_tags_align(self) -> bool:
|
|
305
353
|
"""
|
|
306
|
-
|
|
354
|
+
Return True if the left tags for the nodes are aligned
|
|
307
355
|
"""
|
|
308
|
-
self.
|
|
309
|
-
|
|
356
|
+
arr = np.array(self.left_tag_widths)
|
|
357
|
+
return all(arr == arr[0])
|
|
310
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])
|
|
311
366
|
|
|
312
|
-
@
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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])
|
|
316
374
|
|
|
317
|
-
|
|
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])
|
|
318
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])
|
|
319
390
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
| | |
|
|
328
|
-
| | |
|
|
329
|
-
| | |
|
|
330
|
-
-------------------
|
|
331
|
-
"""
|
|
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])
|
|
332
398
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
399
|
+
@cached_property
|
|
400
|
+
@abc.abstractmethod
|
|
401
|
+
def left_axis_title_clearance(self) -> float:
|
|
402
|
+
"""
|
|
403
|
+
Distance between the left y-axis title and the panel
|
|
404
|
+
"""
|
|
337
405
|
|
|
338
406
|
@cached_property
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return left_item.lefts
|
|
407
|
+
@abc.abstractmethod
|
|
408
|
+
def bottom_axis_title_clearance(self) -> float:
|
|
409
|
+
"""
|
|
410
|
+
Distance between the left x-axis title and the panel
|
|
411
|
+
"""
|
|
345
412
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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]
|
|
352
419
|
|
|
353
420
|
@cached_property
|
|
354
|
-
def
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
else:
|
|
360
|
-
values.append(max(item.bottoms))
|
|
361
|
-
return values
|
|
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]
|
|
362
426
|
|
|
363
|
-
|
|
427
|
+
@abc.abstractmethod
|
|
428
|
+
def set_left_margin_alignment(self, value: float):
|
|
364
429
|
"""
|
|
365
|
-
|
|
430
|
+
Set a margin to align the left of the panels in this composition
|
|
366
431
|
|
|
367
|
-
|
|
368
|
-
| | | | | |
|
|
369
|
-
| | | | | |
|
|
370
|
-
| | | -> | | |
|
|
371
|
-
| |#####| |#####|#####|
|
|
372
|
-
|#####| | | | |
|
|
373
|
-
----------- -----------
|
|
432
|
+
In figure dimenstions
|
|
374
433
|
"""
|
|
375
|
-
# If panels are aligned and have a non-zero alignment_margin,
|
|
376
|
-
# aligning them again will set that value to zero and undoes
|
|
377
|
-
# the alignment.
|
|
378
|
-
if self.bottoms_align:
|
|
379
|
-
return
|
|
380
434
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
else:
|
|
386
|
-
item.set_bottom_alignment_margin(value)
|
|
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
|
|
387
439
|
|
|
388
|
-
|
|
440
|
+
In figure dimenstions
|
|
441
|
+
"""
|
|
389
442
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
else:
|
|
395
|
-
item.set_bottom_alignment_margin(value)
|
|
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
|
|
396
447
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
right_item = self.nodes[-1]
|
|
400
|
-
if isinstance(right_item, LayoutSpaces):
|
|
401
|
-
return [right_item.r.right]
|
|
402
|
-
else:
|
|
403
|
-
return right_item.rights
|
|
448
|
+
In figure dimenstions
|
|
449
|
+
"""
|
|
404
450
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
else:
|
|
410
|
-
right_item.set_right_alignment_margin(value)
|
|
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
|
|
411
455
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
+
"""
|
|
466
|
+
|
|
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):
|
|
416
576
|
if isinstance(item, LayoutSpaces):
|
|
417
|
-
|
|
577
|
+
item.b.margin_alignment = value
|
|
418
578
|
else:
|
|
419
|
-
|
|
420
|
-
|
|
579
|
+
item.set_bottom_margin_alignment(value)
|
|
580
|
+
|
|
581
|
+
del self.panel_bottoms
|
|
421
582
|
|
|
422
|
-
def
|
|
583
|
+
def align_panel_tops(self):
|
|
423
584
|
"""
|
|
424
585
|
Align the immediate top edges in this composition
|
|
425
586
|
|
|
@@ -431,67 +592,194 @@ class ColumnsTree(LayoutTree):
|
|
|
431
592
|
| | | | | |
|
|
432
593
|
----------- -----------
|
|
433
594
|
"""
|
|
434
|
-
if self.
|
|
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:
|
|
435
609
|
return
|
|
436
610
|
|
|
437
|
-
values =
|
|
611
|
+
values = cast(
|
|
612
|
+
"Sequence[float]",
|
|
613
|
+
max(self.bottom_tag_heights) - np.array(self.bottom_tag_heights),
|
|
614
|
+
)
|
|
438
615
|
for item, value in zip(self.nodes, values):
|
|
439
616
|
if isinstance(item, LayoutSpaces):
|
|
440
|
-
item.
|
|
617
|
+
item.l.tag_alignment = value
|
|
441
618
|
else:
|
|
442
|
-
item.
|
|
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
|
+
)
|
|
629
|
+
for item, value in zip(self.nodes, values):
|
|
630
|
+
if isinstance(item, LayoutSpaces):
|
|
631
|
+
item.t.tag_alignment = value
|
|
632
|
+
else:
|
|
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
|
|
443
659
|
|
|
444
|
-
|
|
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
|
|
445
667
|
|
|
446
|
-
|
|
668
|
+
@cached_property
|
|
669
|
+
def panel_bottoms(self):
|
|
670
|
+
values = []
|
|
447
671
|
for item in self.nodes:
|
|
448
672
|
if isinstance(item, LayoutSpaces):
|
|
449
|
-
item.
|
|
673
|
+
values.append(item.b.panel_bottom)
|
|
450
674
|
else:
|
|
451
|
-
item.
|
|
675
|
+
values.append(max(item.panel_bottoms))
|
|
676
|
+
return values
|
|
677
|
+
|
|
678
|
+
@cached_property
|
|
679
|
+
def panel_tops(self):
|
|
680
|
+
values = []
|
|
681
|
+
for item in self.nodes:
|
|
682
|
+
if isinstance(item, LayoutSpaces):
|
|
683
|
+
values.append(item.t.panel_top)
|
|
684
|
+
else:
|
|
685
|
+
values.append(min(item.panel_tops))
|
|
686
|
+
return values
|
|
452
687
|
|
|
453
688
|
@property
|
|
454
689
|
def panel_width(self) -> float:
|
|
455
|
-
"""
|
|
456
|
-
A representative for width for panels of the nodes
|
|
457
|
-
"""
|
|
458
690
|
return sum(self.panel_widths)
|
|
459
691
|
|
|
460
692
|
@property
|
|
461
693
|
def panel_height(self) -> float:
|
|
462
|
-
"""
|
|
463
|
-
A representative for height for panels of the nodes
|
|
464
|
-
"""
|
|
465
694
|
return float(np.mean(self.panel_heights))
|
|
466
695
|
|
|
467
696
|
@property
|
|
468
697
|
def plot_width(self) -> float:
|
|
469
|
-
"""
|
|
470
|
-
A representative for width for plots of the nodes
|
|
471
|
-
"""
|
|
472
698
|
return sum(self.plot_widths)
|
|
473
699
|
|
|
474
700
|
@property
|
|
475
701
|
def plot_height(self) -> float:
|
|
476
|
-
"""
|
|
477
|
-
A representative for height for plots of the nodes
|
|
478
|
-
"""
|
|
479
702
|
return max(self.plot_heights)
|
|
480
703
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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)
|
|
495
783
|
|
|
496
784
|
|
|
497
785
|
@dataclass
|
|
@@ -513,21 +801,34 @@ class RowsTree(LayoutTree):
|
|
|
513
801
|
"""
|
|
514
802
|
|
|
515
803
|
def align(self):
|
|
516
|
-
self.
|
|
517
|
-
self.
|
|
804
|
+
self.align_left_tags()
|
|
805
|
+
self.align_right_tags()
|
|
806
|
+
self.align_panel_lefts()
|
|
807
|
+
self.align_panel_rights()
|
|
518
808
|
self.align_sub_compositions()
|
|
519
809
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
return values
|
|
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
|
|
529
818
|
|
|
530
|
-
|
|
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):
|
|
531
832
|
"""
|
|
532
833
|
Align the immediate left edges in this composition
|
|
533
834
|
|
|
@@ -541,51 +842,19 @@ class RowsTree(LayoutTree):
|
|
|
541
842
|
| # | | # |
|
|
542
843
|
----------- -----------
|
|
543
844
|
"""
|
|
544
|
-
if self.
|
|
845
|
+
if self.panel_lefts_align:
|
|
545
846
|
return
|
|
546
847
|
|
|
547
|
-
values = max(self.
|
|
848
|
+
values = max(self.panel_lefts) - np.array(self.panel_lefts)
|
|
548
849
|
for item, value in zip(self.nodes, values):
|
|
549
850
|
if isinstance(item, LayoutSpaces):
|
|
550
|
-
item.l.
|
|
851
|
+
item.l.margin_alignment = value
|
|
551
852
|
else:
|
|
552
|
-
item.
|
|
553
|
-
|
|
554
|
-
del self.lefts
|
|
555
|
-
|
|
556
|
-
def set_left_alignment_margin(self, value: float):
|
|
557
|
-
for item in self.nodes:
|
|
558
|
-
if isinstance(item, LayoutSpaces):
|
|
559
|
-
item.l.alignment_margin = value
|
|
560
|
-
else:
|
|
561
|
-
item.set_left_alignment_margin(value)
|
|
562
|
-
|
|
563
|
-
@cached_property
|
|
564
|
-
def bottoms(self):
|
|
565
|
-
bottom_item = self.nodes[-1]
|
|
566
|
-
if isinstance(bottom_item, LayoutSpaces):
|
|
567
|
-
return [bottom_item.b.bottom]
|
|
568
|
-
else:
|
|
569
|
-
return bottom_item.bottoms
|
|
570
|
-
|
|
571
|
-
def set_bottom_alignment_margin(self, value: float):
|
|
572
|
-
bottom_item = self.nodes[-1]
|
|
573
|
-
if isinstance(bottom_item, LayoutSpaces):
|
|
574
|
-
bottom_item.b.alignment_margin = value
|
|
575
|
-
else:
|
|
576
|
-
bottom_item.set_bottom_alignment_margin(value)
|
|
853
|
+
item.set_left_margin_alignment(value)
|
|
577
854
|
|
|
578
|
-
|
|
579
|
-
def rights(self):
|
|
580
|
-
values = []
|
|
581
|
-
for item in self.nodes:
|
|
582
|
-
if isinstance(item, LayoutSpaces):
|
|
583
|
-
values.append(item.r.right)
|
|
584
|
-
else:
|
|
585
|
-
values.append(min(item.rights))
|
|
586
|
-
return values
|
|
855
|
+
del self.panel_lefts
|
|
587
856
|
|
|
588
|
-
def
|
|
857
|
+
def align_panel_rights(self):
|
|
589
858
|
"""
|
|
590
859
|
Align the immediate right edges in this composition
|
|
591
860
|
|
|
@@ -599,80 +868,209 @@ class RowsTree(LayoutTree):
|
|
|
599
868
|
| #| | # |
|
|
600
869
|
----------- -----------
|
|
601
870
|
"""
|
|
602
|
-
if self.
|
|
871
|
+
if self.panel_rights_align:
|
|
603
872
|
return
|
|
604
873
|
|
|
605
|
-
values = np.array(self.
|
|
874
|
+
values = np.array(self.panel_rights) - min(self.panel_rights)
|
|
606
875
|
for item, value in zip(self.nodes, values):
|
|
607
876
|
if isinstance(item, LayoutSpaces):
|
|
608
|
-
item.r.
|
|
877
|
+
item.r.margin_alignment = value
|
|
609
878
|
else:
|
|
610
|
-
item.
|
|
879
|
+
item.set_right_margin_alignment(value)
|
|
880
|
+
|
|
881
|
+
del self.panel_rights
|
|
611
882
|
|
|
612
|
-
|
|
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):
|
|
912
|
+
if isinstance(item, LayoutSpaces):
|
|
913
|
+
item.l.tag_alignment = value
|
|
914
|
+
else:
|
|
915
|
+
item.set_left_tag_alignment(value)
|
|
613
916
|
|
|
614
|
-
def
|
|
917
|
+
def align_right_tags(self):
|
|
918
|
+
if self.right_tags_align:
|
|
919
|
+
return
|
|
920
|
+
|
|
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)
|
|
930
|
+
|
|
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
|
+
)
|
|
938
|
+
for item, value in zip(self.nodes, values):
|
|
939
|
+
if value == 0:
|
|
940
|
+
continue
|
|
941
|
+
if isinstance(item, LayoutSpaces):
|
|
942
|
+
item.l.axis_title_alignment = value
|
|
943
|
+
else:
|
|
944
|
+
item.set_left_axis_title_alignment(value)
|
|
945
|
+
|
|
946
|
+
@cached_property
|
|
947
|
+
def panel_lefts(self):
|
|
948
|
+
values = []
|
|
615
949
|
for item in self.nodes:
|
|
616
950
|
if isinstance(item, LayoutSpaces):
|
|
617
|
-
item.
|
|
951
|
+
values.append(item.l.panel_left)
|
|
618
952
|
else:
|
|
619
|
-
item.
|
|
953
|
+
values.append(max(item.panel_lefts))
|
|
954
|
+
return values
|
|
620
955
|
|
|
621
956
|
@cached_property
|
|
622
|
-
def
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
957
|
+
def panel_rights(self):
|
|
958
|
+
values = []
|
|
959
|
+
for item in self.nodes:
|
|
960
|
+
if isinstance(item, LayoutSpaces):
|
|
961
|
+
values.append(item.r.panel_right)
|
|
962
|
+
else:
|
|
963
|
+
values.append(min(item.panel_rights))
|
|
964
|
+
return values
|
|
965
|
+
|
|
966
|
+
@cached_property
|
|
967
|
+
def panel_bottoms(self):
|
|
968
|
+
bottom_item = self.nodes[-1]
|
|
969
|
+
if isinstance(bottom_item, LayoutSpaces):
|
|
970
|
+
return [bottom_item.b.panel_bottom]
|
|
626
971
|
else:
|
|
627
|
-
return
|
|
972
|
+
return bottom_item.panel_bottoms
|
|
628
973
|
|
|
629
|
-
|
|
974
|
+
@cached_property
|
|
975
|
+
def panel_tops(self):
|
|
630
976
|
top_item = self.nodes[0]
|
|
631
977
|
if isinstance(top_item, LayoutSpaces):
|
|
632
|
-
top_item.t.
|
|
978
|
+
return [top_item.t.panel_top]
|
|
633
979
|
else:
|
|
634
|
-
top_item.
|
|
980
|
+
return top_item.panel_tops
|
|
635
981
|
|
|
636
982
|
@property
|
|
637
983
|
def panel_width(self) -> float:
|
|
638
|
-
"""
|
|
639
|
-
A representative for width for panels of the nodes
|
|
640
|
-
"""
|
|
641
984
|
return float(np.mean(self.panel_widths))
|
|
642
985
|
|
|
643
986
|
@property
|
|
644
987
|
def panel_height(self) -> float:
|
|
645
|
-
"""
|
|
646
|
-
A representative for height for panels of the nodes
|
|
647
|
-
"""
|
|
648
988
|
return sum(self.panel_heights)
|
|
649
989
|
|
|
650
990
|
@property
|
|
651
991
|
def plot_width(self) -> float:
|
|
652
|
-
"""
|
|
653
|
-
A representative for width for plots of the nodes
|
|
654
|
-
"""
|
|
655
992
|
return max(self.plot_widths)
|
|
656
993
|
|
|
657
994
|
@property
|
|
658
995
|
def plot_height(self) -> float:
|
|
659
|
-
"""
|
|
660
|
-
A representative for height for plots of the nodes
|
|
661
|
-
"""
|
|
662
996
|
return sum(self.plot_heights)
|
|
663
997
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
998
|
+
@cached_property
|
|
999
|
+
def left_tag_width(self) -> float:
|
|
1000
|
+
return max(self.left_tag_widths)
|
|
667
1001
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
self.
|
|
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)
|