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.
Files changed (64) hide show
  1. plotnine/__init__.py +31 -37
  2. plotnine/_mpl/gridspec.py +265 -0
  3. plotnine/_mpl/layout_manager/__init__.py +6 -0
  4. plotnine/_mpl/layout_manager/_engine.py +87 -0
  5. plotnine/_mpl/layout_manager/_layout_items.py +916 -0
  6. plotnine/_mpl/layout_manager/_layout_tree.py +625 -0
  7. plotnine/_mpl/layout_manager/_spaces.py +1007 -0
  8. plotnine/_mpl/patches.py +1 -1
  9. plotnine/_mpl/text.py +59 -24
  10. plotnine/_mpl/utils.py +78 -10
  11. plotnine/_utils/__init__.py +5 -5
  12. plotnine/_utils/dev.py +45 -27
  13. plotnine/animation.py +1 -1
  14. plotnine/coords/coord_trans.py +1 -1
  15. plotnine/data/__init__.py +12 -8
  16. plotnine/doctools.py +1 -1
  17. plotnine/facets/facet.py +30 -39
  18. plotnine/facets/facet_grid.py +14 -6
  19. plotnine/facets/facet_wrap.py +3 -5
  20. plotnine/facets/strips.py +7 -9
  21. plotnine/geoms/geom_crossbar.py +2 -3
  22. plotnine/geoms/geom_path.py +1 -1
  23. plotnine/ggplot.py +94 -65
  24. plotnine/guides/guide.py +12 -10
  25. plotnine/guides/guide_colorbar.py +3 -3
  26. plotnine/guides/guide_legend.py +12 -13
  27. plotnine/guides/guides.py +3 -3
  28. plotnine/iapi.py +5 -2
  29. plotnine/labels.py +5 -0
  30. plotnine/mapping/aes.py +4 -3
  31. plotnine/options.py +14 -7
  32. plotnine/plot_composition/__init__.py +10 -0
  33. plotnine/plot_composition/_compose.py +436 -0
  34. plotnine/plot_composition/_plotspec.py +50 -0
  35. plotnine/plot_composition/_spacer.py +32 -0
  36. plotnine/positions/position_dodge.py +1 -1
  37. plotnine/positions/position_dodge2.py +1 -1
  38. plotnine/positions/position_stack.py +1 -2
  39. plotnine/qplot.py +1 -2
  40. plotnine/scales/__init__.py +0 -6
  41. plotnine/scales/scale.py +1 -1
  42. plotnine/stats/binning.py +1 -1
  43. plotnine/stats/smoothers.py +3 -5
  44. plotnine/stats/stat_density.py +1 -1
  45. plotnine/stats/stat_qq_line.py +1 -1
  46. plotnine/stats/stat_sina.py +1 -1
  47. plotnine/themes/elements/__init__.py +2 -0
  48. plotnine/themes/elements/element_text.py +35 -24
  49. plotnine/themes/elements/margin.py +73 -60
  50. plotnine/themes/targets.py +3 -1
  51. plotnine/themes/theme.py +13 -7
  52. plotnine/themes/theme_gray.py +28 -31
  53. plotnine/themes/theme_matplotlib.py +25 -28
  54. plotnine/themes/theme_seaborn.py +31 -34
  55. plotnine/themes/theme_void.py +17 -26
  56. plotnine/themes/themeable.py +290 -157
  57. {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/METADATA +4 -3
  58. {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/RECORD +61 -54
  59. {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/WHEEL +1 -1
  60. plotnine/_mpl/_plot_side_space.py +0 -888
  61. plotnine/_mpl/_plotnine_tight_layout.py +0 -293
  62. plotnine/_mpl/layout_engine.py +0 -110
  63. {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info/licenses}/LICENSE +0 -0
  64. {plotnine-0.14.5.dist-info → plotnine-0.15.0.dev2.dist-info}/top_level.txt +0 -0
@@ -1,293 +0,0 @@
1
- """
2
- Routines to adjust subplot params so that subplots are
3
- nicely fit in the figure. In doing so, only axis labels, tick labels, axes
4
- titles and offsetboxes that are anchored to axes are currently considered.
5
-
6
- Internally, this module assumes that the margins (left margin, etc.) which are
7
- differences between `Axes.get_tightbbox` and `Axes.bbox` are independent of
8
- Axes position. This may fail if `Axes.adjustable` is `datalim` as well as
9
- such cases as when left or right margin are affected by xlabel.
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import typing
15
- from dataclasses import dataclass
16
-
17
- from ._plot_side_space import LRTBSpaces, WHSpaceParts, calculate_panel_spacing
18
- from .utils import bbox_in_figure_space, get_transPanels
19
-
20
- if typing.TYPE_CHECKING:
21
- from typing import Literal, TypeAlias
22
-
23
- from matplotlib.figure import Figure
24
- from matplotlib.text import Text
25
- from matplotlib.transforms import Transform
26
-
27
- from plotnine._mpl.offsetbox import FlexibleAnchoredOffsetbox
28
- from plotnine.facets.facet import facet
29
- from plotnine.iapi import legend_artists
30
-
31
- from .layout_engine import LayoutPack
32
-
33
- AxesLocation: TypeAlias = Literal[
34
- "all", "first_row", "last_row", "first_col", "last_col"
35
- ]
36
-
37
-
38
- @dataclass
39
- class GridSpecParams:
40
- """
41
- Gridspec Parameters
42
- """
43
-
44
- left: float
45
- right: float
46
- top: float
47
- bottom: float
48
- wspace: float
49
- hspace: float
50
-
51
-
52
- @dataclass
53
- class TightParams:
54
- """
55
- All parameters computed for the plotnine tight layout engine
56
- """
57
-
58
- facet: facet
59
- sides: LRTBSpaces
60
- gullies: WHSpaceParts
61
-
62
- def __post_init__(self):
63
- self.params = GridSpecParams(
64
- left=self.sides.left,
65
- right=self.sides.right,
66
- top=self.sides.top,
67
- bottom=self.sides.bottom,
68
- wspace=self.gullies.wspace,
69
- hspace=self.gullies.hspace,
70
- )
71
-
72
- if (ratio := self.facet._aspect_ratio()) is not None:
73
- current_ratio = self.gullies.aspect_ratio
74
- if ratio > current_ratio:
75
- # Increase aspect ratio, taller panels
76
- self._reduce_width(ratio)
77
- elif ratio < current_ratio:
78
- # Increase aspect ratio, wider panels
79
- self._reduce_height(ratio)
80
-
81
- def _reduce_height(self, ratio: float):
82
- """
83
- Reduce the height of axes to get the aspect ratio
84
- """
85
- parts = self.gullies
86
-
87
- # New height w.r.t figure height
88
- h1 = ratio * parts.w * (parts.W / parts.H)
89
-
90
- # Half of the total vertical reduction w.r.t figure height
91
- dh = (parts.h - h1) * self.facet.nrow / 2
92
-
93
- # Reduce plot area height
94
- self.params.top -= dh
95
- self.params.bottom += dh
96
- self.params.hspace = parts.sh / h1
97
-
98
- # Add more vertical plot margin
99
- self.sides.t.plot_margin += dh
100
- self.sides.b.plot_margin += dh
101
-
102
- def _reduce_width(self, ratio: float):
103
- """
104
- Reduce the width of axes to get the aspect ratio
105
- """
106
- parts = self.gullies
107
-
108
- # New width w.r.t figure width
109
- w1 = (parts.h * parts.H) / (ratio * parts.W)
110
-
111
- # Half of the total horizontal reduction w.r.t figure width
112
- dw = (parts.w - w1) * self.facet.ncol / 2
113
-
114
- # Reduce width
115
- self.params.left += dw
116
- self.params.right -= dw
117
- self.params.wspace = parts.sw / w1
118
-
119
- # Add more horizontal margin
120
- self.sides.l.plot_margin += dw
121
- self.sides.r.plot_margin += dw
122
-
123
-
124
- def get_plotnine_tight_layout(pack: LayoutPack) -> TightParams:
125
- """
126
- Compute tight layout parameters
127
- """
128
- sides = LRTBSpaces(pack)
129
- gullies = calculate_panel_spacing(pack, sides)
130
- tight_params = TightParams(pack.facet, sides, gullies)
131
- return tight_params
132
-
133
-
134
- def set_figure_artist_positions(
135
- pack: LayoutPack,
136
- tparams: TightParams,
137
- ):
138
- """
139
- Set the x,y position of the artists around the panels
140
- """
141
- theme = pack.theme
142
- sides = tparams.sides
143
- params = tparams.params
144
-
145
- if pack.plot_title:
146
- ha = theme.getp(("plot_title", "ha"))
147
- pack.plot_title.set_y(sides.t.edge("plot_title"))
148
- horizontally_align_text_with_panels(pack.plot_title, params, ha, pack)
149
-
150
- if pack.plot_subtitle:
151
- ha = theme.getp(("plot_subtitle", "ha"))
152
- pack.plot_subtitle.set_y(sides.t.edge("plot_subtitle"))
153
- horizontally_align_text_with_panels(
154
- pack.plot_subtitle, params, ha, pack
155
- )
156
-
157
- if pack.plot_caption:
158
- ha = theme.getp(("plot_caption", "ha"), "right")
159
- pack.plot_caption.set_y(sides.b.edge("plot_caption"))
160
- horizontally_align_text_with_panels(
161
- pack.plot_caption, params, ha, pack
162
- )
163
-
164
- if pack.axis_title_x:
165
- ha = theme.getp(("axis_title_x", "ha"), "center")
166
- pack.axis_title_x.set_y(sides.b.edge("axis_title_x"))
167
- horizontally_align_text_with_panels(
168
- pack.axis_title_x, params, ha, pack
169
- )
170
-
171
- if pack.axis_title_y:
172
- va = theme.getp(("axis_title_y", "va"), "center")
173
- pack.axis_title_y.set_x(sides.l.edge("axis_title_y"))
174
- vertically_align_text_with_panels(pack.axis_title_y, params, va, pack)
175
-
176
- if pack.legends:
177
- set_legends_position(pack.legends, tparams, pack.figure)
178
-
179
-
180
- def horizontally_align_text_with_panels(
181
- text: Text, params: GridSpecParams, ha: str | float, pack: LayoutPack
182
- ):
183
- """
184
- Horizontal justification
185
-
186
- Reinterpret horizontal alignment to be justification about the panels.
187
- """
188
- if isinstance(ha, str):
189
- lookup = {
190
- "left": 0.0,
191
- "center": 0.5,
192
- "right": 1.0,
193
- }
194
- f = lookup[ha]
195
- else:
196
- f = ha
197
-
198
- box = bbox_in_figure_space(text, pack.figure, pack.renderer)
199
- x = params.left * (1 - f) + (params.right - box.width) * f
200
- text.set_x(x)
201
- text.set_horizontalalignment("left")
202
-
203
-
204
- def vertically_align_text_with_panels(
205
- text: Text, params: GridSpecParams, va: str | float, pack: LayoutPack
206
- ):
207
- """
208
- Vertical justification
209
-
210
- Reinterpret vertical alignment to be justification about the panels.
211
- """
212
- if isinstance(va, str):
213
- lookup = {
214
- "top": 1.0,
215
- "center": 0.5,
216
- "baseline": 0.5,
217
- "center_baseline": 0.5,
218
- "bottom": 0.0,
219
- }
220
- f = lookup[va]
221
- else:
222
- f = va
223
-
224
- box = bbox_in_figure_space(text, pack.figure, pack.renderer)
225
- y = params.bottom * (1 - f) + (params.top - box.height) * f
226
- text.set_y(y)
227
- text.set_verticalalignment("bottom")
228
-
229
-
230
- def set_legends_position(
231
- legends: legend_artists,
232
- tparams: TightParams,
233
- fig: Figure,
234
- ):
235
- """
236
- Place legend on the figure and justify is a required
237
- """
238
-
239
- def set_position(
240
- aob: FlexibleAnchoredOffsetbox,
241
- anchor_point: tuple[float, float],
242
- xy_loc: tuple[float, float],
243
- transform: Transform = fig.transFigure,
244
- ):
245
- """
246
- Place box (by the anchor point) at given xy location
247
-
248
- Parameters
249
- ----------
250
- aob :
251
- Offsetbox to place
252
- anchor_point :
253
- Point on the Offsefbox.
254
- xy_loc :
255
- Point where to place the offsetbox.
256
- transform :
257
- Transformation
258
- """
259
- aob.xy_loc = xy_loc
260
- aob.set_bbox_to_anchor(anchor_point, transform) # type: ignore
261
-
262
- sides = tparams.sides
263
- params = fig.subplotpars
264
-
265
- if legends.right:
266
- j = legends.right.justification
267
- y = params.bottom * (1 - j) + (params.top - sides.r._legend_height) * j
268
- x = sides.r.edge("legend")
269
- set_position(legends.right.box, (x, y), (1, 0))
270
-
271
- if legends.left:
272
- j = legends.left.justification
273
- y = params.bottom * (1 - j) + (params.top - sides.l._legend_height) * j
274
- x = sides.l.edge("legend")
275
- set_position(legends.left.box, (x, y), (0, 0))
276
-
277
- if legends.top:
278
- j = legends.top.justification
279
- x = params.left * (1 - j) + (params.right - sides.t._legend_width) * j
280
- y = sides.t.edge("legend")
281
- set_position(legends.top.box, (x, y), (0, 1))
282
-
283
- if legends.bottom:
284
- j = legends.bottom.justification
285
- x = params.left * (1 - j) + (params.right - sides.b._legend_width) * j
286
- y = sides.b.edge("legend")
287
- set_position(legends.bottom.box, (x, y), (0, 0))
288
-
289
- # Inside legends are placed using the panels coordinate system
290
- if legends.inside:
291
- transPanels = get_transPanels(fig)
292
- for l in legends.inside:
293
- set_position(l.box, l.position, l.justification, transPanels)
@@ -1,110 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import typing
4
- from dataclasses import asdict, dataclass
5
-
6
- from matplotlib.layout_engine import LayoutEngine
7
- from matplotlib.text import Text
8
-
9
- if typing.TYPE_CHECKING:
10
- from typing import Any, Optional
11
-
12
- from matplotlib.axes import Axes
13
- from matplotlib.backend_bases import RendererBase
14
- from matplotlib.figure import Figure
15
-
16
- from plotnine import ggplot, theme
17
- from plotnine._mpl.text import StripText
18
- from plotnine.facets.facet import facet
19
- from plotnine.iapi import legend_artists
20
-
21
-
22
- @dataclass
23
- class LayoutPack:
24
- """
25
- Objects required to compute the layout
26
- """
27
-
28
- axs: list[Axes]
29
- figure: Figure
30
- renderer: RendererBase
31
- theme: theme
32
- facet: facet
33
- axis_title_x: Optional[Text] = None
34
- axis_title_y: Optional[Text] = None
35
- # The legends references the structure that contains the
36
- # AnchoredOffsetboxes (groups of legends)
37
- legends: Optional[legend_artists] = None
38
- plot_caption: Optional[Text] = None
39
- plot_subtitle: Optional[Text] = None
40
- plot_title: Optional[Text] = None
41
- strip_text_x: Optional[list[StripText]] = None
42
- strip_text_y: Optional[list[StripText]] = None
43
-
44
-
45
- class PlotnineLayoutEngine(LayoutEngine):
46
- """
47
- Implement geometry management for plotnine plots
48
-
49
- This layout manager automatically adjusts the location of
50
- objects placed around the plot panels and the subplot
51
- spacing parameters so that the plot fits cleanly within
52
- the figure area.
53
- """
54
-
55
- _adjust_compatible = True
56
- _colorbar_gridspec = False
57
-
58
- def __init__(self, plot: ggplot):
59
- self.plot = plot
60
- self.theme = plot.theme
61
-
62
- def execute(self, fig: Figure):
63
- from contextlib import nullcontext
64
-
65
- from ._plotnine_tight_layout import (
66
- get_plotnine_tight_layout,
67
- set_figure_artist_positions,
68
- )
69
-
70
- pack = self.setup()
71
-
72
- with getattr(pack.renderer, "_draw_disabled", nullcontext)():
73
- tparams = get_plotnine_tight_layout(pack)
74
-
75
- fig.subplots_adjust(**asdict(tparams.params))
76
- set_figure_artist_positions(pack, tparams)
77
-
78
- def setup(self) -> LayoutPack:
79
- """
80
- Put together objects required to do the layout
81
- """
82
- targets = self.theme.targets
83
-
84
- def get_target(name: str) -> Any:
85
- """
86
- Return themeable target or None
87
- """
88
- if self.theme.T.is_blank(name):
89
- return None
90
- else:
91
- t = getattr(targets, name)
92
- if isinstance(t, Text) and t.get_text() == "":
93
- return None
94
- return t
95
-
96
- return LayoutPack(
97
- axs=self.plot.axs,
98
- figure=self.plot.figure,
99
- renderer=self.plot.figure._get_renderer(), # pyright: ignore
100
- theme=self.theme,
101
- facet=self.plot.facet,
102
- axis_title_x=get_target("axis_title_x"),
103
- axis_title_y=get_target("axis_title_y"),
104
- legends=get_target("legends"),
105
- plot_caption=get_target("plot_caption"),
106
- plot_subtitle=get_target("plot_subtitle"),
107
- plot_title=get_target("plot_title"),
108
- strip_text_x=get_target("strip_text_x"),
109
- strip_text_y=get_target("strip_text_y"),
110
- )