plotnine 0.15.0a1__py3-none-any.whl → 0.15.0a3__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 (86) hide show
  1. plotnine/_mpl/layout_manager/_layout_items.py +85 -23
  2. plotnine/_mpl/layout_manager/_layout_tree.py +16 -6
  3. plotnine/_mpl/layout_manager/_spaces.py +5 -5
  4. plotnine/_mpl/patches.py +70 -34
  5. plotnine/_mpl/text.py +150 -63
  6. plotnine/_mpl/utils.py +1 -1
  7. plotnine/_utils/__init__.py +30 -2
  8. plotnine/doctools.py +1 -1
  9. plotnine/facets/strips.py +17 -28
  10. plotnine/geoms/annotation_logticks.py +7 -8
  11. plotnine/geoms/annotation_stripes.py +6 -6
  12. plotnine/geoms/geom.py +20 -8
  13. plotnine/geoms/geom_abline.py +3 -2
  14. plotnine/geoms/geom_blank.py +0 -3
  15. plotnine/geoms/geom_boxplot.py +4 -4
  16. plotnine/geoms/geom_crossbar.py +3 -3
  17. plotnine/geoms/geom_dotplot.py +1 -1
  18. plotnine/geoms/geom_errorbar.py +2 -2
  19. plotnine/geoms/geom_errorbarh.py +2 -2
  20. plotnine/geoms/geom_hline.py +3 -2
  21. plotnine/geoms/geom_linerange.py +2 -2
  22. plotnine/geoms/geom_map.py +3 -3
  23. plotnine/geoms/geom_path.py +10 -11
  24. plotnine/geoms/geom_point.py +4 -5
  25. plotnine/geoms/geom_pointrange.py +3 -5
  26. plotnine/geoms/geom_polygon.py +2 -3
  27. plotnine/geoms/geom_raster.py +4 -5
  28. plotnine/geoms/geom_rect.py +3 -4
  29. plotnine/geoms/geom_ribbon.py +7 -7
  30. plotnine/geoms/geom_rug.py +1 -1
  31. plotnine/geoms/geom_segment.py +2 -2
  32. plotnine/geoms/geom_smooth.py +3 -3
  33. plotnine/geoms/geom_step.py +2 -2
  34. plotnine/geoms/geom_text.py +2 -3
  35. plotnine/geoms/geom_violin.py +4 -5
  36. plotnine/geoms/geom_vline.py +3 -2
  37. plotnine/guides/guides.py +1 -1
  38. plotnine/helpers.py +49 -0
  39. plotnine/iapi.py +28 -5
  40. plotnine/layer.py +18 -12
  41. plotnine/mapping/_eval_environment.py +1 -1
  42. plotnine/scales/scale_color.py +46 -14
  43. plotnine/scales/scale_continuous.py +5 -4
  44. plotnine/scales/scale_datetime.py +28 -14
  45. plotnine/scales/scale_discrete.py +2 -2
  46. plotnine/scales/scale_identity.py +10 -2
  47. plotnine/scales/scale_xy.py +2 -2
  48. plotnine/stats/binning.py +4 -1
  49. plotnine/stats/smoothers.py +19 -19
  50. plotnine/stats/stat.py +15 -25
  51. plotnine/stats/stat_bin.py +2 -5
  52. plotnine/stats/stat_bin_2d.py +7 -9
  53. plotnine/stats/stat_bindot.py +6 -11
  54. plotnine/stats/stat_boxplot.py +5 -5
  55. plotnine/stats/stat_count.py +5 -9
  56. plotnine/stats/stat_density.py +6 -9
  57. plotnine/stats/stat_density_2d.py +12 -9
  58. plotnine/stats/stat_ecdf.py +6 -5
  59. plotnine/stats/stat_ellipse.py +5 -6
  60. plotnine/stats/stat_function.py +6 -8
  61. plotnine/stats/stat_hull.py +2 -3
  62. plotnine/stats/stat_identity.py +1 -2
  63. plotnine/stats/stat_pointdensity.py +4 -7
  64. plotnine/stats/stat_qq.py +45 -20
  65. plotnine/stats/stat_qq_line.py +15 -11
  66. plotnine/stats/stat_quantile.py +6 -7
  67. plotnine/stats/stat_sina.py +12 -14
  68. plotnine/stats/stat_smooth.py +7 -10
  69. plotnine/stats/stat_sum.py +1 -2
  70. plotnine/stats/stat_summary.py +6 -9
  71. plotnine/stats/stat_summary_bin.py +10 -13
  72. plotnine/stats/stat_unique.py +1 -2
  73. plotnine/stats/stat_ydensity.py +7 -10
  74. plotnine/themes/elements/__init__.py +2 -1
  75. plotnine/themes/elements/margin.py +64 -1
  76. plotnine/themes/theme_gray.py +5 -3
  77. plotnine/themes/theme_matplotlib.py +5 -4
  78. plotnine/themes/theme_seaborn.py +7 -4
  79. plotnine/themes/theme_void.py +11 -4
  80. plotnine/themes/themeable.py +2 -2
  81. plotnine/typing.py +2 -2
  82. {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/METADATA +7 -4
  83. {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/RECORD +86 -85
  84. {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/WHEEL +1 -1
  85. {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/licenses/LICENSE +0 -0
  86. {plotnine-0.15.0a1.dist-info → plotnine-0.15.0a3.dist-info}/top_level.txt +0 -0
plotnine/_mpl/text.py CHANGED
@@ -1,9 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from functools import lru_cache
3
4
  from typing import TYPE_CHECKING
4
5
 
6
+ from matplotlib import artist
5
7
  from matplotlib.text import Text
6
8
 
9
+ from plotnine._utils import ha_as_float, va_as_float
10
+
7
11
  from .patches import StripTextPatch
8
12
  from .utils import bbox_in_axes_space, rel_position
9
13
 
@@ -11,7 +15,6 @@ if TYPE_CHECKING:
11
15
  from matplotlib.backend_bases import RendererBase
12
16
 
13
17
  from plotnine.iapi import strip_draw_info
14
- from plotnine.typing import HorizontalJustification, VerticalJustification
15
18
 
16
19
 
17
20
  class StripText(Text):
@@ -28,86 +31,170 @@ class StripText(Text):
28
31
  "transform": info.ax.transAxes,
29
32
  "clip_on": False,
30
33
  "zorder": 3.3,
34
+ # Since the text can be rotated, it is simpler to anchor it at
35
+ # the center, align it then do the rotation. Vertically,
36
+ # center_baseline places the text in the visual center, but
37
+ # only if it is one line. For multiline text, we are better
38
+ # off with plain center.
39
+ "ha": "center",
40
+ "va": "center_baseline" if info.is_oneline else "center",
41
+ "rotation_mode": "anchor",
31
42
  }
32
43
 
33
- super().__init__(
34
- info.x,
35
- info.y,
36
- info.label,
37
- **kwargs,
38
- )
44
+ super().__init__(0, 0, info.label, **kwargs)
39
45
  self.draw_info = info
40
46
  self.patch = StripTextPatch(self)
41
47
 
42
- # TODO: Move these _justify methods to the layout manager
43
- # We need to first make sure that the patch has the final size during
44
- # layout computation. Right now, the final size is calculated during
45
- # draw (in these justify methods)
46
- def _justify_horizontally(self, renderer):
48
+ # self.set_horizontalalignment("center")
49
+ # self.set_verticalalignment(
50
+ # "center_baseline" if info.is_oneline else "center"
51
+ # )
52
+ # self.set_rotation_mode("anchor")
53
+
54
+ # TODO: This should really be part of the unit conversions in the
55
+ # margin class.
56
+ @lru_cache(2)
57
+ def _line_height(self, renderer) -> float:
58
+ """
59
+ The line height in display space of the text on the canvas
60
+ """
61
+ # Text string, (width, height), x, y
62
+ parts: list[tuple[str, tuple[float, float], float, float]]
63
+
64
+ try:
65
+ # matplotlib.Text._get_layout is a private API and we cannot
66
+ # tell how using it may fail in the future.
67
+ _, parts, _ = self._get_layout(renderer) # pyright: ignore[reportAttributeAccessIssue]
68
+ except Exception:
69
+ from warnings import warn
70
+
71
+ from plotnine.exceptions import PlotnineWarning
72
+
73
+ # The canvas height is nearly always bigger than the stated
74
+ # fontsize. 1.36 is a good multiplication factor obtained by
75
+ # some rough exploration
76
+ f = 1.36
77
+ size = self.get_fontsize()
78
+ height = round(size * f) if isinstance(size, int) else 14
79
+ warn(
80
+ f"Could not calculate line height for {self.get_text()}. "
81
+ "Using an estimate, please let us know about this at "
82
+ "https://github.com/has2k1/plotnine/issues",
83
+ PlotnineWarning,
84
+ )
85
+ else:
86
+ # The the text has multiple lines, we use the maximum height
87
+ # of anyone single line.
88
+ height = max([p[1][1] for p in parts])
89
+
90
+ return height
91
+
92
+ def _set_position(self, renderer):
93
+ """
94
+ Set the postion of the text within the strip_background
95
+ """
96
+ # We have to two premises that depend on each other:
97
+ #
98
+ # 1. The breadth of the strip_background grows to accomodate
99
+ # the strip_text.
100
+ # 2. The strip_text is justified within the strip_background.
101
+ #
102
+ # From these we note that the strip_background does not need the
103
+ # position of the strip_text, but it needs its size. Therefore
104
+ # we implement StripTextPatch.get_window_extent can use
105
+ # StripText.get_window_extent, peeking only at the size.
106
+ #
107
+ # And we implement StripText._set_position_* to use
108
+ # StripTextPatch.get_window_extent and make the calculations in
109
+ # both methods independent.
110
+ if self.draw_info.position == "top":
111
+ self._set_position_top(renderer)
112
+ else: # "right"
113
+ self._set_position_right(renderer)
114
+
115
+ def _set_position_top(self, renderer):
47
116
  """
48
- Justify the text along the strip_background
117
+ Set position of the text within the top strip_background
49
118
  """
50
119
  info = self.draw_info
51
- lookup: dict[HorizontalJustification, float] = {
52
- "left": 0.0,
53
- "center": 0.5,
54
- "right": 1.0,
55
- }
56
- rel = lookup.get(info.ha, 0.5) if isinstance(info.ha, str) else info.ha
57
- patch_bbox = bbox_in_axes_space(self.patch, info.ax, renderer)
58
- text_bbox = bbox_in_axes_space(self, info.ax, renderer)
59
- l, b, w, h = info.x, info.y, info.box_width, patch_bbox.height
60
- b = b + patch_bbox.height * info.strip_align
61
- x = rel_position(rel, text_bbox.width, patch_bbox.x0, patch_bbox.x1)
62
- y = b + h / 2
63
- self.set_horizontalalignment("left")
64
- self.patch.set_bounds(l, b, w, h)
120
+ ha, va, ax, m = info.ha, info.va, info.ax, info.margin
121
+
122
+ rel_x, rel_y = ha_as_float(ha), va_as_float(va)
123
+ patch_bbox = bbox_in_axes_space(self.patch, ax, renderer)
124
+ text_bbox = bbox_in_axes_space(self, ax, renderer)
125
+
126
+ # line_height and margins in axes space
127
+ line_height = self._line_height(renderer) / ax.bbox.height
128
+
129
+ x = (
130
+ # Justify horizontally within the strip_background
131
+ rel_position(
132
+ rel_x,
133
+ text_bbox.width + (line_height * (m.l + m.r)),
134
+ patch_bbox.x0,
135
+ patch_bbox.x1,
136
+ )
137
+ + (m.l * line_height)
138
+ + text_bbox.width / 2
139
+ )
140
+ # Setting the y position based on the bounding box is wrong
141
+ y = (
142
+ rel_position(
143
+ rel_y,
144
+ text_bbox.height,
145
+ patch_bbox.y0 + m.b * line_height,
146
+ patch_bbox.y1 - m.t * line_height,
147
+ )
148
+ + text_bbox.height / 2
149
+ )
65
150
  self.set_position((x, y))
66
151
 
67
- def _justify_vertically(self, renderer):
152
+ def _set_position_right(self, renderer):
68
153
  """
69
- Justify the text along the strip_background
154
+ Set position of the text within the bottom strip_background
70
155
  """
71
- # Note that the strip text & background and horizontal but
72
- # rotated to appear vertical. So we really are still justifying
73
- # horizontally.
74
156
  info = self.draw_info
75
- lookup: dict[VerticalJustification, float] = {
76
- "bottom": 0.0,
77
- "center": 0.5,
78
- "top": 1.0,
79
- }
80
- rel = lookup.get(info.va, 0.5) if isinstance(info.va, str) else info.va
81
- patch_bbox = bbox_in_axes_space(self.patch, info.ax, renderer)
82
- text_bbox = bbox_in_axes_space(self, info.ax, renderer)
83
- l, b, w, h = info.x, info.y, patch_bbox.width, info.box_height
84
- l = l + patch_bbox.width * info.strip_align
85
- x = l + w / 2
86
- y = rel_position(rel, text_bbox.height, patch_bbox.y0, patch_bbox.y1)
87
- self.set_horizontalalignment("right") # 90CW right means bottom
88
- self.patch.set_bounds(l, b, w, h)
89
- self.set_position((x, y))
157
+ ha, va, ax, m = info.ha, info.va, info.ax, info.margin
90
158
 
91
- def draw(self, renderer: RendererBase):
92
- if not self.get_visible():
93
- return
159
+ # bboxes in axes space
160
+ patch_bbox = bbox_in_axes_space(self.patch, ax, renderer)
161
+ text_bbox = bbox_in_axes_space(self, ax, renderer)
94
162
 
95
- # expand strip_text patch to contain the text
96
- self.patch.update_position_size(renderer)
163
+ # line_height in axes space
164
+ line_height = self._line_height(renderer) / ax.bbox.width
97
165
 
98
- # Align patch across the edge of the panel
99
- if self.draw_info.position == "top":
100
- self._justify_horizontally(renderer)
101
- else: # "right"
102
- self._justify_vertically(renderer)
166
+ rel_x, rel_y = ha_as_float(ha), va_as_float(va)
103
167
 
104
- self.patch.set_transform(self.draw_info.ax.transAxes)
105
- self.patch.set_mutation_scale(0)
168
+ x = (
169
+ rel_position(
170
+ rel_x,
171
+ text_bbox.width,
172
+ patch_bbox.x0 + m.l * line_height,
173
+ patch_bbox.x1 - m.r * line_height,
174
+ )
175
+ + text_bbox.width / 2
176
+ )
177
+ y = (
178
+ # Justify vertically within the strip_background
179
+ rel_position(
180
+ rel_y,
181
+ text_bbox.height + ((m.b + m.t) * line_height),
182
+ patch_bbox.y0,
183
+ patch_bbox.y1,
184
+ )
185
+ + (m.b * line_height)
186
+ + text_bbox.height / 2
187
+ )
188
+ self.set_position((x, y))
106
189
 
107
- # Put text in center of patch
108
- self.set_rotation_mode("anchor")
109
- self.set_verticalalignment("center_baseline")
190
+ @artist.allow_rasterization
191
+ def draw(self, renderer: RendererBase):
192
+ """
193
+ Draw text along with the patch
194
+ """
195
+ if not self.get_visible():
196
+ return
110
197
 
111
- # Draw spatch
198
+ self._set_position(renderer)
112
199
  self.patch.draw(renderer)
113
200
  return super().draw(renderer)
plotnine/_mpl/utils.py CHANGED
@@ -139,7 +139,7 @@ def draw_bbox(bbox, figure, color="black", **kwargs):
139
139
  width=bbox.width,
140
140
  height=bbox.height,
141
141
  edgecolor=color,
142
- fill=False,
142
+ fill="facecolor" in kwargs,
143
143
  clip_on=False,
144
144
  **kwargs,
145
145
  )
@@ -35,8 +35,10 @@ if TYPE_CHECKING:
35
35
  DataLike,
36
36
  FloatArray,
37
37
  FloatArrayLike,
38
+ HorizontalJustification,
38
39
  IntArray,
39
40
  Side,
41
+ VerticalJustification,
40
42
  )
41
43
 
42
44
  T = TypeVar("T")
@@ -344,6 +346,8 @@ def _id_var(x: AnyArrayLike, drop: bool = False) -> list[int]:
344
346
  # NaNs are -1, we give them the highest code
345
347
  nan_code = -1
346
348
  new_nan_code = np.max(x.cat.codes) + 1
349
+ # TODO: We are assuming that x is of type Sequence[int|nan]
350
+ # is that accurate.
347
351
  lst = [val if val != nan_code else new_nan_code for val in x]
348
352
  else:
349
353
  lst = list(x.cat.codes + 1)
@@ -357,7 +361,7 @@ def _id_var(x: AnyArrayLike, drop: bool = False) -> list[int]:
357
361
  lst = match(x, levels)
358
362
  lst = [item + 1 for item in lst]
359
363
 
360
- return lst
364
+ return lst # pyright: ignore[reportReturnType]
361
365
 
362
366
 
363
367
  def join_keys(x, y, by=None):
@@ -511,7 +515,7 @@ def remove_missing(
511
515
  if finite:
512
516
  lst = [np.inf, -np.inf]
513
517
  to_replace = {v: lst for v in vars}
514
- data.replace(to_replace, np.nan, inplace=True) # pyright: ignore[reportArgumentType,reportCallIssue]
518
+ data.replace(to_replace, np.nan, inplace=True)
515
519
  txt = "non-finite"
516
520
  else:
517
521
  txt = "missing"
@@ -1264,3 +1268,27 @@ def ensure_xy_location(
1264
1268
  if isinstance(h, (int, float)) and isinstance(v, (int, float)):
1265
1269
  return (h, v)
1266
1270
  raise ValueError(f"Cannot make a location from '{loc}'")
1271
+
1272
+
1273
+ def ha_as_float(ha: HorizontalJustification | float) -> float:
1274
+ """
1275
+ Return horizontal alignment as a float
1276
+ """
1277
+ lookup = {"left": 0.0, "center": 0.5, "right": 1.0}
1278
+ return lookup[ha] if isinstance(ha, str) else ha
1279
+
1280
+
1281
+ def va_as_float(va: VerticalJustification | float) -> float:
1282
+ """
1283
+ Return vertical alignment as a float
1284
+ """
1285
+ lookup = {
1286
+ "top": 1.0,
1287
+ "center": 0.5,
1288
+ "bottom": 0.0,
1289
+ # baseline and center_baseline are valid for texts but we do
1290
+ # not handle them accurately
1291
+ "baseline": 0.5,
1292
+ "center_baseline": 0.5,
1293
+ }
1294
+ return lookup[va] if isinstance(va, str) else va
plotnine/doctools.py CHANGED
@@ -451,7 +451,7 @@ def document_geom(geom: type[geom]) -> type[geom]:
451
451
  table = dict_to_table(("Aesthetic", "Default value"), contents)
452
452
  aesthetics_table = AESTHETICS_TABLE_TPL.format(table=table)
453
453
  tpl = dedent(geom._aesthetics_doc).strip()
454
- aesthetics_doc = tpl.format(aesthetics_table=aesthetics_table)
454
+ aesthetics_doc = tpl.replace("{aesthetics_table}", aesthetics_table)
455
455
  aesthetics_doc = indent(aesthetics_doc, " " * 4)
456
456
 
457
457
  # common_parameters
plotnine/facets/strips.py CHANGED
@@ -67,56 +67,45 @@ class strip:
67
67
  if position == "top":
68
68
  # The x & y values are just starting locations
69
69
  # The final location is determined by the layout manager.
70
- y = 1
70
+ bg_y = 1
71
71
  ha = theme.getp(("strip_text_x", "ha"), "center")
72
- va = theme.getp(("strip_text_x", "va"), "bottom")
72
+ va = theme.getp(("strip_text_x", "va"), "center")
73
73
  rotation = theme.getp(("strip_text_x", "rotation"))
74
- box_width = 1
75
- box_height = 0 # Determined by the text size
76
- # TODO: Allow two unique paddings for either side.
77
- # Requires implementing an mpl.patches.boxstyle that recognises
78
- # two padding values.
79
- strip_text_margin = (
80
- theme.getp(("strip_text_x", "margin")).to("lines").b
81
- )
74
+ bg_height = 0 # Determined by the text size
75
+ margin = theme.getp(("strip_text_x", "margin")).to("lines")
82
76
  strip_align = theme.getp("strip_align_x")
83
77
 
84
78
  # x & width properties of the background slide and
85
79
  # shrink the strip horizontally.
86
- x = theme.getp(("strip_text_x", "x"), 0)
87
- box_width = theme.getp(("strip_background_x", "width"), 1)
80
+ bg_x = theme.getp(("strip_text_x", "x"), 0)
81
+ bg_width = theme.getp(("strip_background_x", "width"), 1)
88
82
 
89
83
  elif position == "right":
90
84
  # The x & y values are just starting locations
91
85
  # The final location is determined by the layout manager.
92
- x = 1
93
- ha = theme.getp(("strip_text_y", "ha"), "left")
86
+ bg_x = 1
87
+ ha = theme.getp(("strip_text_y", "ha"), "center")
94
88
  va = theme.getp(("strip_text_y", "va"), "center")
95
89
  rotation = theme.getp(("strip_text_y", "rotation"))
96
- box_width = 0 # Determine by the text height
97
- # TODO: Allow two unique paddings for either side.
98
- # Requires implementing an mpl.patches.boxstyle that recognises
99
- # two padding values.
100
- strip_text_margin = (
101
- theme.getp(("strip_text_y", "margin")).to("lines").r
102
- )
90
+ bg_width = 0 # Determine by the text height
91
+ margin = theme.getp(("strip_text_y", "margin")).to("lines")
103
92
  strip_align = theme.getp("strip_align_y")
104
93
 
105
94
  # y & height properties of the background slide and
106
95
  # shrink the strip vertically.
107
- y = theme.getp(("strip_text_y", "y"), 0)
108
- box_height = theme.getp(("strip_background_y", "height"), 1)
96
+ bg_y = theme.getp(("strip_text_y", "y"), 0)
97
+ bg_height = theme.getp(("strip_background_y", "height"), 1)
109
98
  else:
110
99
  raise ValueError(f"Unknown position for strip text: {position!r}")
111
100
 
112
101
  return strip_draw_info(
113
- x=x,
114
- y=y,
102
+ bg_x=bg_x,
103
+ bg_y=bg_y,
115
104
  ha=ha,
116
105
  va=va,
117
- box_width=box_width,
118
- box_height=box_height,
119
- strip_text_margin=strip_text_margin,
106
+ bg_width=bg_width,
107
+ bg_height=bg_height,
108
+ margin=margin,
120
109
  strip_align=strip_align,
121
110
  position=position,
122
111
  label=self.label_info.text(),
@@ -46,9 +46,7 @@ class _geom_logticks(geom_rug):
46
46
  }
47
47
  draw_legend = staticmethod(geom_path.draw_legend)
48
48
 
49
- def draw_layer(
50
- self, data: pd.DataFrame, layout: Layout, coord: coord, **params: Any
51
- ):
49
+ def draw_layer(self, data: pd.DataFrame, layout: Layout, coord: coord):
52
50
  """
53
51
  Draw ticks on every panel
54
52
  """
@@ -56,7 +54,7 @@ class _geom_logticks(geom_rug):
56
54
  ploc = pid - 1
57
55
  panel_params = layout.panel_params[ploc]
58
56
  ax = layout.axs[ploc]
59
- self.draw_panel(data, panel_params, coord, ax, **params)
57
+ self.draw_panel(data, panel_params, coord, ax)
60
58
 
61
59
  @staticmethod
62
60
  def _check_log_scale(
@@ -184,8 +182,8 @@ class _geom_logticks(geom_rug):
184
182
  panel_params: panel_view,
185
183
  coord: coord,
186
184
  ax: Axes,
187
- **params: Any,
188
185
  ):
186
+ params = self.params
189
187
  # Any passed data is ignored, the relevant data is created
190
188
  sides = params["sides"]
191
189
  lengths = params["lengths"]
@@ -203,9 +201,8 @@ class _geom_logticks(geom_rug):
203
201
  ):
204
202
  for position, length in zip(tick_positions, lengths):
205
203
  data = pd.DataFrame({axis: position, **_aesthetics})
206
- geom.draw_group(
207
- data, panel_params, coord, ax, length=length, **params
208
- )
204
+ params["length"] = length
205
+ geom.draw_group(data, panel_params, coord, ax, params)
209
206
 
210
207
  if isinstance(coord, coord_flip):
211
208
  tick_range_x = panel_params.y.range
@@ -285,4 +282,6 @@ class annotation_logticks(annotate):
285
282
  linetype=linetype,
286
283
  lengths=lengths,
287
284
  base=base,
285
+ inherit_aes=False,
286
+ show_legend=False,
288
287
  )
@@ -72,6 +72,8 @@ class annotation_stripes(annotate):
72
72
  fill_range=fill_range,
73
73
  extend=extend,
74
74
  direction=direction,
75
+ inherit_aes=False,
76
+ show_legend=False,
75
77
  **kwargs,
76
78
  )
