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.
Files changed (140) hide show
  1. plotnine/__init__.py +2 -0
  2. plotnine/_mpl/layout_manager/_engine.py +1 -1
  3. plotnine/_mpl/layout_manager/_layout_items.py +128 -83
  4. plotnine/_mpl/layout_manager/_layout_tree.py +761 -310
  5. plotnine/_mpl/layout_manager/_spaces.py +320 -103
  6. plotnine/_mpl/patches.py +70 -34
  7. plotnine/_mpl/text.py +144 -63
  8. plotnine/_mpl/utils.py +1 -1
  9. plotnine/_utils/__init__.py +50 -107
  10. plotnine/_utils/context.py +78 -2
  11. plotnine/_utils/ipython.py +35 -51
  12. plotnine/_utils/quarto.py +21 -0
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/composition/__init__.py +11 -0
  15. plotnine/composition/_beside.py +55 -0
  16. plotnine/composition/_compose.py +471 -0
  17. plotnine/composition/_plot_spacer.py +60 -0
  18. plotnine/composition/_stack.py +55 -0
  19. plotnine/coords/coord.py +3 -3
  20. plotnine/data/__init__.py +31 -0
  21. plotnine/data/anscombe-quartet.csv +45 -0
  22. plotnine/doctools.py +4 -4
  23. plotnine/facets/facet.py +4 -4
  24. plotnine/facets/strips.py +17 -28
  25. plotnine/geoms/annotate.py +13 -13
  26. plotnine/geoms/annotation_logticks.py +7 -8
  27. plotnine/geoms/annotation_stripes.py +6 -6
  28. plotnine/geoms/geom.py +60 -27
  29. plotnine/geoms/geom_abline.py +3 -2
  30. plotnine/geoms/geom_area.py +2 -2
  31. plotnine/geoms/geom_bar.py +11 -2
  32. plotnine/geoms/geom_bin_2d.py +6 -2
  33. plotnine/geoms/geom_blank.py +0 -3
  34. plotnine/geoms/geom_boxplot.py +8 -4
  35. plotnine/geoms/geom_col.py +8 -2
  36. plotnine/geoms/geom_count.py +6 -2
  37. plotnine/geoms/geom_crossbar.py +3 -3
  38. plotnine/geoms/geom_density_2d.py +6 -2
  39. plotnine/geoms/geom_dotplot.py +2 -2
  40. plotnine/geoms/geom_errorbar.py +2 -2
  41. plotnine/geoms/geom_errorbarh.py +2 -2
  42. plotnine/geoms/geom_histogram.py +1 -1
  43. plotnine/geoms/geom_hline.py +3 -2
  44. plotnine/geoms/geom_linerange.py +2 -2
  45. plotnine/geoms/geom_map.py +5 -5
  46. plotnine/geoms/geom_path.py +11 -12
  47. plotnine/geoms/geom_point.py +4 -5
  48. plotnine/geoms/geom_pointdensity.py +4 -0
  49. plotnine/geoms/geom_pointrange.py +3 -5
  50. plotnine/geoms/geom_polygon.py +2 -3
  51. plotnine/geoms/geom_qq.py +4 -0
  52. plotnine/geoms/geom_qq_line.py +4 -0
  53. plotnine/geoms/geom_quantile.py +4 -0
  54. plotnine/geoms/geom_raster.py +4 -5
  55. plotnine/geoms/geom_rect.py +3 -4
  56. plotnine/geoms/geom_ribbon.py +7 -7
  57. plotnine/geoms/geom_rug.py +1 -1
  58. plotnine/geoms/geom_segment.py +2 -2
  59. plotnine/geoms/geom_sina.py +3 -3
  60. plotnine/geoms/geom_smooth.py +7 -3
  61. plotnine/geoms/geom_step.py +2 -2
  62. plotnine/geoms/geom_text.py +2 -3
  63. plotnine/geoms/geom_violin.py +28 -8
  64. plotnine/geoms/geom_vline.py +3 -2
  65. plotnine/ggplot.py +64 -85
  66. plotnine/guides/guide.py +7 -10
  67. plotnine/guides/guide_colorbar.py +3 -3
  68. plotnine/guides/guide_legend.py +3 -3
  69. plotnine/guides/guides.py +6 -6
  70. plotnine/helpers.py +49 -0
  71. plotnine/iapi.py +28 -5
  72. plotnine/labels.py +3 -3
  73. plotnine/layer.py +36 -19
  74. plotnine/mapping/_atomic.py +178 -0
  75. plotnine/mapping/_env.py +13 -2
  76. plotnine/mapping/_eval_environment.py +85 -0
  77. plotnine/mapping/aes.py +91 -72
  78. plotnine/mapping/evaluation.py +7 -65
  79. plotnine/scales/__init__.py +2 -0
  80. plotnine/scales/limits.py +7 -7
  81. plotnine/scales/scale.py +3 -3
  82. plotnine/scales/scale_color.py +82 -18
  83. plotnine/scales/scale_continuous.py +6 -4
  84. plotnine/scales/scale_datetime.py +28 -14
  85. plotnine/scales/scale_discrete.py +1 -1
  86. plotnine/scales/scale_identity.py +21 -2
  87. plotnine/scales/scale_manual.py +8 -2
  88. plotnine/scales/scale_xy.py +2 -2
  89. plotnine/stats/binning.py +4 -1
  90. plotnine/stats/smoothers.py +23 -36
  91. plotnine/stats/stat.py +20 -32
  92. plotnine/stats/stat_bin.py +6 -5
  93. plotnine/stats/stat_bin_2d.py +11 -9
  94. plotnine/stats/stat_bindot.py +13 -16
  95. plotnine/stats/stat_boxplot.py +6 -6
  96. plotnine/stats/stat_count.py +6 -9
  97. plotnine/stats/stat_density.py +7 -10
  98. plotnine/stats/stat_density_2d.py +12 -8
  99. plotnine/stats/stat_ecdf.py +7 -6
  100. plotnine/stats/stat_ellipse.py +9 -6
  101. plotnine/stats/stat_function.py +10 -8
  102. plotnine/stats/stat_hull.py +6 -3
  103. plotnine/stats/stat_identity.py +5 -2
  104. plotnine/stats/stat_pointdensity.py +5 -7
  105. plotnine/stats/stat_qq.py +46 -20
  106. plotnine/stats/stat_qq_line.py +16 -11
  107. plotnine/stats/stat_quantile.py +15 -9
  108. plotnine/stats/stat_sina.py +45 -14
  109. plotnine/stats/stat_smooth.py +8 -10
  110. plotnine/stats/stat_sum.py +5 -2
  111. plotnine/stats/stat_summary.py +7 -10
  112. plotnine/stats/stat_summary_bin.py +11 -14
  113. plotnine/stats/stat_unique.py +5 -2
  114. plotnine/stats/stat_ydensity.py +8 -11
  115. plotnine/themes/elements/__init__.py +2 -1
  116. plotnine/themes/elements/element_line.py +17 -9
  117. plotnine/themes/elements/margin.py +64 -1
  118. plotnine/themes/theme.py +9 -1
  119. plotnine/themes/theme_538.py +0 -1
  120. plotnine/themes/theme_bw.py +0 -1
  121. plotnine/themes/theme_dark.py +0 -1
  122. plotnine/themes/theme_gray.py +6 -5
  123. plotnine/themes/theme_light.py +1 -1
  124. plotnine/themes/theme_matplotlib.py +5 -5
  125. plotnine/themes/theme_seaborn.py +7 -4
  126. plotnine/themes/theme_void.py +9 -8
  127. plotnine/themes/theme_xkcd.py +0 -1
  128. plotnine/themes/themeable.py +110 -32
  129. plotnine/typing.py +17 -6
  130. plotnine/watermark.py +3 -3
  131. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/METADATA +13 -6
  132. plotnine-0.15.1.dist-info/RECORD +221 -0
  133. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/WHEEL +1 -1
  134. plotnine/plot_composition/__init__.py +0 -10
  135. plotnine/plot_composition/_compose.py +0 -436
  136. plotnine/plot_composition/_spacer.py +0 -32
  137. plotnine-0.15.0.dev2.dist-info/RECORD +0 -214
  138. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  139. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/licenses/LICENSE +0 -0
  140. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import numbers
