plotnine 0.15.3__py3-none-any.whl → 0.16.0a1__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/_mpl/gridspec.py +50 -6
- plotnine/_mpl/layout_manager/__init__.py +2 -5
- plotnine/_mpl/layout_manager/_composition_layout_items.py +98 -0
- plotnine/_mpl/layout_manager/_composition_side_space.py +461 -0
- plotnine/_mpl/layout_manager/_engine.py +19 -58
- plotnine/_mpl/layout_manager/_grid.py +94 -0
- plotnine/_mpl/layout_manager/_layout_tree.py +402 -817
- plotnine/_mpl/layout_manager/{_layout_items.py → _plot_layout_items.py} +55 -278
- plotnine/_mpl/layout_manager/{_spaces.py → _plot_side_space.py} +111 -291
- plotnine/_mpl/layout_manager/_side_space.py +176 -0
- plotnine/_mpl/utils.py +259 -1
- plotnine/_utils/__init__.py +23 -3
- plotnine/_utils/context.py +9 -13
- plotnine/_utils/dataclasses.py +24 -0
- plotnine/animation.py +13 -12
- plotnine/composition/__init__.py +6 -0
- plotnine/composition/_beside.py +13 -11
- plotnine/composition/_compose.py +263 -99
- plotnine/composition/_plot_annotation.py +75 -0
- plotnine/composition/_plot_layout.py +143 -0
- plotnine/composition/_plot_spacer.py +1 -1
- plotnine/composition/_stack.py +13 -11
- plotnine/composition/_types.py +28 -0
- plotnine/composition/_wrap.py +60 -0
- plotnine/facets/facet.py +9 -12
- plotnine/facets/facet_grid.py +2 -2
- plotnine/facets/facet_wrap.py +1 -1
- plotnine/geoms/geom.py +2 -2
- plotnine/geoms/geom_map.py +4 -5
- plotnine/geoms/geom_path.py +8 -7
- plotnine/geoms/geom_rug.py +6 -10
- plotnine/geoms/geom_text.py +5 -5
- plotnine/ggplot.py +63 -9
- plotnine/guides/guide.py +24 -6
- plotnine/guides/guide_colorbar.py +88 -46
- plotnine/guides/guide_legend.py +47 -20
- plotnine/guides/guides.py +2 -2
- plotnine/iapi.py +17 -1
- plotnine/scales/scale.py +1 -1
- plotnine/stats/binning.py +15 -43
- plotnine/stats/smoothers.py +7 -3
- plotnine/stats/stat.py +2 -2
- plotnine/stats/stat_density_2d.py +10 -6
- plotnine/stats/stat_pointdensity.py +8 -1
- plotnine/stats/stat_qq.py +5 -5
- plotnine/stats/stat_qq_line.py +6 -1
- plotnine/stats/stat_sina.py +19 -20
- plotnine/stats/stat_summary.py +4 -2
- plotnine/stats/stat_summary_bin.py +7 -1
- plotnine/themes/elements/element_line.py +2 -0
- plotnine/themes/elements/element_text.py +12 -1
- plotnine/themes/theme.py +18 -24
- plotnine/themes/themeable.py +17 -3
- plotnine/typing.py +6 -1
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/METADATA +3 -3
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/RECORD +59 -51
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/WHEEL +1 -1
- plotnine/composition/_plotspec.py +0 -50
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.3.dist-info → plotnine-0.16.0a1.dist-info}/top_level.txt +0 -0
plotnine/_mpl/gridspec.py
CHANGED
|
@@ -11,15 +11,16 @@ except ImportError:
|
|
|
11
11
|
from matplotlib.figure import SubplotParams
|
|
12
12
|
from matplotlib.gridspec import GridSpecBase
|
|
13
13
|
|
|
14
|
+
from matplotlib.gridspec import SubplotSpec
|
|
14
15
|
from matplotlib.transforms import Bbox, BboxTransformTo, TransformedBbox
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
from matplotlib.figure import Figure
|
|
18
|
-
from matplotlib.gridspec import SubplotSpec
|
|
19
19
|
from matplotlib.patches import Rectangle
|
|
20
20
|
from matplotlib.transforms import Transform
|
|
21
21
|
|
|
22
|
-
from plotnine._mpl.layout_manager.
|
|
22
|
+
from plotnine._mpl.layout_manager._side_space import GridSpecParams
|
|
23
|
+
from plotnine.composition._plot_layout import plot_layout
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class p9GridSpec(GridSpecBase):
|
|
@@ -27,8 +28,7 @@ class p9GridSpec(GridSpecBase):
|
|
|
27
28
|
Gridspec for plotnine plots
|
|
28
29
|
|
|
29
30
|
This gridspec does not read any subplot parameter values from matplotlib's
|
|
30
|
-
rcparams
|
|
31
|
-
there is no space along the edges and between the subplots.
|
|
31
|
+
rcparams. And there is no space along the edges and between the subplots.
|
|
32
32
|
|
|
33
33
|
This gridspec can also be initialised while contained/nested in a given
|
|
34
34
|
subplot.
|
|
@@ -61,10 +61,12 @@ class p9GridSpec(GridSpecBase):
|
|
|
61
61
|
*,
|
|
62
62
|
width_ratios=None,
|
|
63
63
|
height_ratios=None,
|
|
64
|
+
byrow: bool = True,
|
|
64
65
|
nest_into: SubplotSpec | None = None,
|
|
65
66
|
):
|
|
66
67
|
self.figure = figure
|
|
67
68
|
self._nested_gridspecs = []
|
|
69
|
+
self.byrow = byrow
|
|
68
70
|
|
|
69
71
|
super().__init__(
|
|
70
72
|
nrows,
|
|
@@ -92,6 +94,34 @@ class p9GridSpec(GridSpecBase):
|
|
|
92
94
|
hspace=0,
|
|
93
95
|
)
|
|
94
96
|
|
|
97
|
+
@staticmethod
|
|
98
|
+
def from_layout(
|
|
99
|
+
layout: plot_layout,
|
|
100
|
+
figure: Figure,
|
|
101
|
+
*,
|
|
102
|
+
nest_into: SubplotSpec | None = None,
|
|
103
|
+
) -> p9GridSpec:
|
|
104
|
+
"""
|
|
105
|
+
Create gridspec from a plot_layout instance
|
|
106
|
+
"""
|
|
107
|
+
return p9GridSpec(
|
|
108
|
+
layout.nrow,
|
|
109
|
+
layout.ncol,
|
|
110
|
+
figure,
|
|
111
|
+
byrow=True if layout.byrow is None else layout.byrow,
|
|
112
|
+
nest_into=nest_into,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def __iter__(self):
|
|
116
|
+
from itertools import product
|
|
117
|
+
|
|
118
|
+
if self.byrow:
|
|
119
|
+
for r, c in product(range(self.nrows), range(self.ncols)):
|
|
120
|
+
yield SubplotSpec(self, r * self.ncols + c)
|
|
121
|
+
else:
|
|
122
|
+
for c, r in product(range(self.ncols), range(self.nrows)):
|
|
123
|
+
yield SubplotSpec(self, r * self.ncols + c)
|
|
124
|
+
|
|
95
125
|
@property
|
|
96
126
|
def patch(self) -> Rectangle:
|
|
97
127
|
"""
|
|
@@ -170,9 +200,9 @@ class p9GridSpec(GridSpecBase):
|
|
|
170
200
|
for gs in self._nested_gridspecs:
|
|
171
201
|
gs._update_artists()
|
|
172
202
|
|
|
173
|
-
def
|
|
203
|
+
def update_params_and_artists(self, gsparams: GridSpecParams):
|
|
174
204
|
"""
|
|
175
|
-
Update
|
|
205
|
+
Update gridpspec params and the artists
|
|
176
206
|
"""
|
|
177
207
|
self.update(**asdict(gsparams))
|
|
178
208
|
self._update_artists()
|
|
@@ -244,6 +274,20 @@ class p9GridSpec(GridSpecBase):
|
|
|
244
274
|
"""
|
|
245
275
|
return TransformedBbox(self.bbox_relative, self.figure.transSubfigure)
|
|
246
276
|
|
|
277
|
+
@property
|
|
278
|
+
def width(self) -> float:
|
|
279
|
+
"""
|
|
280
|
+
Width of bbox in figure space
|
|
281
|
+
"""
|
|
282
|
+
return self.bbox_relative.width
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def height(self) -> float:
|
|
286
|
+
"""
|
|
287
|
+
Height of bbox in figure space
|
|
288
|
+
"""
|
|
289
|
+
return self.bbox_relative.height
|
|
290
|
+
|
|
247
291
|
def to_transform(self) -> Transform:
|
|
248
292
|
"""
|
|
249
293
|
Return transform of this gridspec
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from matplotlib.text import Text
|
|
7
|
+
|
|
8
|
+
from plotnine._mpl.utils import (
|
|
9
|
+
ArtistGeometry,
|
|
10
|
+
JustifyBoundaries,
|
|
11
|
+
TextJustifier,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from plotnine.composition._compose import Compose
|
|
18
|
+
|
|
19
|
+
from ._composition_side_space import CompositionSideSpaces
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CompositionLayoutItems:
|
|
24
|
+
"""
|
|
25
|
+
plot_annotation artists
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, cmp: Compose):
|
|
29
|
+
def get(name: str) -> Any:
|
|
30
|
+
"""
|
|
31
|
+
Return themeable target or None
|
|
32
|
+
"""
|
|
33
|
+
if self._is_blank(name):
|
|
34
|
+
return None
|
|
35
|
+
else:
|
|
36
|
+
t = getattr(cmp.theme.targets, name)
|
|
37
|
+
if isinstance(t, Text) and t.get_text() == "":
|
|
38
|
+
return None
|
|
39
|
+
return t
|
|
40
|
+
|
|
41
|
+
self.cmp = cmp
|
|
42
|
+
self.geometry = ArtistGeometry(cmp.figure)
|
|
43
|
+
|
|
44
|
+
self.plot_title: Text | None = get("plot_title")
|
|
45
|
+
self.plot_subtitle: Text | None = get("plot_subtitle")
|
|
46
|
+
self.plot_caption: Text | None = get("plot_caption")
|
|
47
|
+
|
|
48
|
+
def _is_blank(self, name: str) -> bool:
|
|
49
|
+
return self.cmp.theme.T.is_blank(name)
|
|
50
|
+
|
|
51
|
+
def _move_artists(self, spaces: CompositionSideSpaces):
|
|
52
|
+
"""
|
|
53
|
+
Move the annotations to their final positions
|
|
54
|
+
"""
|
|
55
|
+
theme = self.cmp.theme
|
|
56
|
+
plot_title_position = theme.getp("plot_title_position", "panel")
|
|
57
|
+
plot_caption_position = theme.getp("plot_caption_position", "panel")
|
|
58
|
+
justify = CompositionTextJustifier(spaces)
|
|
59
|
+
|
|
60
|
+
if self.plot_title:
|
|
61
|
+
ha = theme.getp(("plot_title", "ha"))
|
|
62
|
+
self.plot_title.set_y(spaces.t.y2("plot_title"))
|
|
63
|
+
justify.horizontally_about(
|
|
64
|
+
self.plot_title, ha, plot_title_position
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if self.plot_subtitle:
|
|
68
|
+
ha = theme.getp(("plot_subtitle", "ha"))
|
|
69
|
+
self.plot_subtitle.set_y(spaces.t.y2("plot_subtitle"))
|
|
70
|
+
justify.horizontally_about(
|
|
71
|
+
self.plot_subtitle, ha, plot_title_position
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if self.plot_caption:
|
|
75
|
+
ha = theme.getp(("plot_caption", "ha"), "right")
|
|
76
|
+
self.plot_caption.set_y(spaces.b.y1("plot_caption"))
|
|
77
|
+
justify.horizontally_about(
|
|
78
|
+
self.plot_caption, ha, plot_caption_position
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class CompositionTextJustifier(TextJustifier):
|
|
83
|
+
"""
|
|
84
|
+
Justify Text about a composition or it's panels
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def __init__(self, spaces: CompositionSideSpaces):
|
|
88
|
+
boundaries = JustifyBoundaries(
|
|
89
|
+
plot_left=spaces.plot_left,
|
|
90
|
+
plot_right=spaces.plot_right,
|
|
91
|
+
plot_bottom=spaces.plot_bottom,
|
|
92
|
+
plot_top=spaces.plot_top,
|
|
93
|
+
panel_left=spaces.panel_left,
|
|
94
|
+
panel_right=spaces.panel_right,
|
|
95
|
+
panel_bottom=spaces.panel_bottom,
|
|
96
|
+
panel_top=spaces.panel_top,
|
|
97
|
+
)
|
|
98
|
+
super().__init__(spaces.cmp.figure, boundaries)
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from plotnine._mpl.layout_manager._layout_tree import LayoutTree
|
|
6
|
+
from plotnine._mpl.layout_manager._plot_side_space import PlotSideSpaces
|
|
7
|
+
|
|
8
|
+
from ._composition_layout_items import CompositionLayoutItems
|
|
9
|
+
from ._side_space import GridSpecParams, _side_space
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from plotnine.composition._compose import Compose
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _composition_side_space(_side_space):
|
|
16
|
+
"""
|
|
17
|
+
Base class for the side space around a composition
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, items: CompositionLayoutItems):
|
|
21
|
+
self.items = items
|
|
22
|
+
self.gridspec = items.cmp._gridspec
|
|
23
|
+
self._calculate()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class composition_left_space(_composition_side_space):
|
|
27
|
+
plot_margin: float = 0
|
|
28
|
+
|
|
29
|
+
def _calculate(self):
|
|
30
|
+
theme = self.items.cmp.theme
|
|
31
|
+
self.plot_margin = theme.getp("plot_margin_left")
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def offset(self) -> float:
|
|
35
|
+
"""
|
|
36
|
+
Distance from left of the figure to the left of the plot gridspec
|
|
37
|
+
|
|
38
|
+
----------------(1, 1)
|
|
39
|
+
| ---- |
|
|
40
|
+
| dx | | |
|
|
41
|
+
|<--->| | |
|
|
42
|
+
| | | |
|
|
43
|
+
| ---- |
|
|
44
|
+
(0, 0)----------------
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
return self.gridspec.bbox_relative.x0
|
|
48
|
+
|
|
49
|
+
def x1(self, item: str) -> float:
|
|
50
|
+
"""
|
|
51
|
+
Lower x-coordinate in figure space of the item
|
|
52
|
+
"""
|
|
53
|
+
return self.to_figure_space(self.sum_upto(item))
|
|
54
|
+
|
|
55
|
+
def x2(self, item: str) -> float:
|
|
56
|
+
"""
|
|
57
|
+
Higher x-coordinate in figure space of the item
|
|
58
|
+
"""
|
|
59
|
+
return self.to_figure_space(self.sum_incl(item))
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def items_left_relative(self):
|
|
63
|
+
"""
|
|
64
|
+
Left (relative to the gridspec) of the cmp items in figure dimensions
|
|
65
|
+
"""
|
|
66
|
+
return self.total
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def items_left(self):
|
|
70
|
+
"""
|
|
71
|
+
Left of the composition items in figure space
|
|
72
|
+
"""
|
|
73
|
+
return self.to_figure_space(self.items_left_relative)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class composition_right_space(_composition_side_space):
|
|
77
|
+
"""
|
|
78
|
+
Space for annotations to the right of the actual composition
|
|
79
|
+
|
|
80
|
+
Ordered from the edge of the figure and going inwards
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
plot_margin: float = 0
|
|
84
|
+
|
|
85
|
+
def _calculate(self):
|
|
86
|
+
theme = self.items.cmp.theme
|
|
87
|
+
self.plot_margin = theme.getp("plot_margin_right")
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def offset(self):
|
|
91
|
+
"""
|
|
92
|
+
Distance from right of the figure to the right of the plot gridspec
|
|
93
|
+
|
|
94
|
+
---------------(1, 1)
|
|
95
|
+
| ---- |
|
|
96
|
+
| | | -dx |
|
|
97
|
+
| | |<--->|
|
|
98
|
+
| | | |
|
|
99
|
+
| ---- |
|
|
100
|
+
(0, 0)---------------
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
return self.gridspec.bbox_relative.x1 - 1
|
|
104
|
+
|
|
105
|
+
def x1(self, item: str) -> float:
|
|
106
|
+
"""
|
|
107
|
+
Lower x-coordinate in figure space of the item
|
|
108
|
+
"""
|
|
109
|
+
return self.to_figure_space(1 - self.sum_incl(item))
|
|
110
|
+
|
|
111
|
+
def x2(self, item: str) -> float:
|
|
112
|
+
"""
|
|
113
|
+
Higher x-coordinate in figure space of the item
|
|
114
|
+
"""
|
|
115
|
+
return self.to_figure_space(1 - self.sum_upto(item))
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def items_right_relative(self):
|
|
119
|
+
"""
|
|
120
|
+
Right (relative to the gridspec) of the panels in figure dimensions
|
|
121
|
+
"""
|
|
122
|
+
return 1 - self.total
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def items_right(self):
|
|
126
|
+
"""
|
|
127
|
+
Right of the panels in figure space
|
|
128
|
+
"""
|
|
129
|
+
return self.to_figure_space(self.items_right_relative)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class composition_top_space(_composition_side_space):
|
|
133
|
+
"""
|
|
134
|
+
Space for annotations above the actual composition
|
|
135
|
+
|
|
136
|
+
Ordered from the edge of the figure and going inwards
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
plot_margin: float = 0
|
|
140
|
+
plot_title_margin_top: float = 0
|
|
141
|
+
plot_title: float = 0
|
|
142
|
+
plot_title_margin_bottom: float = 0
|
|
143
|
+
plot_subtitle_margin_top: float = 0
|
|
144
|
+
plot_subtitle: float = 0
|
|
145
|
+
plot_subtitle_margin_bottom: float = 0
|
|
146
|
+
|
|
147
|
+
def _calculate(self):
|
|
148
|
+
items = self.items
|
|
149
|
+
theme = self.items.cmp.theme
|
|
150
|
+
geometry = self.items.geometry
|
|
151
|
+
W, H = theme.getp("figure_size")
|
|
152
|
+
F = W / H
|
|
153
|
+
|
|
154
|
+
self.plot_margin = theme.getp("plot_margin_top") * F
|
|
155
|
+
|
|
156
|
+
if items.plot_title:
|
|
157
|
+
m = theme.get_margin("plot_title").fig
|
|
158
|
+
self.plot_title_margin_top = m.t * F
|
|
159
|
+
self.plot_title = geometry.height(items.plot_title)
|
|
160
|
+
self.plot_title_margin_bottom = m.b * F
|
|
161
|
+
|
|
162
|
+
if items.plot_subtitle:
|
|
163
|
+
m = theme.get_margin("plot_subtitle").fig
|
|
164
|
+
self.plot_subtitle_margin_top = m.t * F
|
|
165
|
+
self.plot_subtitle = geometry.height(items.plot_subtitle)
|
|
166
|
+
self.plot_subtitle_margin_bottom = m.b * F
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def offset(self) -> float:
|
|
170
|
+
"""
|
|
171
|
+
Distance from top of the figure to the top of the composition gridspec
|
|
172
|
+
|
|
173
|
+
----------------(1, 1)
|
|
174
|
+
| ^ |
|
|
175
|
+
| |-dy |
|
|
176
|
+
| v |
|
|
177
|
+
| ---- |
|
|
178
|
+
| | | |
|
|
179
|
+
| | | |
|
|
180
|
+
| | | |
|
|
181
|
+
| ---- |
|
|
182
|
+
| |
|
|
183
|
+
(0, 0)----------------
|
|
184
|
+
"""
|
|
185
|
+
return self.gridspec.bbox_relative.y1 - 1
|
|
186
|
+
|
|
187
|
+
def y1(self, item: str) -> float:
|
|
188
|
+
"""
|
|
189
|
+
Lower y-coordinate in figure space of the item
|
|
190
|
+
"""
|
|
191
|
+
return self.to_figure_space(1 - self.sum_incl(item))
|
|
192
|
+
|
|
193
|
+
def y2(self, item: str) -> float:
|
|
194
|
+
"""
|
|
195
|
+
Higher y-coordinate in figure space of the item
|
|
196
|
+
"""
|
|
197
|
+
return self.to_figure_space(1 - self.sum_upto(item))
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def items_top_relative(self):
|
|
201
|
+
"""
|
|
202
|
+
Top (relative to the gridspec) of the panels in figure dimensions
|
|
203
|
+
"""
|
|
204
|
+
return 1 - self.total
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def items_top(self):
|
|
208
|
+
"""
|
|
209
|
+
Top of the composition items in figure space
|
|
210
|
+
"""
|
|
211
|
+
return self.to_figure_space(self.items_top_relative)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class composition_bottom_space(_composition_side_space):
|
|
215
|
+
"""
|
|
216
|
+
Space in the figure for artists below the panel area
|
|
217
|
+
|
|
218
|
+
Ordered from the edge of the figure and going inwards
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
plot_margin: float = 0
|
|
222
|
+
plot_caption_margin_bottom: float = 0
|
|
223
|
+
plot_caption: float = 0
|
|
224
|
+
plot_caption_margin_top: float = 0
|
|
225
|
+
|
|
226
|
+
def _calculate(self):
|
|
227
|
+
items = self.items
|
|
228
|
+
theme = self.items.cmp.theme
|
|
229
|
+
geometry = self.items.geometry
|
|
230
|
+
W, H = theme.getp("figure_size")
|
|
231
|
+
F = W / H
|
|
232
|
+
|
|
233
|
+
self.plot_margin = theme.getp("plot_margin_bottom") * F
|
|
234
|
+
|
|
235
|
+
if items.plot_caption:
|
|
236
|
+
m = theme.get_margin("plot_caption").fig
|
|
237
|
+
self.plot_caption_margin_bottom = m.b * F
|
|
238
|
+
self.plot_caption = geometry.height(items.plot_caption)
|
|
239
|
+
self.plot_caption_margin_top = m.t * F
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def offset(self) -> float:
|
|
243
|
+
"""
|
|
244
|
+
Distance from bottom of the figure to the composition gridspec
|
|
245
|
+
|
|
246
|
+
----------------(1, 1)
|
|
247
|
+
| |
|
|
248
|
+
| ---- |
|
|
249
|
+
| | | |
|
|
250
|
+
| | | |
|
|
251
|
+
| | | |
|
|
252
|
+
| ---- |
|
|
253
|
+
| ^ |
|
|
254
|
+
| |dy |
|
|
255
|
+
| v |
|
|
256
|
+
(0, 0)----------------
|
|
257
|
+
"""
|
|
258
|
+
return self.gridspec.bbox_relative.y0
|
|
259
|
+
|
|
260
|
+
def y1(self, item: str) -> float:
|
|
261
|
+
"""
|
|
262
|
+
Lower y-coordinate in figure space of the item
|
|
263
|
+
"""
|
|
264
|
+
return self.to_figure_space(self.sum_upto(item))
|
|
265
|
+
|
|
266
|
+
def y2(self, item: str) -> float:
|
|
267
|
+
"""
|
|
268
|
+
Higher y-coordinate in figure space of the item
|
|
269
|
+
"""
|
|
270
|
+
return self.to_figure_space(self.sum_incl(item))
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def items_bottom_relative(self):
|
|
274
|
+
"""
|
|
275
|
+
Bottom (relative to the gridspec) of the panels in figure dimensions
|
|
276
|
+
"""
|
|
277
|
+
return self.total
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def items_bottom(self):
|
|
281
|
+
"""
|
|
282
|
+
Bottom of the panels in figure space
|
|
283
|
+
"""
|
|
284
|
+
return self.to_figure_space(self.items_bottom_relative)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class CompositionSideSpaces:
|
|
288
|
+
"""
|
|
289
|
+
Compute the spaces required to layout the composition
|
|
290
|
+
|
|
291
|
+
This is meant for the top-most composition
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
def __init__(self, cmp: Compose):
|
|
295
|
+
self.cmp = cmp
|
|
296
|
+
self.gridspec = cmp._gridspec
|
|
297
|
+
self.sub_gridspec = cmp._sub_gridspec
|
|
298
|
+
self.items = CompositionLayoutItems(cmp)
|
|
299
|
+
|
|
300
|
+
self.l = composition_left_space(self.items)
|
|
301
|
+
"""All subspaces to the left of the panels"""
|
|
302
|
+
|
|
303
|
+
self.r = composition_right_space(self.items)
|
|
304
|
+
"""All subspaces to the right of the panels"""
|
|
305
|
+
|
|
306
|
+
self.t = composition_top_space(self.items)
|
|
307
|
+
"""All subspaces above the top of the panels"""
|
|
308
|
+
|
|
309
|
+
self.b = composition_bottom_space(self.items)
|
|
310
|
+
"""All subspaces below the bottom of the panels"""
|
|
311
|
+
|
|
312
|
+
self._create_plot_sidespaces()
|
|
313
|
+
self.tree = LayoutTree.create(cmp)
|
|
314
|
+
|
|
315
|
+
def arrange(self):
|
|
316
|
+
"""
|
|
317
|
+
Resize composition and place artists in final positions
|
|
318
|
+
"""
|
|
319
|
+
# We first resize the compositions gridspec so that the tree
|
|
320
|
+
# algorithms can work with the final position and total area.
|
|
321
|
+
self.resize_gridspec()
|
|
322
|
+
self.tree.arrange_layout()
|
|
323
|
+
self.items._move_artists(self)
|
|
324
|
+
self._arrange_plots()
|
|
325
|
+
|
|
326
|
+
def _arrange_plots(self):
|
|
327
|
+
"""
|
|
328
|
+
Arrange all the plots in the composition
|
|
329
|
+
"""
|
|
330
|
+
for plot in self.cmp.iter_plots_all():
|
|
331
|
+
plot._sidespaces.arrange()
|
|
332
|
+
|
|
333
|
+
def _create_plot_sidespaces(self):
|
|
334
|
+
"""
|
|
335
|
+
Create sidespaces for all the plots in the composition
|
|
336
|
+
"""
|
|
337
|
+
for plot in self.cmp.iter_plots_all():
|
|
338
|
+
plot._sidespaces = PlotSideSpaces(plot)
|
|
339
|
+
|
|
340
|
+
def resize_gridspec(self):
|
|
341
|
+
"""
|
|
342
|
+
Apply the space calculations to the sub_gridspec
|
|
343
|
+
|
|
344
|
+
After calling this method, the sub_gridspec will be appropriately
|
|
345
|
+
sized to accomodate the content of the annotations.
|
|
346
|
+
"""
|
|
347
|
+
gsparams = self.calculate_gridspec_params()
|
|
348
|
+
gsparams.validate()
|
|
349
|
+
self.sub_gridspec.update_params_and_artists(gsparams)
|
|
350
|
+
|
|
351
|
+
def calculate_gridspec_params(self) -> GridSpecParams:
|
|
352
|
+
"""
|
|
353
|
+
Grid spacing between compositions w.r.t figure
|
|
354
|
+
"""
|
|
355
|
+
return GridSpecParams(
|
|
356
|
+
self.l.items_left_relative,
|
|
357
|
+
self.r.items_right_relative,
|
|
358
|
+
self.t.items_top_relative,
|
|
359
|
+
self.b.items_bottom_relative,
|
|
360
|
+
0,
|
|
361
|
+
0,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def horizontal_space(self) -> float:
|
|
366
|
+
"""
|
|
367
|
+
Horizontal non-panel space [figure dimensions]
|
|
368
|
+
"""
|
|
369
|
+
return self.l.total + self.r.total
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def vertical_space(self) -> float:
|
|
373
|
+
"""
|
|
374
|
+
Vertical non-panel space [figure dimensions]
|
|
375
|
+
"""
|
|
376
|
+
return self.t.total + self.b.total
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def plot_left(self) -> float:
|
|
380
|
+
"""
|
|
381
|
+
Distance up to left most artist in the composition
|
|
382
|
+
"""
|
|
383
|
+
try:
|
|
384
|
+
return min([l.plot_left for l in self.tree.left_most_spaces])
|
|
385
|
+
except ValueError:
|
|
386
|
+
return self.sub_gridspec.bbox_relative.x0
|
|
387
|
+
|
|
388
|
+
@property
|
|
389
|
+
def plot_right(self) -> float:
|
|
390
|
+
"""
|
|
391
|
+
Distance up to right most artist in the composition
|
|
392
|
+
"""
|
|
393
|
+
try:
|
|
394
|
+
return max([r.plot_right for r in self.tree.right_most_spaces])
|
|
395
|
+
except ValueError:
|
|
396
|
+
# When the user asks for more columns than there are
|
|
397
|
+
# plots/compositions to fill the columns, we get one or
|
|
398
|
+
# more empty columns on the right. i.e. max([])
|
|
399
|
+
# In that case, act as if there is an invisible plot
|
|
400
|
+
# whose right edge is along that of the gridspec.
|
|
401
|
+
return self.sub_gridspec.bbox_relative.x1
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def plot_bottom(self) -> float:
|
|
405
|
+
"""
|
|
406
|
+
Distance up to bottom most artist in the composition
|
|
407
|
+
"""
|
|
408
|
+
try:
|
|
409
|
+
return min([b.plot_bottom for b in self.tree.bottom_most_spaces])
|
|
410
|
+
except ValueError:
|
|
411
|
+
return self.sub_gridspec.bbox_relative.y0
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def plot_top(self) -> float:
|
|
415
|
+
"""
|
|
416
|
+
Distance upto top most artist in the composition
|
|
417
|
+
"""
|
|
418
|
+
try:
|
|
419
|
+
return max([t.plot_top for t in self.tree.top_most_spaces])
|
|
420
|
+
except ValueError:
|
|
421
|
+
return self.sub_gridspec.bbox_relative.y1
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def panel_left(self) -> float:
|
|
425
|
+
"""
|
|
426
|
+
Distance up to left most artist in the composition
|
|
427
|
+
"""
|
|
428
|
+
try:
|
|
429
|
+
return min([l.panel_left for l in self.tree.left_most_spaces])
|
|
430
|
+
except ValueError:
|
|
431
|
+
return self.sub_gridspec.bbox_relative.x0
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def panel_right(self) -> float:
|
|
435
|
+
"""
|
|
436
|
+
Distance up to right most artist in the composition
|
|
437
|
+
"""
|
|
438
|
+
try:
|
|
439
|
+
return max([r.panel_right for r in self.tree.right_most_spaces])
|
|
440
|
+
except ValueError:
|
|
441
|
+
return self.sub_gridspec.bbox_relative.x1
|
|
442
|
+
|
|
443
|
+
@property
|
|
444
|
+
def panel_bottom(self) -> float:
|
|
445
|
+
"""
|
|
446
|
+
Distance up to bottom most artist in the composition
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
return min([b.panel_bottom for b in self.tree.bottom_most_spaces])
|
|
450
|
+
except ValueError:
|
|
451
|
+
return self.sub_gridspec.bbox_relative.y0
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def panel_top(self) -> float:
|
|
455
|
+
"""
|
|
456
|
+
Distance upto top most artist in the composition
|
|
457
|
+
"""
|
|
458
|
+
try:
|
|
459
|
+
return max([t.panel_top for t in self.tree.top_most_spaces])
|
|
460
|
+
except ValueError:
|
|
461
|
+
return self.sub_gridspec.bbox_relative.y1
|