77
79
 
@@ -94,9 +96,7 @@ class _geom_stripes(geom):
94
96
  }
95
97
  draw_legend = staticmethod(geom_polygon.draw_legend)
96
98
 
97
- def draw_layer(
98
- self, data: pd.DataFrame, layout: Layout, coord: coord, **params: Any
99
- ):
99
+ def draw_layer(self, data: pd.DataFrame, layout: Layout, coord: coord):
100
100
  """
101
101
  Draw stripes on every panel
102
102
  """
@@ -104,7 +104,7 @@ class _geom_stripes(geom):
104
104
  ploc = pid - 1
105
105
  panel_params = layout.panel_params[ploc]
106
106
  ax = layout.axs[ploc]
107
- self.draw_group(data, panel_params, coord, ax, **params)
107
+ self.draw_group(data, panel_params, coord, ax, self.params)
108
108
 
109
109
  @staticmethod
110
110
  def draw_group(
@@ -112,7 +112,7 @@ class _geom_stripes(geom):
112
112
  panel_params: panel_view,
113
113
  coord: coord,
114
114
  ax: Axes,
115
- **params: Any,
115
+ params: dict[str, Any],
116
116
  ):
117
117
  extend = params["extend"]
118
118
  fill_range = params["fill_range"]
@@ -193,4 +193,4 @@ class _geom_stripes(geom):
193
193
  }