4
- import typing
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
  import numpy as np
7
7
  import pandas as pd
8
8
  import pandas.api.types as pdtypes
9
9
 
10
10
  from ..exceptions import PlotnineError
11
+ from ._eval_environment import factor, reorder
11
12
 
12
- if typing.TYPE_CHECKING:
13
+ if TYPE_CHECKING:
13
14
  from typing import Any
14
15
 
15
16
  from . import aes
@@ -18,6 +19,9 @@ if typing.TYPE_CHECKING:
18
19
 
19
20
  __all__ = ("after_stat", "after_scale", "stage")
20
21
 
22
+
23
+ EVAL_ENVIRONMENT = {"factor": factor, "reorder": reorder}
24
+
21
25
  _TPL_EVAL_FAIL = """\
22
26
  Could not evaluate the '{}' mapping: '{}' \
23
27
  (original error: {})"""
@@ -108,68 +112,6 @@ def after_scale(x):
108
112
  return stage(after_scale=x)
109
113
 
110
114
 
111
- def reorder(x, y, fun=np.median, ascending=True):
112
- """
113
- Reorder categorical by sorting along another variable
114
-
115
- It is the order of the categories that changes. Values in x
116
- are grouped by categories and summarised to determine the
117
- new order.
118
-
119
- Credit: Copied from plydata
120
-
121
- Parameters
122
- ----------
123
- x : list-like
124
- Values that will make up the categorical.
125
- y : list-like
126
- Values by which `c` will be ordered.
127
- fun : callable
128
- Summarising function to `x` for each category in `c`.
129
- Default is the *median*.
130
- ascending : bool
131
- If `True`, the `c` is ordered in ascending order of `x`.
132
-
133
- Examples
134
- --------
135
- >>> c = list('abbccc')
136
- >>> x = [11, 2, 2, 3, 33, 3]
137
- >>> cat_reorder(c, x)
138
- [a, b, b, c, c, c]
139
- Categories (3, object): [b, c, a]
140
- >>> cat_reorder(c, x, fun=max)
141
- [a, b, b, c, c, c]
142
- Categories (3, object): [b, a, c]
143
- >>> cat_reorder(c, x, fun=max, ascending=False)
144
- [a, b, b, c, c, c]
145
- Categories (3, object): [c, a, b]
146
- >>> c_ordered = pd.Categorical(c, ordered=True)
147
- >>> cat_reorder(c_ordered, x)
148
- [a, b, b, c, c, c]
149
- Categories (3, object): [b < c < a]
150
- >>> cat_reorder(c + ['d'], x)
151
- Traceback (most recent call last):
152
- ...
153
- ValueError: Lengths are not equal. len(c) is 7 and len(x) is 6.
154
- """
155
- if len(x) != len(y):
156
- raise ValueError(f"Lengths are not equal. {len(x)=}, {len(x)=}")
157
- summary = (
158
- pd.Series(y)
159
- .groupby(x, observed=True)
160
- .apply(fun)
161
- .sort_values(ascending=ascending)
162
- )
163
- cats = summary.index.to_list()
164
- return pd.Categorical(x, categories=cats)
165
-
166
-
167
- # These are function that can be called by the user inside the aes()
168
- # mapping. This is meant to make the variable transformations as easy
169
- # as they are in ggplot2
170
- AES_INNER_NAMESPACE = {"factor": pd.Categorical, "reorder": reorder}
171
-
172
-
173
115
  def evaluate(
174
116
  aesthetics: aes | dict[str, Any], data: pd.DataFrame, env: Environment
175
117
  ) -> pd.DataFrame:
@@ -207,7 +149,7 @@ def evaluate(
207
149
  3 16
208
150
  4 25
209
151
  """
210
- env = env.with_outer_namespace(AES_INNER_NAMESPACE)
152
+ env = env.with_outer_namespace(EVAL_ENVIRONMENT)
211
153
 
212
154
  # Store evaluation results in a dict column in a dict
213
155
  evaled = {}
@@ -74,6 +74,7 @@ from .scale_identity import (
74
74
  scale_linetype_identity,
75
75
  scale_shape_identity,
76
76
  scale_size_identity,
77
+ scale_stroke_identity,
77
78
  )
78
79
 
79
80
  # linetype
@@ -217,6 +218,7 @@ __all__ = (
217
218
  "scale_linetype_identity",
218
219
  "scale_shape_identity",
219
220
  "scale_size_identity",
221
+ "scale_stroke_identity",
220
222
  # manual
221
223
  "scale_color_manual",
222
224
  "scale_colour_manual",
plotnine/scales/limits.py CHANGED
@@ -78,10 +78,10 @@ class _lim:
78
78
  self.aesthetic, series, limits=self.limits, trans=self.trans
79
79
  )
80
80
 
81
- def __radd__(self, plot):
82
- scale = self.get_scale(plot)
83
- plot.scales.append(scale)
84
- return plot
81
+ def __radd__(self, other):
82
+ scale = self.get_scale(other)
83
+ other.scales.append(scale)
84
+ return other
85
85
 
86
86
 
87
87
  class xlim(_lim):
@@ -194,7 +194,7 @@ class lims:
194
194
  def __init__(self, **kwargs):
195
195
  self._kwargs = kwargs
196
196
 
197
- def __radd__(self, plot):
197
+ def __radd__(self, other):
198
198
  """
199
199
  Add limits to ggplot object
200
200
  """
@@ -206,9 +206,9 @@ class lims:
206
206
  msg = "Cannot change limits for '{}'"
207
207
  raise PlotnineError(msg) from e
208
208
 
209
- plot += klass(value)
209
+ other += klass(value)
210
210
 
211
- return plot
211
+ return other
212
212
 
213
213
 
214
214
  def expand_limits(**kwargs):
plotnine/scales/scale.py CHANGED
@@ -148,12 +148,12 @@ class scale(
148
148
  self.aesthetics if self.aesthetics else self._aesthetics
149
149
  )
150
150
 
151
- def __radd__(self, plot):
151
+ def __radd__(self, other):
152
152
  """
153
153
  Add this scale to ggplot object
154
154
  """
155
- plot.scales.append(copy(self))
156
- return plot
155
+ other.scales.append(copy(self))
156
+ return other
157
157
 
158
158
  def map(self, x, limits=None):
159
159
  """
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import KW_ONLY, InitVar, dataclass
3
+ from dataclasses import KW_ONLY, InitVar, dataclass, field
4
4
  from typing import Literal, Sequence
5
5
  from warnings import warn
6
6
 
@@ -48,36 +48,72 @@ class _scale_color_continuous(
48
48
  class scale_color_hue(_scale_color_discrete):
49
49
  """
50
50
  Qualitative color scale with evenly spaced hues
51
+
52
+ See Also
53
+ --------
54
+ mizani.palettes.hue_pal : The palette class that generates colours
55
+ in HCL space.
51
56
  """
52
57
 
53
- h: InitVar[float] = 0.01
58
+ h: InitVar[float | tuple[float, float]] = 15
54
59
  """
55
- Hue. Must be in the range [0, 1]
60
+ Hue. If a float, it is the first hue value, in the range `[0, 360]`.
61
+ The range of the palette will be `[first, first + 360)`.
62
+
63
+ If a tuple, it is the range `[first, last)` of the hues.
56
64
  """
57
65
 
58
- l: InitVar[float] = 0.6
66
+ c: InitVar[float] = 100
59
67
  """
60
- Lightness. Must be in the range [0, 1]
68
+ Chroma. Must be in the range `[0, 100]`
61
69
  """
62
70
 
63
- s: InitVar[float] = 0.65
71
+ l: InitVar[float] = 65
64
72
  """
65
- Saturation. Must be in the range [0, 1]
73
+ Lightness. Must be in the range [0, 100]
66
74
  """
67
75
 
68
- color_space: InitVar[Literal["hls", "hsluv"]] = "hls"
76
+ direction: InitVar[Literal[1, -1]] = 1
69
77
  """
70
- Color space to use. Should be one of
71
- [hls](https://en.wikipedia.org/wiki/HSL_and_HSV)
72
- or [hsluv](https://www.hsluv.org/).
73
- https://www.hsluv.org/
78
+ The order of colours in the scale. If -1 the order
79
+ of colours is reversed. The default is 1.
74
80
  """
75
81
 
76
- def __post_init__(self, h, l, s, color_space):
82
+ _: KW_ONLY
83
+
84
+ s: None = field(default=None, repr=False)
85
+ """
86
+ Not being used and will be removed in a future version
87
+ """
88
+ color_space: None = field(default=None, repr=False)
89
+ """
90
+ Not being used and will be removed in a future version
91
+ """
92
+
93
+ def __post_init__(self, h, c, l, direction):
77
94
  from mizani.palettes import hue_pal
78
95
 
96
+ if (s := self.s) is not None:
97
+ warn(
98
+ f"You used {s=} for the saturation which has been ignored. "
99
+ f"{self.__class__.__name__} now works in HCL colorspace. "
100
+ f"Using `s` in future versions will throw an exception.",
101
+ FutureWarning,
102
+ )
103
+ del self.s
104
+
105
+ if (color_space := self.color_space) is not None:
106
+ warn(
107
+ f"You used {color_space=} to select a color_space and it "
108
+ f"has been ignored. {self.__class__.__name__} now only works "
109
+ f"in HCL colorspace. Using `color_space` in future versions "
110
+ "will throw an exception.",
111
+ FutureWarning,
112
+ )
113
+ del self.color_space
114
+
79
115
  super().__post_init__()
80
- self.palette = hue_pal(h, l, s, color_space=color_space)
116
+ self.palette = hue_pal(h, c, l, direction)
81
117
 
82
118
 
83
119
  @dataclass
@@ -95,6 +131,11 @@ class scale_color_brewer(_scale_color_discrete):
95
131
  Sequential, diverging and qualitative discrete color scales
96
132
 
97
133
  See `colorbrewer.org <http://colorbrewer2.org/>`_
134
+
135
+ See Also
136
+ --------
137
+ mizani.palette.brewer_pal : The palette class that generates colours
138
+ that generates the brewer colors.
98
139
  """
99
140
 
100
141
  type: InitVar[
@@ -146,6 +187,11 @@ class scale_fill_brewer(scale_color_brewer):
146
187
  class scale_color_grey(_scale_color_discrete):
147
188
  """
148
189
  Sequential grey color scale.
190
+
191
+ See Also
192
+ --------
193
+ mizani.palettes.grey_pal : The palette class that generates colours
194
+ gray scale color.
149
195
  """
150
196
 
151
197
  start: InitVar[float] = 0.2
@@ -188,6 +234,8 @@ class scale_color_gradient(_scale_color_continuous):
188
234
  --------
189
235
  plotnine.scale_color_gradient2
190
236
  plotnine.scale_color_gradientn
237
+ mizani.palettes.gradient_n_pal : The palette class that generates
238
+ the colour gradient.
191
239
  """
192
240
 
193
241
  low: InitVar[str] = "#132B43"
@@ -220,6 +268,11 @@ class scale_fill_gradient(scale_color_gradient):
220
268
  class scale_color_desaturate(_scale_color_continuous):
221
269
  """
222
270
  Create a desaturated color gradient
271
+
272
+ See Also
273
+ --------
274
+ mizani.palettes.desaturate_pal : The palette class that generates
275
+ the desaturated colours.
223
276
  """
224
277
 
225
278
  color: InitVar[str] = "red"
@@ -263,6 +316,8 @@ class scale_color_gradient2(_scale_color_continuous):
263
316
  --------
264
317
  plotnine.scale_color_gradient
265
318
  plotnine.scale_color_gradientn
319
+ mizani.palettes.gradient_n_pal : The palette class that generates
320
+ the colour gradient.
266
321
  """
267
322
 
268
323
  low: InitVar[str] = "#832424"
@@ -316,9 +371,11 @@ class scale_color_gradientn(_scale_color_continuous):
316
371
  --------
317
372
  plotnine.scale_color_gradient
318
373
  plotnine.scale_color_gradientn
374
+ mizani.palettes.gradient_n_pal : The palette class that generates
375
+ the colour gradient.
319
376
  """
320
377
 
321
- colors: InitVar[Sequence[str]] = "#832424"
378
+ colors: InitVar[Sequence[str]]
322
379
  """
323
380
  List of colors
324
381
  """
@@ -332,8 +389,8 @@ class scale_color_gradientn(_scale_color_continuous):
332
389
  def __post_init__(self, colors, values):
333
390
  from mizani.palettes import gradient_n_pal
334
391
 
335
- self.palette = gradient_n_pal(colors, values)
336
392
  super().__post_init__()
393
+ self.palette = gradient_n_pal(colors, values)
337
394
 
338
395
 
339
396
  @dataclass
@@ -430,6 +487,8 @@ class scale_color_cmap(_scale_color_continuous):
430
487
  --------
431
488
  [](`matplotlib.cm`)
432
489
  [](`matplotlib.colors`)
490
+ mizani.palettes.cmap_pal : The palette class that generates
491
+ the colour gradients of this scale.
433
492
  """
434
493
 
435
494
  cmap_name: InitVar[str] = "viridis"
@@ -457,7 +516,7 @@ class scale_fill_cmap(scale_color_cmap):
457
516
 
458
517
 
459
518
  @dataclass
460
- class scale_color_cmap_d(scale_discrete):
519
+ class scale_color_cmap_d(_scale_color_discrete):
461
520
  """
462
521
  A discrete color scales using Matplotlib colormaps
463
522
 
@@ -465,6 +524,8 @@ class scale_color_cmap_d(scale_discrete):
465
524
  --------
466
525
  [](`matplotlib.cm`)
467
526
  [](`matplotlib.colors`)
527
+ mizani.palettes.cmap_pal : The palette class that generates
528
+ the colours of this scale.
468
529
  """
469
530
 
470
531
  cmap_name: InitVar[str] = "viridis"
@@ -474,7 +535,6 @@ class scale_color_cmap_d(scale_discrete):
474
535
  `matplotlib.cm.cmap_d.keys()` or see the
475
536
  `documentation <http://matplotlib.org/users/colormaps.html>`_.
476
537
  """
477
- _aesthetics = ["color"]
478
538
 
479
539
  def __post_init__(self, cmap_name):
480
540
  from mizani.palettes import cmap_d_pal
@@ -496,6 +556,10 @@ class scale_fill_cmap_d(scale_color_cmap_d):
496
556
  class scale_color_datetime(scale_datetime, scale_color_cmap): # pyright: ignore[reportIncompatibleVariableOverride]
497
557
  """
498
558
  Datetime color scale
559
+
560
+ See Also
561
+ --------
562
+ plotnine.scale_color_cmap : The parent class.
499
563
  """
500
564
 
501
565
  _: KW_ONLY
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from contextlib import suppress
4
4
  from dataclasses import dataclass
5
- from typing import TYPE_CHECKING, Sequence
5
+ from typing import TYPE_CHECKING, Sequence, cast
6
6
  from warnings import warn
7
7
 
8
8
  import numpy as np
@@ -387,14 +387,15 @@ class scale_continuous(
387
387
  limits = self.final_limits
388
388
 
389
389
  x = self.oob(self.rescaler(x, _from=limits))
390
+ na_value = cast("float", self.na_value)
390
391
 
391
392
  uniq = np.unique(x)
392
393
  pal = np.asarray(self.palette(uniq))
393
394
  scaled = pal[match(x, uniq)]
394
395
  if scaled.dtype.kind == "U":
395
- scaled = [self.na_value if x == "nan" else x for x in scaled]
396
+ scaled = [na_value if x == "nan" else x for x in scaled]
396
397
  else:
397
- scaled[pd.isna(scaled)] = self.na_value
398
+ scaled[pd.isna(scaled)] = na_value
398
399
  return scaled
399
400
 
400
401
  def get_breaks(
@@ -520,11 +521,12 @@ class scale_continuous(
520
521
  # When user sets breaks and labels of equal size,
521
522
  # but the limits exclude some of the breaks.
522
523
  # We remove the corresponding labels
523
- from collections.abc import Sized
524
+ from collections.abc import Iterable, Sized
524
525
 
525
526
  labels = self.labels
526
527
  if (
527
528
  len(labels) != len(breaks)
529
+ and isinstance(self.breaks, Iterable)
528
530
  and isinstance(self.breaks, Sized)
529
531
  and len(labels) == len(self.breaks)
530
532
  ):
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import KW_ONLY, InitVar, dataclass
4
4
  from typing import TYPE_CHECKING
5
+ from warnings import warn
5
6
 
6
7
  from ._runtime_typing import TransUser # noqa: TCH001
7
8
  from .scale_continuous import scale_continuous
@@ -20,24 +21,21 @@ class scale_datetime(scale_continuous):
20
21
  """
21
22
  A string giving the distance between major breaks.
22
23
  For example `'2 weeks'`, `'5 years'`. If specified,
23
- `date_breaks` takes precedence over
24
- `breaks`.
24
+ `date_breaks` takes precedence over `breaks`.
25
25
  """
26
26
 
27
27
  date_labels: InitVar[str | None] = None
28
28
  """
29
29
  Format string for the labels.
30
30
  See [strftime](:ref:`strftime-strptime-behavior`).
31
- If specified, `date_labels` takes precedence over
32
- `labels`.
31
+ If specified, `date_labels` takes precedence over `labels`.
33
32
  """
34
33
 
35
34
  date_minor_breaks: InitVar[str | None] = None
36
35
  """
37
36
  A string giving the distance between minor breaks.
38
37
  For example `'2 weeks'`, `'5 years'`. If specified,
39
- `date_minor_breaks` takes precedence over
40
- `minor_breaks`.
38
+ `date_minor_breaks` takes precedence over `minor_breaks`.
41
39
  """
42
40
 
43
41
  _: KW_ONLY
@@ -80,22 +78,38 @@ class scale_datetime(scale_continuous):
80
78
  date_labels: str | None,
81
79
  date_minor_breaks: str | None,
82
80
  ):
83
- from mizani.breaks import breaks_date as breaks_func
84
- from mizani.labels import label_date as labels_func
81
+ from mizani.breaks import breaks_date_width
82
+ from mizani.labels import label_date
85
83
 
86
84
  if date_breaks is not None:
87
- self.breaks = breaks_func(date_breaks) # pyright: ignore
85
+ self.breaks = breaks_date_width(date_breaks) # pyright: ignore[reportAttributeAccessIssue]
88
86
  elif isinstance(self.breaks, str):
89
- self.breaks = breaks_func(width=self.breaks) # pyright: ignore
87
+ warn(
88
+ "Passing a string to `breaks` will not work in "
89
+ f"future versions. Use `date_breaks={self.breaks!r}`",
90
+ FutureWarning,
91
+ )
92
+ self.breaks = breaks_date_width(width=self.breaks) # pyright: ignore[reportAttributeAccessIssue]
90
93
 
91
94
  if date_labels is not None:
92
- self.labels = labels_func(date_labels) # pyright: ignore
95
+ self.labels = label_date(fmt=date_labels) # pyright: ignore[reportAttributeAccessIssue]
93
96
  elif isinstance(self.labels, str):
94
- self.labels = labels_func(width=self.labels) # pyright: ignore
97
+ warn(
98
+ "Passing a string to `labels` will not work in "
99
+ f"future versions. Use `date_labels={self.labels!r}`",
100
+ FutureWarning,
101
+ )
102
+ self.labels = label_date(fmt=self.labels) # pyright: ignore[reportAttributeAccessIssue]
95
103
 
96
104
  if date_minor_breaks is not None:
97
- self.minor_breaks = breaks_func(date_minor_breaks) # pyright: ignore
105
+ self.minor_breaks = breaks_date_width(date_minor_breaks) # pyright: ignore[reportAttributeAccessIssue]
98
106
  elif isinstance(self.minor_breaks, str):
99
- self.minor_breaks = breaks_func(width=self.minor_breaks) # pyright: ignore
107
+ warn(
108
+ "Passing a string to `minor_breaks` will not work in "
109
+ "future versions. "
110
+ f"Use `date_minor_breaks={self.minor_breaks!r}`",
111
+ FutureWarning,
112
+ )
113
+ self.minor_breaks = breaks_date_width(width=self.minor_breaks) # pyright: ignore[reportAttributeAccessIssue]
100
114
 
101
115
  scale_continuous.__post_init__(self)
@@ -156,7 +156,7 @@ class scale_discrete(
156
156
  range = self.dimension(limits=limits)
157
157
 
158
158
  breaks_d = self.get_breaks(limits)
159
- breaks = self.map(pd.Categorical(breaks_d))
159
+ breaks = self.map(pd.Categorical(breaks_d)) # pyright: ignore[reportArgumentType]
160
160
  minor_breaks = []
161
161
  labels = self.get_labels(breaks_d)
162
162
 
@@ -43,6 +43,8 @@ class scale_color_identity(MapTrainMixin, scale_discrete):
43
43
  """
44
44
 
45
45
  _aesthetics = ["color"]
46
+ _: KW_ONLY
47
+ guide: Literal["legend"] | None = None
46
48
 
47
49
 
48
50
  @dataclass
@@ -52,6 +54,8 @@ class scale_fill_identity(scale_color_identity):
52
54
  """
53
55
 
54
56
  _aesthetics = ["fill"]
57
+ _: KW_ONLY
58
+ guide: Literal["legend"] | None = None
55
59
 
56
60
 
57
61
  @dataclass
@@ -61,6 +65,8 @@ class scale_shape_identity(MapTrainMixin, scale_discrete):
61
65
  """
62
66
 
63
67
  _aesthetics = ["shape"]
68
+ _: KW_ONLY
69
+ guide: Literal["legend"] | None = None
64
70
 
65
71
 
66
72
  @dataclass
@@ -70,6 +76,8 @@ class scale_linetype_identity(MapTrainMixin, scale_discrete):
70
76
  """
71
77
 
72
78
  _aesthetics = ["linetype"]
79
+ _: KW_ONLY
80
+ guide: Literal["legend"] | None = None
73
81
 
74
82
 
75
83
  @dataclass
@@ -82,7 +90,7 @@ class scale_alpha_identity(
82
90
 
83
91
  _aesthetics = ["alpha"]
84
92
  _: KW_ONLY
85
- guide: Literal["legend"] | None = "legend"
93
+ guide: Literal["legend"] | None = None
86
94
 
87
95
 
88
96
  @dataclass
@@ -95,7 +103,18 @@ class scale_size_identity(
95
103
 
96
104
  _aesthetics = ["size"]
97
105
  _: KW_ONLY
98
- guide: Literal["legend"] | None = "legend"
106
+ guide: Literal["legend"] | None = None
107
+
108
+
109
+ @dataclass
110
+ class scale_stroke_identity(MapTrainMixin, scale_discrete):
111
+ """
112
+ No stroke scaling
113
+ """
114
+
115
+ _aesthetics = ["stroke"]
116
+ _: KW_ONLY
117
+ guide: Literal["legend"] | None = None
99
118
 
100
119
 
101
120
  # American to British spelling
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Mapping
3
4
  from dataclasses import KW_ONLY, InitVar, dataclass
4
5
  from typing import Any, Sequence
5
6
  from warnings import warn
@@ -21,11 +22,16 @@ class _scale_manual(scale_discrete):
21
22
  """
22
23
 
23
24
  def __post_init__(self, values):
24
- from collections.abc import Sized
25
+ from collections.abc import Iterable, Sized
25
26
 
26
27
  super().__post_init__()
27
28
 
28
- if isinstance(self.breaks, Sized) and len(self.breaks) == len(values):
29
+ if (
30
+ isinstance(self.breaks, Iterable)
31
+ and isinstance(self.breaks, Sized)
32
+ and len(self.breaks) == len(values)
33
+ and not isinstance(values, Mapping)
34
+ ):
29
35
  values = dict(zip(self.breaks, values))
30
36
 
31
37
  def palette(n):
@@ -213,7 +213,7 @@ class scale_x_discrete(scale_position_discrete):
213
213
  Discrete x position
214
214
  """
215
215
 
216
- _aesthetics = ["x", "xmin", "xmax", "xend"]
216
+ _aesthetics = ["x", "xmin", "xmax", "xend", "xintercept"]
217
217
 
218
218
 
219
219
  @dataclass(kw_only=True)
@@ -222,7 +222,7 @@ class scale_y_discrete(scale_position_discrete):
222
222
  Discrete y position
223
223
  """
224
224
 
225
- _aesthetics = ["y", "ymin", "ymax", "yend"]
225
+ _aesthetics = ["y", "ymin", "ymax", "yend", "yintercept"]
226
226
 
227
227
 
228
228
  # Not part of the user API
plotnine/stats/binning.py CHANGED
@@ -165,7 +165,10 @@ def assign_bins(
165
165
  if weight is None:
166
166
  weight = np.ones(len(x))
167
167
  else:
168
- weight = np.asarray(weight)
168
+ # If weight is a dtype that isn't writeable
169
+ # and does not own it's memory. Using a list
170
+ # as an intermediate easily solves this.
171
+ weight = np.array(list(weight))
169
172
  weight[np.isnan(weight)] = 0
170
173
 
171
174
  bin_idx = pd.cut(