plotnine 0.15.0.dev2__py3-none-any.whl → 0.15.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plotnine/__init__.py +2 -0
- plotnine/_mpl/layout_manager/_engine.py +1 -1
- plotnine/_mpl/layout_manager/_layout_items.py +128 -83
- plotnine/_mpl/layout_manager/_layout_tree.py +761 -310
- plotnine/_mpl/layout_manager/_spaces.py +320 -103
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +144 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +50 -107
- plotnine/_utils/context.py +78 -2
- plotnine/_utils/ipython.py +35 -51
- plotnine/_utils/quarto.py +21 -0
- plotnine/_utils/yippie.py +115 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/composition/_beside.py +55 -0
- plotnine/composition/_compose.py +471 -0
- plotnine/composition/_plot_spacer.py +60 -0
- plotnine/composition/_stack.py +55 -0
- plotnine/coords/coord.py +3 -3
- plotnine/data/__init__.py +31 -0
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +4 -4
- plotnine/facets/facet.py +4 -4
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotate.py +13 -13
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +60 -27
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_area.py +2 -2
- plotnine/geoms/geom_bar.py +11 -2
- plotnine/geoms/geom_bin_2d.py +6 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +8 -4
- plotnine/geoms/geom_col.py +8 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +2 -2
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +5 -5
- plotnine/geoms/geom_path.py +11 -12
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointdensity.py +4 -0
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_qq.py +4 -0
- plotnine/geoms/geom_qq_line.py +4 -0
- plotnine/geoms/geom_quantile.py +4 -0
- plotnine/geoms/geom_raster.py +4 -5
- plotnine/geoms/geom_rect.py +3 -4
- plotnine/geoms/geom_ribbon.py +7 -7
- plotnine/geoms/geom_rug.py +1 -1
- plotnine/geoms/geom_segment.py +2 -2
- plotnine/geoms/geom_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +7 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +28 -8
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/ggplot.py +64 -85
- plotnine/guides/guide.py +7 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +3 -3
- plotnine/guides/guides.py +6 -6
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/labels.py +3 -3
- plotnine/layer.py +36 -19
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/_env.py +13 -2
- plotnine/mapping/_eval_environment.py +85 -0
- plotnine/mapping/aes.py +91 -72
- plotnine/mapping/evaluation.py +7 -65
- plotnine/scales/__init__.py +2 -0
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +3 -3
- plotnine/scales/scale_color.py +82 -18
- plotnine/scales/scale_continuous.py +6 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/scales/scale_identity.py +21 -2
- plotnine/scales/scale_manual.py +8 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +23 -36
- plotnine/stats/stat.py +20 -32
- plotnine/stats/stat_bin.py +6 -5
- plotnine/stats/stat_bin_2d.py +11 -9
- plotnine/stats/stat_bindot.py +13 -16
- plotnine/stats/stat_boxplot.py +6 -6
- plotnine/stats/stat_count.py +6 -9
- plotnine/stats/stat_density.py +7 -10
- plotnine/stats/stat_density_2d.py +12 -8
- plotnine/stats/stat_ecdf.py +7 -6
- plotnine/stats/stat_ellipse.py +9 -6
- plotnine/stats/stat_function.py +10 -8
- plotnine/stats/stat_hull.py +6 -3
- plotnine/stats/stat_identity.py +5 -2
- plotnine/stats/stat_pointdensity.py +5 -7
- plotnine/stats/stat_qq.py +46 -20
- plotnine/stats/stat_qq_line.py +16 -11
- plotnine/stats/stat_quantile.py +15 -9
- plotnine/stats/stat_sina.py +45 -14
- plotnine/stats/stat_smooth.py +8 -10
- plotnine/stats/stat_sum.py +5 -2
- plotnine/stats/stat_summary.py +7 -10
- plotnine/stats/stat_summary_bin.py +11 -14
- plotnine/stats/stat_unique.py +5 -2
- plotnine/stats/stat_ydensity.py +8 -11
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme.py +9 -1
- plotnine/themes/theme_538.py +0 -1
- plotnine/themes/theme_bw.py +0 -1
- plotnine/themes/theme_dark.py +0 -1
- plotnine/themes/theme_gray.py +6 -5
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +5 -5
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +9 -8
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +110 -32
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/METADATA +13 -6
- plotnine-0.15.1.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/WHEEL +1 -1
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_compose.py +0 -436
- plotnine/plot_composition/_spacer.py +0 -32
- plotnine-0.15.0.dev2.dist-info/RECORD +0 -214
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
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.
|
|
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
|
-
@
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
@staticmethod
|
|
84
|
+
def create(
|
|
85
|
+
cmp: Compose,
|
|
86
|
+
lookup_spaces: dict[ggplot, LayoutSpaces],
|
|
87
|
+
) -> LayoutTree:
|
|
87
88
|
"""
|
|
88
|
-
|
|
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
|
|
126
|
+
def align(self):
|
|
93
127
|
"""
|
|
94
|
-
|
|
128
|
+
Align all the edges in this composition & contained compositions
|
|
95
129
|
|
|
96
|
-
|
|
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
|
|
135
|
+
def resize(self):
|
|
102
136
|
"""
|
|
103
|
-
|
|
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
|
|
152
|
+
def align_axis_titles(self):
|
|
108
153
|
"""
|
|
109
|
-
|
|
154
|
+
Align the axis titles along the composing dimension
|
|
110
155
|
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
def tops(self) -> Sequence[float]:
|
|
173
|
+
def sub_compositions(self) -> list[LayoutTree]:
|
|
117
174
|
"""
|
|
118
|
-
|
|
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
|
|
181
|
+
def panel_lefts(self) -> Sequence[float]:
|
|
123
182
|
"""
|
|
124
|
-
|
|
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
|
|
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
|
|
195
|
+
def panel_bottoms(self) -> Sequence[float]:
|
|
138
196
|
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
In figure dimenstions
|
|
197
|
+
Bottom values [figure space] of nodes in this tree
|
|
142
198
|
"""
|
|
143
199
|
|
|
144
|
-
|
|
200
|
+
@cached_property
|
|
201
|
+
@abc.abstractmethod
|
|
202
|
+
def panel_tops(self) -> Sequence[float]:
|
|
145
203
|
"""
|
|
146
|
-
|
|
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
|
-
|
|
207
|
+
@property
|
|
208
|
+
def panel_lefts_align(self) -> bool:
|
|
160
209
|
"""
|
|
161
|
-
|
|
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
|
-
|
|
215
|
+
@property
|
|
216
|
+
def panel_rights_align(self) -> bool:
|
|
173
217
|
"""
|
|
174
|
-
|
|
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
|
-
|
|
223
|
+
@property
|
|
224
|
+
def panel_bottoms_align(self) -> bool:
|
|
188
225
|
"""
|
|
189
|
-
|
|
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
|
-
|
|
231
|
+
@property
|
|
232
|
+
def panel_tops_align(self) -> bool:
|
|
201
233
|
"""
|
|
202
|
-
|
|
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.
|
|
208
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
295
|
+
@cached_property
|
|
296
|
+
@abc.abstractmethod
|
|
297
|
+
def left_tag_width(self) -> float:
|
|
285
298
|
"""
|
|
286
|
-
|
|
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
|
-
|
|
302
|
+
@cached_property
|
|
303
|
+
@abc.abstractmethod
|
|
304
|
+
def right_tag_width(self) -> float:
|
|
299
305
|
"""
|
|
300
|
-
|
|
306
|
+
A representative width [figure space] for the right tags of the nodes
|
|
301
307
|
"""
|
|
302
308
|
|
|
303
|
-
|
|
309
|
+
@cached_property
|
|
310
|
+
@abc.abstractmethod
|
|
311
|
+
def bottom_tag_height(self) -> float:
|
|
304
312
|
"""
|
|
305
|
-
|
|
313
|
+
A representative height [figure space] for the top tags of the nodes
|
|
306
314
|
"""
|
|
307
315
|
|
|
308
|
-
@
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
351
|
+
@property
|
|
352
|
+
def left_tags_align(self) -> bool:
|
|
343
353
|
"""
|
|
344
|
-
|
|
354
|
+
Return True if the left tags for the nodes are aligned
|
|
345
355
|
"""
|
|
346
|
-
self.
|
|
347
|
-
|
|
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
|
-
@
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
427
|
-
|
|
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.
|
|
631
|
+
item.t.tag_alignment = value
|
|
431
632
|
else:
|
|
432
|
-
item.
|
|
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
|
|
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.
|
|
673
|
+
values.append(item.b.panel_bottom)
|
|
440
674
|
else:
|
|
441
|
-
values.append(
|
|
675
|
+
values.append(max(item.panel_bottoms))
|
|
442
676
|
return values
|
|
443
677
|
|
|
444
|
-
|
|
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.
|
|
683
|
+
values.append(item.t.panel_top)
|
|
448
684
|
else:
|
|
449
|
-
item.
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
510
|
-
|
|
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.
|
|
851
|
+
item.l.margin_alignment = value
|
|
514
852
|
else:
|
|
515
|
-
item.
|
|
853
|
+
item.set_left_margin_alignment(value)
|
|
516
854
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
877
|
+
item.r.margin_alignment = value
|
|
523
878
|
else:
|
|
524
|
-
|
|
525
|
-
return values
|
|
879
|
+
item.set_right_margin_alignment(value)
|
|
526
880
|
|
|
527
|
-
|
|
528
|
-
|
|
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.
|
|
913
|
+
item.l.tag_alignment = value
|
|
531
914
|
else:
|
|
532
|
-
item.
|
|
915
|
+
item.set_left_tag_alignment(value)
|
|
533
916
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
550
|
-
|
|
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.
|
|
942
|
+
item.l.axis_title_alignment = value
|
|
554
943
|
else:
|
|
555
|
-
item.
|
|
944
|
+
item.set_left_axis_title_alignment(value)
|
|
556
945
|
|
|
557
946
|
@cached_property
|
|
558
|
-
def
|
|
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.
|
|
951
|
+
values.append(item.l.panel_left)
|
|
563
952
|
else:
|
|
564
|
-
values.append(
|
|
953
|
+
values.append(max(item.panel_lefts))
|
|
565
954
|
return values
|
|
566
955
|
|
|
567
|
-
|
|
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.
|
|
961
|
+
values.append(item.r.panel_right)
|
|
571
962
|
else:
|
|
572
|
-
item.
|
|
963
|
+
values.append(min(item.panel_rights))
|
|
964
|
+
return values
|
|
573
965
|
|
|
574
966
|
@cached_property
|
|
575
|
-
def
|
|
576
|
-
|
|
577
|
-
if isinstance(
|
|
578
|
-
return [
|
|
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
|
|
972
|
+
return bottom_item.panel_bottoms
|
|
581
973
|
|
|
582
|
-
|
|
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.
|
|
978
|
+
return [top_item.t.panel_top]
|
|
586
979
|
else:
|
|
587
|
-
top_item.
|
|
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
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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)
|