194
194
  )
195
195
 
196
- return geom_rect.draw_group(data, panel_params, coord, ax, **params)
196
+ return geom_rect.draw_group(data, panel_params, coord, ax, params)
plotnine/geoms/geom.py CHANGED
@@ -171,6 +171,21 @@ class geom(ABC, metaclass=Register):
171
171
 
172
172
  return result
173
173
 
174
+ def setup_params(self, data: pd.DataFrame):
175
+ """
176
+ Override this method to verify and/or adjust parameters
177
+
178
+ Parameters
179
+ ----------
180
+ data :
181
+ Data
182
+
183
+ Returns
184
+ -------
185
+ out :
186
+ Parameters used by the geoms.
187
+ """
188
+
174
189
  def setup_data(self, data: pd.DataFrame) -> pd.DataFrame:
175
190
  """
176
191
  Modify the data before drawing takes place
@@ -261,9 +276,7 @@ class geom(ABC, metaclass=Register):
261
276
 
262
277
  return data
263
278
 
264
- def draw_layer(
265
- self, data: pd.DataFrame, layout: Layout, coord: coord, **params: Any
266
- ):
279
+ def draw_layer(self, data: pd.DataFrame, layout: Layout, coord: coord):
267
280
  """
268
281
  Draw layer across all panels
