plotnine 0.14.5__py3-none-any.whl → 0.15.0.dev2__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 +31 -37
- plotnine/_mpl/gridspec.py +265 -0
- plotnine/_mpl/layout_manager/__init__.py +6 -0
- plotnine/_mpl/layout_manager/_engine.py +87 -0
- plotnine/_mpl/layout_manager/_layout_items.py +916 -0
- plotnine/_mpl/layout_manager/_layout_tree.py +625 -0
- plotnine/_mpl/layout_manager/_spaces.py +1007 -0
- plotnine/_mpl/patches.py +1 -1
- plotnine/_mpl/text.py +59 -24
- plotnine/_mpl/utils.py +78 -10
- plotnine/_utils/__init__.py +5 -5
- plotnine/_utils/dev.py +45 -27
- plotnine/animation.py +1 -1
- plotnine/coords/coord_trans.py +1 -1
- plotnine/data/__init__.py +12 -8
- plotnine/doctools.py +1 -1
- plotnine/facets/facet.py +30 -39
- plotnine/facets/facet_grid.py +14 -6
- plotnine/facets/facet_wrap.py +3 -5
- plotnine/facets/strips.py +7 -9
- plotnine/geoms/geom_crossbar.py +2 -3
- plotnine/geoms/geom_path.py +1 -1
- plotnine/ggplot.py +94 -65
- plotnine/guides/guide.py +12 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +12 -13
- plotnine/guides/guides.py +3 -3
- plotnine/iapi.py +5 -2
- plotnine/labels.py +5 -0
- plotnine/mapping/aes.py +4 -3
- plotnine/options.py +14 -7
- plotnine/plot_composition/__init__.py +10 -0
- plotnine/plot_composition/_compose.py +436 -0
- plotnine/plot_composition/_plotspec.py +50 -0
- plotnine/plot_composition/_spacer.py +32 -0
- plotnine/positions/position_dodge.py +1 -1
- plotnine/positions/position_dodge2.py +1 -1
- plotnine/positions/position_stack.py +1 -2
- plotnine/qplot.py +1 -2
- plotnine/scales/__init__.py +0 -6
- plotnine/scales/scale.py +1 -1
- plotnine/stats/binning.py +1 -1
- plotnine/stats/smoothers.py +3 -5
- plotnine/stats/stat_density.py +1 -1
- plotnine/stats/stat_qq_line.py +1 -1
- plotnine/stats/stat_sina.py +1 -1
- plotnine/themes/elements/__init__.py +2 -0
- plotnine/themes/elements/element_text.py +35 -24
- plotnine/themes/elements/margin.py +73 -60
- plotnine/themes/targets.py +3 -1
- plotnine/themes/theme.py +13 -7
- plotnine/themes/theme_gray.py +28 -31
- plotnine/themes/theme_matplotlib.py +25 -28
- plotnine/themes/theme_seaborn.py +31 -34
- plotnine/themes/theme_void.py +17 -26
- plotnine/themes/themeable.py +290 -157
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/METADATA +4 -3
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/RECORD +61 -54
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/WHEEL +1 -1
- plotnine/_mpl/_plot_side_space.py +0 -888
- plotnine/_mpl/_plotnine_tight_layout.py +0 -293
- plotnine/_mpl/layout_engine.py +0 -110
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info/licenses}/LICENSE +0 -0
- {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import cached_property
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from plotnine.plot_composition import OR
|
|
12
|
+
|
|
13
|
+
from ._spaces import LayoutSpaces
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from typing import Sequence
|
|
17
|
+
|
|
18
|
+
from plotnine import ggplot
|
|
19
|
+
from plotnine._mpl.gridspec import p9GridSpec
|
|
20
|
+
from plotnine.plot_composition import Compose
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class LayoutTree:
|
|
25
|
+
"""
|
|
26
|
+
A Tree representation of the composition
|
|
27
|
+
|
|
28
|
+
The purpose of this class (and its subclasses) is to align and
|
|
29
|
+
and resize plots in a composition.
|
|
30
|
+
|
|
31
|
+
For example, this composition;
|
|
32
|
+
|
|
33
|
+
(p1 | p2) | (p3 / p4)
|
|
34
|
+
|
|
35
|
+
where p1, p2, p3 & p4 are ggplot objects would look like this;
|
|
36
|
+
|
|
37
|
+
-----------------------------
|
|
38
|
+
| | | |
|
|
39
|
+
| | | |
|
|
40
|
+
| | | |
|
|
41
|
+
| | | |
|
|
42
|
+
| | |---------|
|
|
43
|
+
| | | |
|
|
44
|
+
| | | |
|
|
45
|
+
| | | |
|
|
46
|
+
| | | |
|
|
47
|
+
-----------------------------
|
|
48
|
+
|
|
49
|
+
and the tree would have this structure;
|
|
50
|
+
|
|
51
|
+
ColumnsTree
|
|
52
|
+
|
|
|
53
|
+
----------------------------
|
|
54
|
+
| | |
|
|
55
|
+
LayoutSpaces LayoutSpaces RowsTree
|
|
56
|
+
|
|
|
57
|
+
-------------
|
|
58
|
+
| |
|
|
59
|
+
LayoutSpaces LayoutSpaces
|
|
60
|
+
|
|
61
|
+
Each composition is a tree or subtree
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
gridspec: p9GridSpec
|
|
65
|
+
"""
|
|
66
|
+
Gridspec of the composition
|
|
67
|
+
|
|
68
|
+
Originally this gridspec occupies all the space available to it so the
|
|
69
|
+
subplots are of equal sizes. As each subplot contains full ggplot,
|
|
70
|
+
differences in texts and legend sizes may make the panels (panel area)
|
|
71
|
+
have unequal sizes. We can resize the panels, by changing the height
|
|
72
|
+
and width ratios of this (composition) gridspec.
|
|
73
|
+
|
|
74
|
+
The information about the size (width & height) of the panels is in the
|
|
75
|
+
LayoutSpaces.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
nodes: list[LayoutSpaces | LayoutTree]
|
|
79
|
+
"""
|
|
80
|
+
The spaces or tree of spaces in the composition that the tree
|
|
81
|
+
represents.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@cached_property
|
|
85
|
+
@abc.abstractmethod
|
|
86
|
+
def lefts(self) -> Sequence[float]:
|
|
87
|
+
"""
|
|
88
|
+
Left values [figure space] of nodes in this tree
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@abc.abstractmethod
|
|
92
|
+
def set_left_alignment_margin(self, value: float):
|
|
93
|
+
"""
|
|
94
|
+
Set a margin to align the left of the panels in this composition
|
|
95
|
+
|
|
96
|
+
In figure dimenstions
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
@cached_property
|
|
100
|
+
@abc.abstractmethod
|
|
101
|
+
def bottoms(self) -> Sequence[float]:
|
|
102
|
+
"""
|
|
103
|
+
Bottom values [figure space] of nodes in this tree
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
@abc.abstractmethod
|
|
107
|
+
def set_bottom_alignment_margin(self, value: float):
|
|
108
|
+
"""
|
|
109
|
+
Set a margin to align the bottom of the panels in this composition
|
|
110
|
+
|
|
111
|
+
In figure dimenstions
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
@cached_property
|
|
115
|
+
@abc.abstractmethod
|
|
116
|
+
def tops(self) -> Sequence[float]:
|
|
117
|
+
"""
|
|
118
|
+
Top values [figure space] of nodes in this tree
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
@abc.abstractmethod
|
|
122
|
+
def set_top_alignment_margin(self, value: float):
|
|
123
|
+
"""
|
|
124
|
+
Set a margin to align the top of the panels in this composition
|
|
125
|
+
|
|
126
|
+
In figure dimenstions
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
@cached_property
|
|
130
|
+
@abc.abstractmethod
|
|
131
|
+
def rights(self) -> Sequence[float]:
|
|
132
|
+
"""
|
|
133
|
+
Right values [figure space] of nodes in this tree
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
@abc.abstractmethod
|
|
137
|
+
def set_right_alignment_margin(self, value: float):
|
|
138
|
+
"""
|
|
139
|
+
Set a margin to align the right of the panels in this composition
|
|
140
|
+
|
|
141
|
+
In figure dimenstions
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def align_lefts(self):
|
|
145
|
+
"""
|
|
146
|
+
Align the immediate left edges in this composition
|
|
147
|
+
|
|
148
|
+
----------- -----------
|
|
149
|
+
|# | | # |
|
|
150
|
+
|# | | # |
|
|
151
|
+
|# | | # |
|
|
152
|
+
|-----------| -> |-----------|
|
|
153
|
+
| # | | # |
|
|
154
|
+
| # | | # |
|
|
155
|
+
| # | | # |
|
|
156
|
+
----------- -----------
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def align_bottoms(self):
|
|
160
|
+
"""
|
|
161
|
+
Align the immediate bottom edges this composition
|
|
162
|
+
|
|
163
|
+
----------- -----------
|
|
164
|
+
| | | | | |
|
|
165
|
+
| | | | | |
|
|
166
|
+
| | | -> | | |
|
|
167
|
+
| |#####| |#####|#####|
|
|
168
|
+
|#####| | | | |
|
|
169
|
+
----------- -----------
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def align_rights(self):
|
|
173
|
+
"""
|
|
174
|
+
Align the immediate right edges in this composition
|
|
175
|
+
|
|
176
|
+
----------- -----------
|
|
177
|
+
| # | | # |
|
|
178
|
+
| # | | # |
|
|
179
|
+
| # | | # |
|
|
180
|
+
|-----------| -> |-----------|
|
|
181
|
+
| #| | # |
|
|
182
|
+
| #| | # |
|
|
183
|
+
| #| | # |
|
|
184
|
+
----------- -----------
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def align_tops(self):
|
|
188
|
+
"""
|
|
189
|
+
Align the immediate top edges in this composition
|
|
190
|
+
|
|
191
|
+
----------- -----------
|
|
192
|
+
|#####| | | | |
|
|
193
|
+
| |#####| |#####|#####|
|
|
194
|
+
| | | -> | | |
|
|
195
|
+
| | | | | |
|
|
196
|
+
| | | | | |
|
|
197
|
+
----------- -----------
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def align(self):
|
|
201
|
+
"""
|
|
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.
|
|
206
|
+
"""
|
|
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
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
@abc.abstractmethod
|
|
230
|
+
def panel_width(self) -> float:
|
|
231
|
+
"""
|
|
232
|
+
A representative for width for panels of the nodes
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
@abc.abstractmethod
|
|
237
|
+
def panel_height(self) -> float:
|
|
238
|
+
"""
|
|
239
|
+
A representative for height for panels of the nodes
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
@abc.abstractmethod
|
|
244
|
+
def plot_width(self) -> float:
|
|
245
|
+
"""
|
|
246
|
+
A representative for width for plots of the nodes
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
@abc.abstractmethod
|
|
251
|
+
def plot_height(self) -> float:
|
|
252
|
+
"""
|
|
253
|
+
A representative for height for plots of the nodes
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def plot_widths(self) -> Sequence[float]:
|
|
258
|
+
"""
|
|
259
|
+
Widths [figure space] of nodes in this tree
|
|
260
|
+
"""
|
|
261
|
+
return [node.plot_width for node in self.nodes]
|
|
262
|
+
|
|
263
|
+
@property
|
|
264
|
+
def plot_heights(self) -> Sequence[float]:
|
|
265
|
+
"""
|
|
266
|
+
Heights [figure space] of nodes in this tree
|
|
267
|
+
"""
|
|
268
|
+
return [node.plot_height for node in self.nodes]
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def panel_widths(self) -> Sequence[float]:
|
|
272
|
+
"""
|
|
273
|
+
Widths [figure space] of the panels in this tree
|
|
274
|
+
"""
|
|
275
|
+
return [node.panel_width for node in self.nodes]
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def panel_heights(self) -> Sequence[float]:
|
|
279
|
+
"""
|
|
280
|
+
Heights [figure space] of the panels in this tree
|
|
281
|
+
"""
|
|
282
|
+
return [node.panel_height for node in self.nodes]
|
|
283
|
+
|
|
284
|
+
def resize(self):
|
|
285
|
+
"""
|
|
286
|
+
Resize panels and the entire plots
|
|
287
|
+
|
|
288
|
+
This function mutates the composition gridspecs; specifically the
|
|
289
|
+
width_ratios and height_ratios.
|
|
290
|
+
"""
|
|
291
|
+
self.resize_widths()
|
|
292
|
+
self.resize_heights()
|
|
293
|
+
|
|
294
|
+
for item in self.nodes:
|
|
295
|
+
if isinstance(item, LayoutTree):
|
|
296
|
+
item.resize()
|
|
297
|
+
|
|
298
|
+
def resize_widths(self):
|
|
299
|
+
"""
|
|
300
|
+
Resize the widths of gridspec so that panels have equal widths
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
def resize_heights(self):
|
|
304
|
+
"""
|
|
305
|
+
Resize the heights of gridspec so that panels have equal heights
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def create(
|
|
310
|
+
cmp: Compose,
|
|
311
|
+
lookup_spaces: dict[ggplot, LayoutSpaces],
|
|
312
|
+
) -> LayoutTree:
|
|
313
|
+
"""
|
|
314
|
+
Create a LayoutTree for this composition
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
cmp :
|
|
319
|
+
Composition
|
|
320
|
+
lookup_spaces :
|
|
321
|
+
A table to lookup the LayoutSpaces for each plot.
|
|
322
|
+
|
|
323
|
+
Notes
|
|
324
|
+
-----
|
|
325
|
+
LayoutTree works by modifying the `.gridspec` of the compositions,
|
|
326
|
+
and the `LayoutSpaces` of the plots.
|
|
327
|
+
"""
|
|
328
|
+
from plotnine import ggplot
|
|
329
|
+
|
|
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))
|
|
336
|
+
|
|
337
|
+
if isinstance(cmp, OR):
|
|
338
|
+
return ColumnsTree(cmp.gridspec, nodes)
|
|
339
|
+
else:
|
|
340
|
+
return RowsTree(cmp.gridspec, nodes)
|
|
341
|
+
|
|
342
|
+
def harmonise(self):
|
|
343
|
+
"""
|
|
344
|
+
Align and resize plots in composition to look good
|
|
345
|
+
"""
|
|
346
|
+
self.align()
|
|
347
|
+
self.resize()
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@dataclass
|
|
351
|
+
class ColumnsTree(LayoutTree):
|
|
352
|
+
"""
|
|
353
|
+
Tree with columns at the outermost level
|
|
354
|
+
|
|
355
|
+
e.g. p1 | (p2 / p3)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
-------------------
|
|
359
|
+
| | |
|
|
360
|
+
| | |
|
|
361
|
+
| | |
|
|
362
|
+
| | |
|
|
363
|
+
| |---------|
|
|
364
|
+
| | |
|
|
365
|
+
| | |
|
|
366
|
+
| | |
|
|
367
|
+
| | |
|
|
368
|
+
-------------------
|
|
369
|
+
"""
|
|
370
|
+
|
|
371
|
+
@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)
|
|
393
|
+
|
|
394
|
+
@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
|
|
403
|
+
|
|
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)
|
|
410
|
+
|
|
411
|
+
@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
|
|
418
|
+
|
|
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)
|
|
425
|
+
|
|
426
|
+
def align_tops(self):
|
|
427
|
+
values = np.array(self.tops) - min(self.tops)
|
|
428
|
+
for item, value in zip(self.nodes, values):
|
|
429
|
+
if isinstance(item, LayoutSpaces):
|
|
430
|
+
item.t.alignment_margin = value
|
|
431
|
+
else:
|
|
432
|
+
item.set_top_alignment_margin(value)
|
|
433
|
+
|
|
434
|
+
@cached_property
|
|
435
|
+
def tops(self):
|
|
436
|
+
values = []
|
|
437
|
+
for item in self.nodes:
|
|
438
|
+
if isinstance(item, LayoutSpaces):
|
|
439
|
+
values.append(item.t.top)
|
|
440
|
+
else:
|
|
441
|
+
values.append(min(item.tops))
|
|
442
|
+
return values
|
|
443
|
+
|
|
444
|
+
def set_top_alignment_margin(self, value: float):
|
|
445
|
+
for item in self.nodes:
|
|
446
|
+
if isinstance(item, LayoutSpaces):
|
|
447
|
+
item.t.alignment_margin = value
|
|
448
|
+
else:
|
|
449
|
+
item.set_top_alignment_margin(value)
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def panel_width(self) -> float:
|
|
453
|
+
"""
|
|
454
|
+
A representative for width for panels of the nodes
|
|
455
|
+
"""
|
|
456
|
+
return sum(self.panel_widths)
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def panel_height(self) -> float:
|
|
460
|
+
"""
|
|
461
|
+
A representative for height for panels of the nodes
|
|
462
|
+
"""
|
|
463
|
+
return float(np.mean(self.panel_heights))
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def plot_width(self) -> float:
|
|
467
|
+
"""
|
|
468
|
+
A representative for width for plots of the nodes
|
|
469
|
+
"""
|
|
470
|
+
return sum(self.plot_widths)
|
|
471
|
+
|
|
472
|
+
@property
|
|
473
|
+
def plot_height(self) -> float:
|
|
474
|
+
"""
|
|
475
|
+
A representative for height for plots of the nodes
|
|
476
|
+
"""
|
|
477
|
+
return max(self.plot_heights)
|
|
478
|
+
|
|
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)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@dataclass
|
|
492
|
+
class RowsTree(LayoutTree):
|
|
493
|
+
"""
|
|
494
|
+
Tree with rows at the outermost level
|
|
495
|
+
|
|
496
|
+
e.g. p1 / (p2 | p3)
|
|
497
|
+
|
|
498
|
+
-------------------
|
|
499
|
+
| |
|
|
500
|
+
| |
|
|
501
|
+
| |
|
|
502
|
+
|-------------------|
|
|
503
|
+
| | |
|
|
504
|
+
| | |
|
|
505
|
+
| | |
|
|
506
|
+
-------------------
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
def align_lefts(self):
|
|
510
|
+
values = max(self.lefts) - np.array(self.lefts)
|
|
511
|
+
for item, value in zip(self.nodes, values):
|
|
512
|
+
if isinstance(item, LayoutSpaces):
|
|
513
|
+
item.l.alignment_margin = value
|
|
514
|
+
else:
|
|
515
|
+
item.set_left_alignment_margin(value)
|
|
516
|
+
|
|
517
|
+
@cached_property
|
|
518
|
+
def lefts(self):
|
|
519
|
+
values = []
|
|
520
|
+
for item in self.nodes:
|
|
521
|
+
if isinstance(item, LayoutSpaces):
|
|
522
|
+
values.append(item.l.left)
|
|
523
|
+
else:
|
|
524
|
+
values.append(max(item.lefts))
|
|
525
|
+
return values
|
|
526
|
+
|
|
527
|
+
def set_left_alignment_margin(self, value: float):
|
|
528
|
+
for item in self.nodes:
|
|
529
|
+
if isinstance(item, LayoutSpaces):
|
|
530
|
+
item.l.alignment_margin = value
|
|
531
|
+
else:
|
|
532
|
+
item.set_left_alignment_margin(value)
|
|
533
|
+
|
|
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
|
|
541
|
+
|
|
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)
|
|
548
|
+
|
|
549
|
+
def align_rights(self):
|
|
550
|
+
values = np.array(self.rights) - min(self.rights)
|
|
551
|
+
for item, value in zip(self.nodes, values):
|
|
552
|
+
if isinstance(item, LayoutSpaces):
|
|
553
|
+
item.r.alignment_margin = value
|
|
554
|
+
else:
|
|
555
|
+
item.set_right_alignment_margin(value)
|
|
556
|
+
|
|
557
|
+
@cached_property
|
|
558
|
+
def rights(self):
|
|
559
|
+
values = []
|
|
560
|
+
for item in self.nodes:
|
|
561
|
+
if isinstance(item, LayoutSpaces):
|
|
562
|
+
values.append(item.r.right)
|
|
563
|
+
else:
|
|
564
|
+
values.append(min(item.rights))
|
|
565
|
+
return values
|
|
566
|
+
|
|
567
|
+
def set_right_alignment_margin(self, value: float):
|
|
568
|
+
for item in self.nodes:
|
|
569
|
+
if isinstance(item, LayoutSpaces):
|
|
570
|
+
item.r.alignment_margin = value
|
|
571
|
+
else:
|
|
572
|
+
item.set_right_alignment_margin(value)
|
|
573
|
+
|
|
574
|
+
@cached_property
|
|
575
|
+
def tops(self):
|
|
576
|
+
top_item = self.nodes[0]
|
|
577
|
+
if isinstance(top_item, LayoutSpaces):
|
|
578
|
+
return [top_item.t.top]
|
|
579
|
+
else:
|
|
580
|
+
return top_item.tops
|
|
581
|
+
|
|
582
|
+
def set_top_alignment_margin(self, value: float):
|
|
583
|
+
top_item = self.nodes[0]
|
|
584
|
+
if isinstance(top_item, LayoutSpaces):
|
|
585
|
+
top_item.t.alignment_margin = value
|
|
586
|
+
else:
|
|
587
|
+
top_item.set_top_alignment_margin(value)
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def panel_width(self) -> float:
|
|
591
|
+
"""
|
|
592
|
+
A representative for width for panels of the nodes
|
|
593
|
+
"""
|
|
594
|
+
return float(np.mean(self.panel_widths))
|
|
595
|
+
|
|
596
|
+
@property
|
|
597
|
+
def panel_height(self) -> float:
|
|
598
|
+
"""
|
|
599
|
+
A representative for height for panels of the nodes
|
|
600
|
+
"""
|
|
601
|
+
return sum(self.panel_heights)
|
|
602
|
+
|
|
603
|
+
@property
|
|
604
|
+
def plot_width(self) -> float:
|
|
605
|
+
"""
|
|
606
|
+
A representative for width for plots of the nodes
|
|
607
|
+
"""
|
|
608
|
+
return max(self.plot_widths)
|
|
609
|
+
|
|
610
|
+
@property
|
|
611
|
+
def plot_height(self) -> float:
|
|
612
|
+
"""
|
|
613
|
+
A representative for height for plots of the nodes
|
|
614
|
+
"""
|
|
615
|
+
return sum(self.plot_heights)
|
|
616
|
+
|
|
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)
|