269
282
 
@@ -289,7 +302,7 @@ class geom(ABC, metaclass=Register):
289
302
  ploc = pdata["PANEL"].iloc[0] - 1
290
303
  panel_params = layout.panel_params[ploc]
291
304
  ax = layout.axs[ploc]
292
- self.draw_panel(pdata, panel_params, coord, ax, **params)
305
+ self.draw_panel(pdata, panel_params, coord, ax)
293
306
 
294
307
  def draw_panel(
295
308
  self,
@@ -297,7 +310,6 @@ class geom(ABC, metaclass=Register):
297
310
  panel_params: panel_view,
298
311
  coord: coord,
299
312
  ax: Axes,
300
- **params: Any,
301
313
  ):
302
314
  """
303
315
  Plot all groups
@@ -331,7 +343,7 @@ class geom(ABC, metaclass=Register):
331
343
  """
332
344
  for _, gdata in data.groupby("group"):
333
345
  gdata.reset_index(inplace=True, drop=True)
334
- self.draw_group(gdata, panel_params, coord, ax, **params)
346
+ self.draw_group(gdata, panel_params, coord, ax, self.params)
335
347
 
336
348
  @staticmethod
337
349
  def draw_group(
@@ -339,7 +351,7 @@ class geom(ABC, metaclass=Register):
339
351
  panel_params: panel_view,
340
352
  coord: coord,
341
353
  ax: Axes,
342
- **params: Any,
354
+ params: dict[str, Any],
343
355
  ):
344
356
  """
345
357
  Plot data belonging to a group.
@@ -376,7 +388,7 @@ class geom(ABC, metaclass=Register):
376
388
  panel_params: panel_view,
377
389
  coord: coord,
378
390
  ax: Axes,
379
- **params: Any,
391
+ params: dict[str, Any],
380
392
  ):
381
393
  """
382
394
  Plot data belonging to a unit.
@@ -102,7 +102,6 @@ class geom_abline(geom):
102
102
  panel_params: panel_view,
103
103
  coord: coord,
104
104
  ax: Axes,
105
- **params: Any,
106
105
  ):
107
106
  """
108
107
  Plot all groups
@@ -116,4 +115,6 @@ class geom_abline(geom):
116
115
 
117
116
  for _, gdata in data.groupby("group"):
118
117
  gdata.reset_index(inplace=True)
119
- geom_segment.draw_group(gdata, panel_params, coord, ax, **params)
118
+ geom_segment.draw_group(
119
+ gdata, panel_params, coord, ax, self.params
120
+ )
@@ -6,8 +6,6 @@ from ..doctools import document
6
6
  from .geom import geom
7
7
 
8
8
  if typing.TYPE_CHECKING:
9
- from typing import Any
10
-
11
9
  import pandas as pd
12
10
  from matplotlib.axes import Axes
13
11
 
@@ -39,7 +37,6 @@ class geom_blank(geom):
39
37
  panel_params: panel_view,
40
38
  coord: coord,
41
39
  ax: Axes,
42
- **params: Any,
43
40
  ):
44
41
  pass
45
42
 
@@ -183,7 +183,7 @@ class geom_boxplot(geom):
183
183
  panel_params: panel_view,
184
184
  coord: coord,
185
185
  ax: Axes,
186
- **params: Any,
186
+ params: dict[str, Any],
187
187
  ):
188
188
  def flat(*args: pd.Series[Any]) -> npt.NDArray[Any]:
189
189
  """Flatten list-likes"""
@@ -245,11 +245,11 @@ class geom_boxplot(geom):
245
245
  outliers["shape"] = outlier_value("shape")
246
246
  outliers["size"] = outlier_value("size")
247
247
  outliers["stroke"] = outlier_value("stroke")
248
- geom_point.draw_group(outliers, panel_params, coord, ax, **params)
248
+ geom_point.draw_group(outliers, panel_params, coord, ax, params)
249
249
 
250
250
  # plot
251
- geom_segment.draw_group(whiskers, panel_params, coord, ax, **params)
252
- geom_crossbar.draw_group(box, panel_params, coord, ax, **params)
251
+ geom_segment.draw_group(whiskers, panel_params, coord, ax, params)
252
+ geom_crossbar.draw_group(box, panel_params, coord, ax, params)
253
253
 
254
254
  @staticmethod
255
255
  def draw_legend(
@@ -78,7 +78,7 @@ class geom_crossbar(geom):
78
78
  panel_params: panel_view,
79
79
  coord: coord,
80
80
  ax: Axes,
81
- **params: Any,
81
+ params: dict[str, Any],
82
82
  ):
83
83
  y = data["y"]
84
84
  xmin = data["xmin"]
@@ -160,8 +160,8 @@ class geom_crossbar(geom):
160
160
  )
161
161
 
162
162
  copy_missing_columns(box, data)
163
- geom_polygon.draw_group(box, panel_params, coord, ax, **params)
164
- geom_segment.draw_group(middle, panel_params, coord, ax, **params)
163
+ geom_polygon.draw_group(box, panel_params, coord, ax, params)
164
+ geom_segment.draw_group(middle, panel_params, coord, ax, params)
165
165
 
166
166
  @staticmethod
167
167
  def draw_legend(
@@ -186,7 +186,7 @@ class geom_dotplot(geom):
186
186
  panel_params: panel_view,
187
187
  coord: coord,
188
188
  ax: Axes,
189
- **params: Any,
189
+ params: dict[str, Any],
190
190
  ):
191
191
  from matplotlib.collections import PatchCollection
192
192
  from matplotlib.patches import Ellipse