plotnine 0.15.0a4__py3-none-any.whl → 0.15.0a6__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.
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
  from matplotlib.figure import Figure
14
14
 
15
15
  from plotnine import ggplot
16
- from plotnine.plot_composition import Compose
16
+ from plotnine.composition import Arrange
17
17
 
18
18
 
19
19
  class PlotnineLayoutEngine(LayoutEngine):
@@ -54,7 +54,7 @@ class PlotnineCompositionLayoutEngine(LayoutEngine):
54
54
  _adjust_compatible = True
55
55
  _colorbar_gridspec = False
56
56
 
57
- def __init__(self, composition: Compose):
57
+ def __init__(self, composition: Arrange):
58
58
  self.composition = composition
59
59
 
60
60
  def execute(self, fig: Figure):
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, cast
7
7
 
8
8
  import numpy as np
9
9
 
10
- from plotnine.plot_composition import OR
10
+ from plotnine.composition import Beside
11
11
 
12
12
  from ._spaces import LayoutSpaces
13
13
 
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
16
16
 
17
17
  from plotnine import ggplot
18
18
  from plotnine._mpl.gridspec import p9GridSpec
19
- from plotnine.plot_composition import Compose
19
+ from plotnine.composition import Arrange
20
20
 
21
21
 
22
22
  @dataclass
@@ -82,7 +82,7 @@ class LayoutTree:
82
82
 
83
83
  @staticmethod
84
84
  def create(
85
- cmp: Compose,
85
+ cmp: Arrange,
86
86
  lookup_spaces: dict[ggplot, LayoutSpaces],
87
87
  ) -> LayoutTree:
88
88
  """
@@ -109,7 +109,7 @@ class LayoutTree:
109
109
  else:
110
110
  nodes.append(LayoutTree.create(item, lookup_spaces))
111
111
 
112
- if isinstance(cmp, OR):
112
+ if isinstance(cmp, Beside):
113
113
  return ColumnsTree(cmp.gridspec, nodes)
114
114
  else:
115
115
  return RowsTree(cmp.gridspec, nodes)
@@ -1194,3 +1194,16 @@ def va_as_float(va: VerticalJustification | float) -> float:
1194
1194
  "center_baseline": 0.5,
1195
1195
  }
1196
1196
  return lookup[va] if isinstance(va, str) else va
1197
+
1198
+
1199
+ def has_alpha_channel(c: str | tuple) -> bool:
1200
+ """
1201
+ Return True if c a color with an alpha value
1202
+
1203
+ Either a 9 character hex string e.g. #AABBCC88 or
1204
+ an RGBA tuple e.g. (.6, .7, .8, .5)
1205
+ """
1206
+ if isinstance(c, str):
1207
+ return c.startswith("#") and len(c) == 9
1208
+ else:
1209
+ return color_utils.is_color_tuple(c) and len(c) == 4
@@ -0,0 +1,11 @@
1
+ from ._arrange import Arrange
2
+ from ._beside import Beside
3
+ from ._plot_spacer import plot_spacer
4
+ from ._stack import Stack
5
+
6
+ __all__ = (
7
+ "Arrange",
8
+ "Stack",
9
+ "Beside",
10
+ "plot_spacer",
11
+ )
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import abc
4
4
  from copy import deepcopy
5
- from dataclasses import dataclass
5
+ from dataclasses import dataclass, field
6
6
  from io import BytesIO
7
7
  from typing import TYPE_CHECKING
8
8
 
@@ -24,9 +24,15 @@ if TYPE_CHECKING:
24
24
  from plotnine.ggplot import PlotAddable, ggplot
25
25
 
26
26
 
27
- class Compose:
27
+ @dataclass
28
+ class Arrange:
28
29
  """
29
- Arrange two or more plots
30
+ Base class for those that create plot compositions
31
+
32
+ As a user, you will never directly work with this class, except
33
+ the operators [`|`](`plotnine.composition.Beside`) and
34
+ [`/`](`plotnine.composition.Stack`) that are powered by subclasses
35
+ of this class.
30
36
 
31
37
  Parameters
32
38
  ----------
@@ -34,27 +40,26 @@ class Compose:
34
40
  The objects to be put together (composed).
35
41
  """
36
42
 
37
- def __init__(self, operands: list[ggplot | Compose]):
38
- self.operands = operands
43
+ operands: list[ggplot | Arrange]
39
44
 
40
- # These are created in the _create_figure method
41
- self.figure: Figure
42
- self.plotspecs: list[plotspec]
43
- self.gridspec: p9GridSpec
45
+ # These are created in the _create_figure method
46
+ figure: Figure = field(init=False, repr=False)
47
+ plotspecs: list[plotspec] = field(init=False, repr=False)
48
+ gridspec: p9GridSpec = field(init=False, repr=False)
44
49
 
45
50
  @abc.abstractmethod
46
- def __or__(self, rhs: ggplot | Compose) -> Compose:
51
+ def __or__(self, rhs: ggplot | Arrange) -> Arrange:
47
52
  """
48
53
  Add rhs as a column
49
54
  """
50
55
 
51
56
  @abc.abstractmethod
52
- def __truediv__(self, rhs: ggplot | Compose) -> Compose:
57
+ def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
53
58
  """
54
59
  Add rhs as a row
55
60
  """
56
61
 
57
- def __add__(self, rhs: ggplot | Compose | PlotAddable) -> Compose:
62
+ def __add__(self, rhs: ggplot | Arrange | PlotAddable) -> Arrange:
58
63
  """
59
64
  Add rhs to the composition
60
65
 
@@ -65,13 +70,13 @@ class Compose:
65
70
  """
66
71
  from plotnine import ggplot
67
72
 
68
- if not isinstance(rhs, (ggplot, Compose)):
73
+ if not isinstance(rhs, (ggplot, Arrange)):
69
74
  cmp = deepcopy(self)
70
75
  cmp.last_plot = cmp.last_plot + rhs
71
76
  return cmp
72
77
  return self.__class__([*self, rhs])
73
78
 
74
- def __sub__(self, rhs: ggplot | Compose) -> Compose:
79
+ def __sub__(self, rhs: ggplot | Arrange) -> Arrange:
75
80
  """
76
81
  Add the rhs besides the composition
77
82
 
@@ -82,7 +87,7 @@ class Compose:
82
87
  """
83
88
  return self.__class__([self, rhs])
84
89
 
85
- def __and__(self, rhs: PlotAddable) -> Compose:
90
+ def __and__(self, rhs: PlotAddable) -> Arrange:
86
91
  """
87
92
  Add rhs to all plots in the composition
88
93
 
@@ -93,9 +98,9 @@ class Compose:
93
98
  """
94
99
  self = deepcopy(self)
95
100
 
96
- def add_other(op: Compose):
101
+ def add_other(op: Arrange):
97
102
  for item in op:
98
- if isinstance(item, Compose):
103
+ if isinstance(item, Arrange):
99
104
  add_other(item)
100
105
  else:
101
106
  item += rhs
@@ -103,7 +108,7 @@ class Compose:
103
108
  add_other(self)
104
109
  return self
105
110
 
106
- def __mul__(self, rhs: PlotAddable) -> Compose:
111
+ def __mul__(self, rhs: PlotAddable) -> Arrange:
107
112
  """
108
113
  Add rhs to the outermost nesting level of the composition
109
114
 
@@ -127,7 +132,7 @@ class Compose:
127
132
  """
128
133
  return len(self.operands)
129
134
 
130
- def __iter__(self) -> Iterator[ggplot | Compose]:
135
+ def __iter__(self) -> Iterator[ggplot | Arrange]:
131
136
  """
132
137
  Return an iterable of all the operands
133
138
  """
@@ -227,7 +232,7 @@ class Compose:
227
232
  from plotnine._mpl.gridspec import p9GridSpec
228
233
 
229
234
  def _make_plotspecs(
230
- cmp: Compose, parent_gridspec: p9GridSpec | None
235
+ cmp: Arrange, parent_gridspec: p9GridSpec | None
231
236
  ) -> Generator[plotspec]:
232
237
  """
233
238
  Return the plot specification for each subplot in the composition
@@ -289,7 +294,7 @@ class Compose:
289
294
 
290
295
  def draw(self, *, show: bool = False) -> Figure:
291
296
  """
292
- Render the composed plots
297
+ Render the arranged plots
293
298
 
294
299
  Parameters
295
300
  ----------
@@ -315,9 +320,15 @@ class Compose:
315
320
  )
316
321
  return figure
317
322
 
318
- def save(self, filename: str | Path | BytesIO, format: str | None = None):
323
+ def save(
324
+ self,
325
+ filename: str | Path | BytesIO,
326
+ format: str | None = None,
327
+ dpi: int | None = None,
328
+ **kwargs,
329
+ ):
319
330
  """
320
- Save a Compose object as an image file
331
+ Save a composition as an image file
321
332
 
322
333
  Parameters
323
334
  ----------
@@ -326,14 +337,25 @@ class Compose:
326
337
  format :
327
338
  Image format to use, automatically extract from
328
339
  file name extension.
329
- """
330
- figure = self.draw()
340
+ dpi :
341
+ DPI to use for raster graphics. If None, defaults to using
342
+ the `dpi` of theme to the first plot.
343
+ kwargs :
344
+ These are ignored. Here to "softly" match the API of
345
+ `ggplot.save()`.
346
+ """
347
+ from plotnine import theme
348
+
349
+ # To set the dpi, we only need to change the dpi of
350
+ # the last plot and theme gets added to the last plot
351
+ plot = (self + theme(dpi=dpi)) if dpi else self
352
+ figure = plot.draw()
331
353
  figure.savefig(filename, format=format)
332
354
 
333
355
 
334
356
  @dataclass
335
357
  class plot_composition_context:
336
- cmp: Compose
358
+ cmp: Arrange
337
359
  show: bool
338
360
 
339
361
  def __post_init__(self):
@@ -368,95 +390,3 @@ class plot_composition_context:
368
390
  plt.close(self.cmp.figure)
369
391
 
370
392
  self._rc_context.__exit__(exc_type, exc_value, exc_traceback)
371
-
372
-
373
- class OR(Compose):
374
- """
375
- Compose by adding a column
376
- """
377
-
378
- @property
379
- def nrow(self) -> int:
380
- return 1
381
-
382
- @property
383
- def ncol(self) -> int:
384
- return len(self)
385
-
386
- def __or__(self, rhs: ggplot | Compose) -> Compose:
387
- """
388
- Add rhs as a column
389
- """
390
- # This is adjacent or i.e. (OR | rhs) so we collapse the
391
- # operands into a single operation
392
- return OR([*self, rhs])
393
-
394
- def __truediv__(self, rhs: ggplot | Compose) -> Compose:
395
- """
396
- Add rhs as a row
397
- """
398
- return DIV([self, rhs])
399
-
400
-
401
- class DIV(Compose):
402
- """
403
- Compose by adding a row
404
- """
405
-
406
- @property
407
- def nrow(self) -> int:
408
- return len(self)
409
-
410
- @property
411
- def ncol(self) -> int:
412
- return 1
413
-
414
- def __truediv__(self, rhs: ggplot | Compose) -> Compose:
415
- """
416
- Add rhs as a row
417
- """
418
- # This is an adjacent div i.e. (DIV | rhs) so we collapse the
419
- # operands into a single operation
420
- return DIV([*self, rhs])
421
-
422
- def __or__(self, rhs: ggplot | Compose) -> Compose:
423
- """
424
- Add rhs as a column
425
- """
426
- return OR([self, rhs])
427
-
428
-
429
- class ADD(Compose):
430
- """
431
- Compose by adding
432
- """
433
-
434
- @property
435
- def nrow(self) -> int:
436
- from plotnine.facets.facet_wrap import wrap_dims
437
-
438
- return wrap_dims(len(self))[0]
439
-
440
- @property
441
- def ncol(self) -> int:
442
- from plotnine.facets.facet_wrap import wrap_dims
443
-
444
- return wrap_dims(len(self))[1]
445
-
446
- def __or__(self, rhs: ggplot | Compose) -> Compose:
447
- """
448
- Add rhs as a column
449
- """
450
- return OR([self, rhs])
451
-
452
- def __truediv__(self, rhs: ggplot | Compose) -> Compose:
453
- """
454
- Add rhs as a row
455
- """
456
- return DIV([self, rhs])
457
-
458
- def __sub__(self, rhs: ggplot | Compose) -> Compose:
459
- """
460
- Add rhs as a column
461
- """
462
- return OR([self, rhs])
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from ._arrange import Arrange
6
+
7
+ if TYPE_CHECKING:
8
+ from plotnine.ggplot import ggplot
9
+
10
+
11
+ class Beside(Arrange):
12
+ """
13
+ Place plots or compositions side by side
14
+
15
+ **Usage**
16
+
17
+ plot | plot
18
+ plot | composition
19
+ composition | plot
20
+ composition | composition
21
+
22
+ Typically, you will use this class through the `|` operator.
23
+ """
24
+
25
+ @property
26
+ def nrow(self) -> int:
27
+ return 1
28
+
29
+ @property
30
+ def ncol(self) -> int:
31
+ return len(self)
32
+
33
+ def __or__(self, rhs: ggplot | Arrange) -> Arrange:
34
+ """
35
+ Add rhs as a column
36
+ """
37
+ # This is adjacent or i.e. (OR | rhs) so we collapse the
38
+ # operands into a single operation
39
+ return Beside([*self, rhs])
40
+
41
+ def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
42
+ """
43
+ Add rhs as a row
44
+ """
45
+ from ._stack import Stack
46
+
47
+ return Stack([self, rhs])
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+
5
+ from plotnine import element_rect, ggplot, theme, theme_void
6
+
7
+
8
+ class plot_spacer(ggplot):
9
+ """
10
+ Blank area as wide or as tall as a plot
11
+
12
+ Parameters
13
+ ----------
14
+ fill :
15
+ Background color. The default is a transparent area, but it
16
+ can be changed through this parameter.
17
+
18
+ The color can also be modified by adding a [](`~plotnine.theme`)
19
+ and setting the [](`~plotnine.themes.themeable.plot_background`).
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ fill: (
25
+ str
26
+ | tuple[float, float, float]
27
+ | tuple[float, float, float, float]
28
+ | None
29
+ ) = None,
30
+ ):
31
+ super().__init__()
32
+ self.theme = theme_void()
33
+ if fill:
34
+ self.theme += theme(plot_background=element_rect(fill=fill))
35
+
36
+ def __add__(self, rhs) -> plot_spacer:
37
+ """
38
+ Add to spacer
39
+
40
+ All added objects are no ops except the `plot_background` in
41
+ in a theme.
42
+ """
43
+ self = deepcopy(self)
44
+ if isinstance(rhs, theme):
45
+ fill = rhs.getp(("plot_background", "facecolor"))
46
+ self.theme += theme(
47
+ plot_background=element_rect(fill=fill),
48
+ )
49
+ return self
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from ._arrange import Arrange
6
+
7
+ if TYPE_CHECKING:
8
+ from plotnine.ggplot import ggplot
9
+
10
+
11
+ class Stack(Arrange):
12
+ """
13
+ Place plots or compositions on top of each other
14
+
15
+ **Usage**
16
+
17
+ plot / plot
18
+ plot / composition
19
+ composition / plot
20
+ composition / composition
21
+
22
+ Typically, you will use this class through the `/` operator.
23
+ """
24
+
25
+ @property
26
+ def nrow(self) -> int:
27
+ return len(self)
28
+
29
+ @property
30
+ def ncol(self) -> int:
31
+ return 1
32
+
33
+ def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
34
+ """
35
+ Add rhs as a row
36
+ """
37
+ # This is an adjacent div i.e. (DIV | rhs) so we collapse the
38
+ # operands into a single operation
39
+ return Stack([*self, rhs])
40
+
41
+ def __or__(self, rhs: ggplot | Arrange) -> Arrange:
42
+ """
43
+ Add rhs as a column
44
+ """
45
+ from ._beside import Beside
46
+
47
+ return Beside([self, rhs])
plotnine/geoms/geom.py CHANGED
@@ -2,9 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import typing
4
4
  from abc import ABC
5
+ from contextlib import suppress
5
6
  from copy import deepcopy
6
7
  from itertools import chain, repeat
7
8
 
9
+ import numpy as np
10
+
8
11
  from .._utils import (
9
12
  data_mapping_as_kwargs,
10
13
  remove_missing,
@@ -12,7 +15,7 @@ from .._utils import (
12
15
  from .._utils.registry import Register, Registry
13
16
  from ..exceptions import PlotnineError
14
17
  from ..layer import layer
15
- from ..mapping.aes import RepeatAesthetic, rename_aesthetics
18
+ from ..mapping.aes import rename_aesthetics
16
19
  from ..mapping.evaluation import evaluate
17
20
  from ..positions.position import position
18
21
  from ..stats.stat import stat
@@ -243,6 +246,9 @@ class geom(ABC, metaclass=Register):
243
246
  :
244
247
  Data used for drawing the geom.
245
248
  """
249
+ from plotnine.mapping import _atomic as atomic
250
+ from plotnine.mapping._atomic import ae_value
251
+
246
252
  missing_aes = (
247
253
  self.DEFAULT_AES.keys()
248
254
  - self.aes_params.keys()
@@ -261,25 +267,31 @@ class geom(ABC, metaclass=Register):
261
267
  num_panels = len(data["PANEL"].unique()) if "PANEL" in data else 1
262
268
  across_panels = num_panels > 1 and not self.params["inherit_aes"]
263
269
 
264
- # Aesthetics set as parameters to the geom/stat
270
+ # Aesthetics set as parameters in the geom/stat
265
271
  for ae, value in self.aes_params.items():
266
- try:
272
+ if isinstance(value, (str, int, float, np.integer, np.floating)):
267
273
  data[ae] = value
268
- except ValueError as e:
269
- # NOTE: Handling of the edgecases in this exception is not
270
- # foolproof.
271
- repeat_ae = getattr(RepeatAesthetic, ae, None)
272
- if across_panels:
273
- # Adding an annotation/abline/hline/vhline with multiple
274
- # items across to more than 1 panel
275
- value = list(chain(*repeat(value, num_panels)))
274
+ elif isinstance(value, ae_value):
275
+ data[ae] = value * len(data)
276
+ elif across_panels:
277
+ value = list(chain(*repeat(value, num_panels)))
278
+ data[ae] = value
279
+ else:
280
+ # Try to make sense of aesthetics whose values can be tuples
281
+ # or sequences of sorts.
282
+ ae_value_cls: type[ae_value] | None = getattr(atomic, ae, None)
283
+ if ae_value_cls:
284
+ with suppress(ValueError):
285
+ data[ae] = ae_value_cls(value) * len(data)
286
+ continue
287
+
288
+ # This should catch the aesthetic assignments to
289
+ # non-numeric or non-string values or sequence of values.
290
+ # e.g. x=datetime, x=Sequence[datetime],
291
+ # x=Sequence[float], shape=Sequence[str]
292
+ try:
276
293
  data[ae] = value
277
- elif repeat_ae:
278
- # Some aesthetics may have valid values that are not
279
- # scalar. e.g. sequences. For such case, we need to
280
- # insert a sequence of the same value.
281
- data[ae] = repeat_ae(value, len(data))
282
- else:
294
+ except ValueError as e:
283
295
  msg = f"'{ae}={value}' does not look like a valid value"
284
296
  raise PlotnineError(msg) from e
285
297
 
@@ -14,12 +14,12 @@ class geom_area(geom_ribbon):
14
14
  """
15
15
  Area plot
16
16
 
17
+ {usage}
18
+
17
19
  An area plot is a special case of geom_ribbon,
18
20
  where the minimum of the range is fixed to 0,
19
21
  and the position adjustment defaults to 'stack'.
20
22
 
21
- {usage}
22
-
23
23
  Parameters
24
24
  ----------
25
25
  {common_parameters}
@@ -7,13 +7,13 @@ class geom_bin_2d(geom_rect):
7
7
  """
8
8
  Heatmap of 2d bin counts
9
9
 
10
+ {usage}
11
+
10
12
  Divides the plane into rectangles, counts the number of
11
13
  cases in each rectangle, and then (by default) maps the number
12
14
  of cases to the rectangle's fill. This is a useful alternative
13
15
  to geom_point in the presence of overplotting.
14
16
 
15
- {usage}
16
-
17
17
  Parameters
18
18
  ----------
19
19
  {common_parameters}
@@ -7,13 +7,13 @@ class geom_col(geom_bar):
7
7
  """
8
8
  Bar plot with base on the x-axis
9
9
 
10
+ {usage}
11
+
10
12
  This is an alternate version of [](`~plotnine.geoms.geom_bar`) that maps
11
13
  the height of bars to an existing variable in your data. If
12
14
  you want the height of the bar to represent a count of cases,
13
15
  use [](`~plotnine.geoms.geom_bar`).
14
16
 
15
- {usage}
16
-
17
17
  Parameters
18
18
  ----------
19
19
  {common_parameters}
@@ -7,12 +7,12 @@ class geom_count(geom_point):
7
7
  """
8
8
  Plot overlapping points
9
9
 
10
+ {usage}
11
+
10
12
  This is a variant [](`~plotnine.geoms.geom_point`) that counts the number
11
13
  of observations at each location, then maps the count to point
12
14
  area. It useful when you have discrete data and overplotting.
13
15
 
14
- {usage}
15
-
16
16
  Parameters
17
17
  ----------
18
18
  {common_parameters}
@@ -7,10 +7,10 @@ class geom_density_2d(geom_path):
7
7
  """
8
8
  2D density estimate
9
9
 
10
- This is a 2d version of [](`~plotnine.geoms.geom_density`).
11
-
12
10
  {usage}
13
11
 
12
+ This is a 2d version of [](`~plotnine.geoms.geom_density`).
13
+
14
14
  Parameters
15
15
  ----------
16
16
  {common_parameters}
@@ -33,10 +33,10 @@ class geom_map(geom):
33
33
  """
34
34
  Draw map feature
35
35
 
36
- The map feature are drawn without any special projections.
37
-
38
36
  {usage}
39
37
 
38
+ The map feature are drawn without any special projections.
39
+
40
40
  Parameters
41
41
  ----------
42
42
  {common_parameters}
@@ -7,13 +7,13 @@ class geom_sina(geom_point):
7
7
  """
8
8
  Draw a sina plot
9
9
 
10
+ {usage}
11
+
10
12
  A sina plot is a data visualization chart suitable for plotting
11
13
  any single variable in a multiclass dataset. It is an enhanced
12
14
  jitter strip chart, where the width of the jitter is controlled
13
15
  by the density distribution of the data within each class.
14
16
 
15
- {usage}
16
-
17
17
  Parameters
18
18
  ----------
19
19
  {common_parameters}
plotnine/ggplot.py CHANGED
@@ -13,7 +13,6 @@ from typing import (
13
13
  Iterable,
14
14
  Optional,
15
15
  cast,
16
- overload,
17
16
  )
18
17
  from warnings import warn
19
18
 
@@ -53,10 +52,10 @@ if TYPE_CHECKING:
53
52
 
54
53
  from plotnine import watermark
55
54
  from plotnine._mpl.gridspec import p9GridSpec
55
+ from plotnine.composition import Arrange
56
56
  from plotnine.coords.coord import coord
57
57
  from plotnine.facets.facet import facet
58
58
  from plotnine.layer import layer
59
- from plotnine.plot_composition import Compose
60
59
  from plotnine.typing import DataLike
61
60
 
62
61
  class PlotAddable(Protocol):
@@ -230,18 +229,10 @@ class ggplot:
230
229
  other.__radd__(self)
231
230
  return self
232
231
 
233
- @overload
234
- def __add__(
235
- self, rhs: PlotAddable | list[PlotAddable] | None
236
- ) -> ggplot: ...
237
-
238
- @overload
239
- def __add__(self, rhs: ggplot | Compose) -> Compose: ...
240
-
241
232
  def __add__(
242
233
  self,
243
- rhs: PlotAddable | list[PlotAddable] | None | ggplot | Compose,
244
- ) -> ggplot | Compose:
234
+ rhs: PlotAddable | list[PlotAddable] | None,
235
+ ) -> ggplot:
245
236
  """
246
237
  Add to ggplot
247
238
 
@@ -251,37 +242,32 @@ class ggplot:
251
242
  Either an object that knows how to "radd"
252
243
  itself to a ggplot, or a list of such objects.
253
244
  """
254
- from .plot_composition import ADD, Compose
255
-
256
- if isinstance(rhs, (ggplot, Compose)):
257
- return ADD([self, rhs])
258
-
259
245
  self = deepcopy(self)
260
246
  return self.__iadd__(rhs)
261
247
 
262
- def __or__(self, rhs: ggplot | Compose) -> Compose:
248
+ def __or__(self, rhs: ggplot | Arrange) -> Arrange:
263
249
  """
264
250
  Compose 2 plots columnwise
265
251
  """
266
- from .plot_composition import OR
252
+ from .composition import Beside
267
253
 
268
- return OR([self, rhs])
254
+ return Beside([self, rhs])
269
255
 
270
- def __truediv__(self, rhs: ggplot | Compose) -> Compose:
256
+ def __truediv__(self, rhs: ggplot | Arrange) -> Arrange:
271
257
  """
272
258
  Compose 2 plots rowwise
273
259
  """
274
- from .plot_composition import DIV
260
+ from .composition import Stack
275
261
 
276
- return DIV([self, rhs])
262
+ return Stack([self, rhs])
277
263
 
278
- def __sub__(self, rhs: ggplot | Compose) -> Compose:
264
+ def __sub__(self, rhs: ggplot | Arrange) -> Arrange:
279
265
  """
280
266
  Compose 2 plots columnwise
281
267
  """
282
- from .plot_composition import OR
268
+ from .composition import Beside
283
269
 
284
- return OR([self, rhs])
270
+ return Beside([self, rhs])
285
271
 
286
272
  def __rrshift__(self, other: DataLike) -> ggplot:
287
273
  """
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from dataclasses import dataclass
5
+ from typing import (
6
+ Any,
7
+ Generic,
8
+ Literal,
9
+ Sequence,
10
+ TypeAlias,
11
+ TypeVar,
12
+ )
13
+
14
+ import numpy as np
15
+ from mizani._colors.utils import is_color_tuple
16
+
17
+ # NOTE:For now we shall use these class privately and not list them
18
+ # in documentation. We can't deal with assigning Sequence[ae_value]
19
+ # to an aesthetic.
20
+
21
+ __all__ = (
22
+ "linetype",
23
+ "color",
24
+ "colour",
25
+ "fill",
26
+ "shape",
27
+ )
28
+
29
+ T = TypeVar("T")
30
+
31
+ ShapeType: TypeAlias = (
32
+ str | tuple[int, Literal[0, 1, 2], float] | Sequence[tuple[float, float]]
33
+ )
34
+
35
+
36
+ @dataclass
37
+ class ae_value(Generic[T]):
38
+ """
39
+ Atomic aesthetic value
40
+
41
+ The goal of this base class is simplify working with the more complex
42
+ aesthetic values. e.g. if a value is a tuple, we don't want it to be
43
+ seen as a sequence of values when assigning it to a dataframe column.
44
+ The subclasses should be able to recognise valid aesthetic values and
45
+ repeat (using multiplication) the value any number of times.
46
+ """
47
+
48
+ value: T
49
+
50
+ def __mul__(self, n: int) -> Sequence[T]:
51
+ """
52
+ Repeat value n times
53
+ """
54
+ return [self.value] * n
55
+
56
+
57
+ @dataclass
58
+ class linetype(ae_value[str | tuple]):
59
+ """
60
+ A single linetype value
61
+ """
62
+
63
+ def __post_init__(self):
64
+ value = self.value
65
+ named = {
66
+ " ",
67
+ "",
68
+ "-",
69
+ "--",
70
+ "-.",
71
+ ":",
72
+ "None",
73
+ "none",
74
+ "dashdot",
75
+ "dashed",
76
+ "dotted",
77
+ "solid",
78
+ }
79
+ if self.value in named:
80
+ return
81
+
82
+ # tuple of the form (offset, (on, off, on, off, ...))
83
+ # e.g (0, (1, 2))
84
+ if (
85
+ isinstance(value, tuple)
86
+ and isinstance(value[0], int)
87
+ and isinstance(value[1], tuple)
88
+ and len(value[1]) % 2 == 0
89
+ and all(isinstance(x, int) for x in value[1])
90
+ ):
91
+ return
92
+
93
+ raise ValueError(f"{value} is not a known linetype.")
94
+
95
+
96
+ @dataclass
97
+ class color(ae_value[str | tuple]):
98
+ """
99
+ A single color value
100
+ """
101
+
102
+ def __post_init__(self):
103
+ if isinstance(self.value, str):
104
+ return
105
+ elif is_color_tuple(self.value):
106
+ self.value = tuple(self.value)
107
+ return
108
+
109
+ raise ValueError(f"{self.value} is not a known color.")
110
+
111
+
112
+ colour = color
113
+
114
+
115
+ @dataclass
116
+ class fill(color):
117
+ """
118
+ A single color value
119
+ """
120
+
121
+
122
+ @dataclass
123
+ class shape(ae_value[ShapeType]):
124
+ """
125
+ A single shape value
126
+ """
127
+
128
+ def __post_init__(self):
129
+ from matplotlib.path import Path
130
+
131
+ from ..scales.scale_shape import FILLED_SHAPES, UNFILLED_SHAPES
132
+
133
+ value = self.value
134
+
135
+ with suppress(TypeError):
136
+ if value in (FILLED_SHAPES | UNFILLED_SHAPES):
137
+ return
138
+
139
+ if isinstance(value, Path):
140
+ return
141
+
142
+ # tuple of the form (numsides, style, angle)
143
+ # where style is in the range [0, 3]
144
+ # e.g (4, 1, 45)
145
+ if (
146
+ isinstance(value, tuple)
147
+ and len(value) == 3
148
+ and isinstance(value[0], int)
149
+ and value[1] in (0, 1, 2)
150
+ and isinstance(value[2], (float, int))
151
+ ):
152
+ return
153
+
154
+ if is_shape_points(value):
155
+ self.value = tuple(value) # pyright: ignore[reportAttributeAccessIssue]
156
+ return
157
+
158
+ raise ValueError(f"{value} is not a known shape.")
159
+
160
+
161
+ def is_shape_points(obj: Any) -> bool:
162
+ """
163
+ Return True if obj is like Sequence[tuple[float, float]]
164
+ """
165
+
166
+ def is_numeric(obj) -> bool:
167
+ """
168
+ Return True if obj is a python or numpy float or integer
169
+ """
170
+ return isinstance(obj, (float, int, np.floating, np.integer))
171
+
172
+ if not iter(obj):
173
+ return False
174
+
175
+ try:
176
+ return all(is_numeric(a) and is_numeric(b) for a, b in obj)
177
+ except (ValueError, TypeError):
178
+ return False
@@ -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
@@ -29,6 +30,7 @@ class _scale_manual(scale_discrete):
29
30
  isinstance(self.breaks, Iterable)
30
31
  and isinstance(self.breaks, Sized)
31
32
  and len(self.breaks) == len(values)
33
+ and not isinstance(values, Mapping)
32
34
  ):
33
35
  values = dict(zip(self.breaks, values))
34
36
 
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from .element_base import element_base
6
6
 
7
7
  if TYPE_CHECKING:
8
- from typing import Any, Literal, Optional, Sequence
8
+ from typing import Any, Literal, Sequence
9
9
 
10
10
 
11
11
  class element_line(element_base):
@@ -26,6 +26,8 @@ class element_line(element_base):
26
26
  using tuples, see [](`~matplotlib.lines.line2D.set_linestyle`).
27
27
  size : float
28
28
  line thickness
29
+ alpha : float
30
+ Opacity value
29
31
  kwargs : dict
30
32
  Parameters recognised by [](`~matplotlib.lines.line2d`).
31
33
  """
@@ -33,31 +35,37 @@ class element_line(element_base):
33
35
  def __init__(
34
36
  self,
35
37
  *,
36
- color: Optional[
38
+ color: (
37
39
  str
38
40
  | tuple[float, float, float]
39
41
  | tuple[float, float, float, float]
40
- ] = None,
41
- size: Optional[float] = None,
42
- linetype: Optional[str | Sequence[int]] = None,
43
- lineend: Optional[Literal["butt", "projecting", "round"]] = None,
44
- colour: Optional[
42
+ | None
43
+ ) = None,
44
+ size: float | None = None,
45
+ linetype: str | Sequence[int] | None = None,
46
+ lineend: Literal["butt", "projecting", "round"] | None = None,
47
+ colour: (
45
48
  str
46
49
  | tuple[float, float, float]
47
50
  | tuple[float, float, float, float]
48
- ] = None,
51
+ | None
52
+ ) = None,
53
+ alpha: float | None = None,
49
54
  **kwargs: Any,
50
55
  ):
51
56
  super().__init__()
52
57
  self.properties.update(**kwargs)
53
58
 
54
- color = color if color else colour
59
+ color = color if color is not None else colour
60
+
55
61
  if color:
56
62
  self.properties["color"] = color
57
63
  if size:
58
64
  self.properties["linewidth"] = size
59
65
  if linetype:
60
66
  self.properties["linestyle"] = linetype
67
+ if alpha is not None:
68
+ self.properties["alpha"] = alpha
61
69
 
62
70
  if linetype in ("solid", "-") and lineend:
63
71
  self.properties["solid_capstyle"] = lineend
@@ -17,7 +17,7 @@ from warnings import warn
17
17
 
18
18
  import numpy as np
19
19
 
20
- from .._utils import to_rgba
20
+ from .._utils import has_alpha_channel, to_rgba
21
21
  from .._utils.registry import RegistryHierarchyMeta
22
22
  from ..exceptions import PlotnineError, deprecated_themeable_name
23
23
  from .elements import element_blank
@@ -504,6 +504,25 @@ class MixinSequenceOfValues(themeable):
504
504
  a.set(**{name: value})
505
505
 
506
506
 
507
+ def blend_alpha(
508
+ properties: dict[str, Any], key: str = "color"
509
+ ) -> dict[str, Any]:
510
+ """
511
+ Blend color with alpha
512
+
513
+ When setting color property values of matplotlib objects,
514
+ for a color with an alpha channel, we don't want the alpha
515
+ property if any to have any effect on that color.
516
+ """
517
+ if (color := properties.get(key)) is not None:
518
+ if "alpha" in properties:
519
+ properties[key] = to_rgba(color, properties["alpha"])
520
+ properties["alpha"] = None
521
+ elif has_alpha_channel(color):
522
+ properties["alpha"] = None
523
+ return properties
524
+
525
+
507
526
  # element_text themeables
508
527
 
509
528
 
@@ -1366,7 +1385,7 @@ class panel_grid_major_x(themeable):
1366
1385
 
1367
1386
  def apply_ax(self, ax: Axes):
1368
1387
  super().apply_ax(ax)
1369
- ax.xaxis.grid(which="major", **self.properties)
1388
+ ax.xaxis.grid(which="major", **blend_alpha(self.properties))
1370
1389
 
1371
1390
  def blank_ax(self, ax: Axes):
1372
1391
  super().blank_ax(ax)
@@ -1384,7 +1403,7 @@ class panel_grid_major_y(themeable):
1384
1403
 
1385
1404
  def apply_ax(self, ax: Axes):
1386
1405
  super().apply_ax(ax)
1387
- ax.yaxis.grid(which="major", **self.properties)
1406
+ ax.yaxis.grid(which="major", **blend_alpha(self.properties))
1388
1407
 
1389
1408
  def blank_ax(self, ax: Axes):
1390
1409
  super().blank_ax(ax)
@@ -1612,11 +1631,7 @@ class panel_background(legend_key):
1612
1631
 
1613
1632
  def apply_ax(self, ax: Axes):
1614
1633
  super().apply_ax(ax)
1615
- d = self.properties
1616
- if "facecolor" in d and "alpha" in d:
1617
- d["facecolor"] = to_rgba(d["facecolor"], d["alpha"])
1618
- del d["alpha"]
1619
-
1634
+ d = blend_alpha(self.properties, "facecolor")
1620
1635
  d["edgecolor"] = "none"
1621
1636
  d["linewidth"] = 0
1622
1637
  ax.patch.set(**d)
@@ -1642,16 +1657,12 @@ class panel_border(MixinSequenceOfValues):
1642
1657
  if not (rects := targets.panel_border):
1643
1658
  return
1644
1659
 
1645
- d = self.properties
1660
+ d = blend_alpha(self.properties, "edgecolor")
1646
1661
 
1647
1662
  with suppress(KeyError):
1648
1663
  if d["edgecolor"] == "none" or d["size"] == 0:
1649
1664
  return
1650
1665
 
1651
- if "edgecolor" in d and "alpha" in d:
1652
- d["edgecolor"] = to_rgba(d["edgecolor"], d["alpha"])
1653
- del d["alpha"]
1654
-
1655
1666
  self.set(rects, d)
1656
1667
 
1657
1668
  def blank_figure(self, figure: Figure, targets: ThemeTargets):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotnine
3
- Version: 0.15.0a4
3
+ Version: 0.15.0a6
4
4
  Summary: A Grammar of Graphics for Python
5
5
  Author-email: Hassan Kibirige <has2k1@gmail.com>
6
6
  License: The MIT License (MIT)
@@ -2,7 +2,7 @@ plotnine/__init__.py,sha256=HrJhd65bnny1t-TawUgvApVj4p-gDZ0ftpr2NKZeW_s,10316
2
2
  plotnine/animation.py,sha256=izJZ4Gy0cBHEBc8ehofsWSWOzZW8UEroy1Uvw86Igb0,7521
3
3
  plotnine/doctools.py,sha256=JBF55q1MX2fXYQcGDpVrGPdlKf5OiQ5gyTdWhnM_IzU,14558
4
4
  plotnine/exceptions.py,sha256=SgTxBHkV65HjGI3aFy2q1_lHP9HAdiuxVLN3U-PJWSQ,1616
5
- plotnine/ggplot.py,sha256=xFj9iWAyBvnhitCrpgdNonQIqqjBQ2aDgkqpvHbH364,24823
5
+ plotnine/ggplot.py,sha256=Nl41v1zRUulyIBMzrzIE4IgIrHU0bicL2YH4x01wHt8,24457
6
6
  plotnine/helpers.py,sha256=4R3KZmtGH46-kRNSGOA0JxZaLKBo0ge8Vnx1cDQ8_gI,966
7
7
  plotnine/iapi.py,sha256=jNLmUSoh5g9kNdhOoXSqNcqOdd2-6xdWAmst-YGU41U,9095
8
8
  plotnine/labels.py,sha256=3pOXth2Xma_qCqB_xXAGIkPQ9gcaUaaFEAsa5if1iR0,2830
@@ -21,17 +21,23 @@ plotnine/_mpl/ticker.py,sha256=RY_7AdTggc7QBq9_t0KBJXg36oxKfB-Vtc9FzLnaGnQ,393
21
21
  plotnine/_mpl/transforms.py,sha256=DNaOlNq76xlT696sN8ot1bmYyp4mmrjXQHk3kTi4HIg,76
22
22
  plotnine/_mpl/utils.py,sha256=0GY3CWWXZiNHe333v9GAquRe5naO54JeiPt67-UvVYw,4147
23
23
  plotnine/_mpl/layout_manager/__init__.py,sha256=IXpPF5Oycc45uFpK4MJ6kcQCe1u5VUfnHLNZGcnrJCg,157
24
- plotnine/_mpl/layout_manager/_engine.py,sha256=ESUvbLAlZApFbBi6w7gZA7S4guS0Rmrj-us-gzYP2ZM,2809
24
+ plotnine/_mpl/layout_manager/_engine.py,sha256=m6FDFc1XELfzNSIXaFiN3ruoTgub2PaV3wPQWP0IBYE,2804
25
25
  plotnine/_mpl/layout_manager/_layout_items.py,sha256=3XRBl7xEdBKdhrexRmnVe7k919po6nkyE0q5Hx7j5cQ,29642
26
- plotnine/_mpl/layout_manager/_layout_tree.py,sha256=O6U78CYOjSwS4lt56YF3YiOCXdxmV_oy3lYS4gMmzSc,26447
26
+ plotnine/_mpl/layout_manager/_layout_tree.py,sha256=yFNt7fsv2VhEt3261OfLP7kgPYNd_npLoNDLp_QJi3Y,26445
27
27
  plotnine/_mpl/layout_manager/_spaces.py,sha256=ahBpKt-q1kVOTGiGliwl_DNB6pTEekAzA_7-GXwFlBk,35789
28
- plotnine/_utils/__init__.py,sha256=czHi-uv4eIBCDf6td11bGA41PMXaJ5j7t-avx_JbYgY,30636
28
+ plotnine/_utils/__init__.py,sha256=3tcSTng6Mtk1o6NPEikALHKD3ZGuAYofjQJREHX84a4,30992
29
29
  plotnine/_utils/context.py,sha256=HPQy_uyNXdS0s9URD7ZePyuc5hFU2XrRBLDTqRDLJzY,1708
30
30
  plotnine/_utils/dev.py,sha256=0qgRbMhcd4dfuLuYxx0skocKAtfwHF02ntyILRBogbg,1629
31
31
  plotnine/_utils/ipython.py,sha256=5Obr73xJ-4dzJEdBrFA8z9TXuxY7pIjKmzdTzWwnxNk,1884
32
32
  plotnine/_utils/quarto.py,sha256=bwbU3ork8wuUIW5VDJ73J_DbWWzpYWpAd76cHMzCRao,890
33
33
  plotnine/_utils/registry.py,sha256=HoiB2NnbEHufXjYnVJyrJKflk2RwKtTYk2L3n7tH4XA,3321
34
34
  plotnine/_utils/yippie.py,sha256=DbmxvVrd34P24CCmOZrAulyGQ35rXNaSr8BuutS2Uio,2392
35
+ plotnine/composition/__init__.py,sha256=WXkLaqbgpOquCbq_hakUUQlcIka9fN0IMKNm-DOns6E,198
36
+ plotnine/composition/_arrange.py,sha256=-ZPe3JydfVAvVYiTI0W7aJaUySonpE2o76aKaxfjS84,11040
37
+ plotnine/composition/_beside.py,sha256=DAMZfIrOfz4mQSqro22GpooaCnm8ns6lG6vIp8SnAtg,993
38
+ plotnine/composition/_plot_spacer.py,sha256=gjAymmjOVZwwT0Y01MqHA3tAEDAr0mcWSX07npzo1Hw,1298
39
+ plotnine/composition/_plotspec.py,sha256=0F7q7PjDMDqcallpnBdX3N2iSRjdBTyjSvMFf83uvPU,1015
40
+ plotnine/composition/_stack.py,sha256=TXvy8FQ7CNNzntkjz4h4R_6-LxYSNFF5ri9E82dv17Q,1007
35
41
  plotnine/coords/__init__.py,sha256=inM-9JwSPvYLlOy6opye0YV2EGWsI4708wGKdHvQvVM,301
36
42
  plotnine/coords/coord.py,sha256=Yu2xj4BqFBWlS0XHNDa6h8Dek0_PA6Lssv3M9EneSB8,6883
37
43
  plotnine/coords/coord_cartesian.py,sha256=vGeSFe9UyycUkbjdjoPNSQJwnnU7fEmsk0nl_oBvzxY,3038
@@ -69,18 +75,18 @@ plotnine/geoms/__init__.py,sha256=HEfhNmmNH4xm4rpXnFRXY4eLkJha3XPM72IIwVjv5Lc,26
69
75
  plotnine/geoms/annotate.py,sha256=T5RxepV55HVNzPfkq43BWxduNIZPslRfPD1yx4bJtoo,4165
70
76
  plotnine/geoms/annotation_logticks.py,sha256=6iGdo5szck0_nXdHnvMaRMZuRbH8Tg87tJ_aan_frqg,8969
71
77
  plotnine/geoms/annotation_stripes.py,sha256=4Cw7TJ4SZChm_ioqfiiku0cPNnLruGuAP-4vyRao-9Y,6080
72
- plotnine/geoms/geom.py,sha256=cjxzWTZiAMop9FuXWg5kqgo1lBEG2Uu6DzARjHOpBY4,17300
78
+ plotnine/geoms/geom.py,sha256=ayhBEoPc-9MLpu18HkwLoby4NIKC68ED4Pq0ioa4I9c,17687
73
79
  plotnine/geoms/geom_abline.py,sha256=6oxAJl_yFKKmf7OTHvACw6fg6kgJEN54hGKkyWOLr6o,3188
74
- plotnine/geoms/geom_area.py,sha256=wvQ4nNvhJNN3nfn6Bv1gCARC6IWTjOjOfHPfSmg6Sxc,818
80
+ plotnine/geoms/geom_area.py,sha256=QtS9NO17v-i5gvfakN8L96xwjt3dFuyZdYN8oXaXWOY,818
75
81
  plotnine/geoms/geom_bar.py,sha256=SnqS4hPTfqXzdPh1U-kNuBg0LNX9_tQC9OKhIlB7cy0,1732
76
- plotnine/geoms/geom_bin_2d.py,sha256=b2fAQVywug-ey3KtqeOoYQ2RNRSRE_fa4s7M41W-_FE,574
82
+ plotnine/geoms/geom_bin_2d.py,sha256=IhlpR62o4Y6qe-tNUywOMjhseJNPV3c-x2QB6D11EPU,574
77
83
  plotnine/geoms/geom_blank.py,sha256=38BpE9iSh3ktbOe9ayNer6IDD1q7E4EiKS_KnFSko0c,770
78
84
  plotnine/geoms/geom_boxplot.py,sha256=OISXd3CCuH06sKqOB8xlfUPrwUX_FUKQauSqE7L-PhQ,9778
79
- plotnine/geoms/geom_col.py,sha256=sGZZzkXgwlgI2D-M1UE1QAWsWZqtP1z8R0HjrnW9hkY,1187
80
- plotnine/geoms/geom_count.py,sha256=IRIlQqwt0kIHf9swYZbFqd5RSQCYRyFKm5hp1C9xHB4,511
85
+ plotnine/geoms/geom_col.py,sha256=KCpme3vsc_tTbjGvL7ZhJZ4KFrQDQw3K8JyO6EBFn4E,1187
86
+ plotnine/geoms/geom_count.py,sha256=4quwvXQSrdDdlQfaSRmkBzpEP6qCRPB9SS6_5UV037M,511
81
87
  plotnine/geoms/geom_crossbar.py,sha256=0NGUY4fhL8VxpGaBgjKQNJy85XJeLc6GiPCs5LZQuCc,6242
82
88
  plotnine/geoms/geom_density.py,sha256=UwUkJxI79L3z19tmoSI6ZYs418XTbRznd-Abzrec3VY,526
83
- plotnine/geoms/geom_density_2d.py,sha256=V37QYpdZpZvYxishlN1UBmyyRC3U2eOqR-gaIPxQqS4,400
89
+ plotnine/geoms/geom_density_2d.py,sha256=xEtQTTQnBzOs5PMDViZNQoSiXduU93SGmTN0u77RPj4,400
84
90
  plotnine/geoms/geom_dotplot.py,sha256=ZHmRCEGS3Nv2tiNwALBC7ewBg1prcb_FOxAImTGKP3c,8680
85
91
  plotnine/geoms/geom_errorbar.py,sha256=jpCuQ8CE2RKfRR8Aw_3s473HXrOk-WPOl46n4oC-8UI,2199
86
92
  plotnine/geoms/geom_errorbarh.py,sha256=mgRo8KngGyKVSPPF8vUYlzdslBJEIe9QJC--DlWL5t4,2213
@@ -91,7 +97,7 @@ plotnine/geoms/geom_jitter.py,sha256=mJCLt2SoZN_tx_3XS8oF5JojtL2m5HO1bEqdN-JEPUg
91
97
  plotnine/geoms/geom_label.py,sha256=lPxmDlS6VgEHqD3Ac1Q-Zxs75JcnduoXjJ_D1AzzWew,2835
92
98
  plotnine/geoms/geom_line.py,sha256=IzKVPwF_Oe3uwRpuh5Xu9LFvz0XcKqCL4pN03iGHBdk,522
93
99
  plotnine/geoms/geom_linerange.py,sha256=dkgQ28YZfjyMb_y8s0wfcAz5mHF7f7y_h1Den5wHyJk,1282
94
- plotnine/geoms/geom_map.py,sha256=E2DVSMx9-8-4duCUOd-4n-Lqu_VDL5qzQrt2PCLkMzY,9602
100
+ plotnine/geoms/geom_map.py,sha256=tvzFVNOtSOfof1NQFduB8SV143o2irNsQrM8BgYiEKU,9602
95
101
  plotnine/geoms/geom_path.py,sha256=nDrlmqPn869vyUF4RYbLKkbQka14SWJu9pqCN-cdKbQ,17106
96
102
  plotnine/geoms/geom_point.py,sha256=KWM511zxIpKQzxI_NJpFqEszNWg5E7iHZJylKvvaK2c,4607
97
103
  plotnine/geoms/geom_pointdensity.py,sha256=TKaxAUPwdrVMw0MVYe14apBsRfW_5_zXFLSlOS60Zlw,372
@@ -105,7 +111,7 @@ plotnine/geoms/geom_rect.py,sha256=dGOAkVXf5NSJgIbaVezg1ac78GqimElB9Ua0L_SH_YM,3
105
111
  plotnine/geoms/geom_ribbon.py,sha256=af9uElWaJQSc4taTLZ7ZDzng2Zw4IrIfNPrus4OuTmM,5771
106
112
  plotnine/geoms/geom_rug.py,sha256=2Sdarf5z3w4JbklVUk7JykWdbBNKaT0Wl5NMBl4LqT4,3520
107
113
  plotnine/geoms/geom_segment.py,sha256=v63MoKlK4aNengCXYdPeQrcBIgBCis8QobpF3CJOf5U,3052
108
- plotnine/geoms/geom_sina.py,sha256=oyCiBCdsWjrSNaS2jOpu0Ng8nEl_peaCT-EuHE9jG34,912
114
+ plotnine/geoms/geom_sina.py,sha256=JspG0o_K4px0KKZVaVeuBnk0iGjVJnB9PGjt8UAf08I,912
109
115
  plotnine/geoms/geom_smooth.py,sha256=8jSxQFTdoo7RmeTrGl2j-iE6lfydUPitz-dKvxKk0Kw,3545
110
116
  plotnine/geoms/geom_spoke.py,sha256=s-kug0H-YGhyjso9W43XvzJf9-g6inh8zzuSFeXzSaU,977
111
117
  plotnine/geoms/geom_step.py,sha256=tTohADfGyC3M4zCzxLUhzkA2XsHBeH0eaWq-m2DQEQQ,2371
@@ -120,14 +126,11 @@ plotnine/guides/guide_colorbar.py,sha256=gL0218k3iJPNEpj6hWKxau3R8qRpT2bPkS1q9Pt
120
126
  plotnine/guides/guide_legend.py,sha256=CrcV3iCAcEfUnSbkGtsS31c3OFQMWKiHqZZXejxOdno,14212
121
127
  plotnine/guides/guides.py,sha256=cV7CwoYNrjkeaDHZ2AGcS2Dij5RpPovSiB-v47E7vhQ,15471
122
128
  plotnine/mapping/__init__.py,sha256=DLu9E0kwwuHxzTUenoVjCNTTdkWMwIDtkExLleBq1MI,205
129
+ plotnine/mapping/_atomic.py,sha256=TbobHVJlHRoSHibi6OOWMVM2J1r_kKQJMS6G5zvEhrg,4029
123
130
  plotnine/mapping/_env.py,sha256=ZXlTt2THRIcWb2WGk9fCpCMdVynlUX_BpG0Uwj7axi0,6072
124
131
  plotnine/mapping/_eval_environment.py,sha256=PTrnnqrxMXqjt23t2NGRcU9i8Jie3ZaMe6W5aKtI7bI,2502
125
132
  plotnine/mapping/aes.py,sha256=eqNTBHqFnSBPoVNdrUB7pYM-ShlUTYvmwdQRXh9beV4,16717
126
133
  plotnine/mapping/evaluation.py,sha256=kblTxVv3M4xIGnHyReUU0RtmmIN77Or2JBcci0nGGfE,5913
127
- plotnine/plot_composition/__init__.py,sha256=ZJYpfVF158cQZ1zREXy6wHNJ4FbSmqWxIkHWZwX3QT8,148
128
- plotnine/plot_composition/_compose.py,sha256=6UgXs6VBH0LIXW2uQlBQy-URh_mM948F5GOwN6IV734,12167
129
- plotnine/plot_composition/_plotspec.py,sha256=0F7q7PjDMDqcallpnBdX3N2iSRjdBTyjSvMFf83uvPU,1015
130
- plotnine/plot_composition/_spacer.py,sha256=vaC4F5tHhvL7T7Ns9zxUbytqwB6MLNhm5jtiKG0vAiU,798
131
134
  plotnine/positions/__init__.py,sha256=DQZE6duMUNRQifpa6SBrOKxZpGDk4NqQSGZLr9Yc9GI,595
132
135
  plotnine/positions/position.py,sha256=BRD_dUh9dhdSP4SQfl8_u_VpaY_s3A2zuznwOn5M_80,8325
133
136
  plotnine/positions/position_dodge.py,sha256=6PbAhWDfNK7NK6FZZOcfr4mdmVOUV9VLH8xD0dbQFEU,3941
@@ -151,7 +154,7 @@ plotnine/scales/scale_datetime.py,sha256=OM9gfHKGkQIBrgqCEE00zUV0cMXXxTIS7wc9dYQ
151
154
  plotnine/scales/scale_discrete.py,sha256=UwAB0icMljH-eW6mK3g0YWAVzuE4P_91ZMYJhoMoMV4,9431
152
155
  plotnine/scales/scale_identity.py,sha256=-PL9vJn0C_wOgrOQpqYSQbTWFSALoRPFzGXzebABTv8,2211
153
156
  plotnine/scales/scale_linetype.py,sha256=pwLTmglN6d4bnChbuBi6HWbDP_nsE4viXD8CK6XKBPw,1373
154
- plotnine/scales/scale_manual.py,sha256=oMnIfQNfxO4qw-gkBG3ikyuXdeoMgstRSdiibLc_DAA,4525
157
+ plotnine/scales/scale_manual.py,sha256=FmzF4gRxdiDVgryodN2Mx6Gr1FHW0grKO3WiL1ffkTc,4609
155
158
  plotnine/scales/scale_shape.py,sha256=I0JdYDreCbznQilaTi6m27F91N5daCj9D2ZPuP2Szls,2081
156
159
  plotnine/scales/scale_size.py,sha256=04nUQzKB_qpuQrcg8C7jYmLDYtdnlg6PkY7SivP5OlY,3273
157
160
  plotnine/scales/scale_stroke.py,sha256=7LKAg6-Q3VTuw78N2XEjFFzBa8yY5NVnfBSoJGsBS5o,1711
@@ -203,16 +206,16 @@ plotnine/themes/theme_seaborn.py,sha256=8giJh8QvkMJ8pr_XXHPDMfkysoI2_yr0vJhz48_s
203
206
  plotnine/themes/theme_tufte.py,sha256=qUOrZhQyfJgc0fmy8Es7tT7aYqUSzCjvkP7-dBilwHE,1926
204
207
  plotnine/themes/theme_void.py,sha256=gYXr367zYNhvt_NXlDUx0rBPUYJa1F6adnANn5HEx5A,3407
205
208
  plotnine/themes/theme_xkcd.py,sha256=5MOdj_H23Kr_jbDnepmq1DQDbn8-TfUmtzYBZTb4RB4,2207
206
- plotnine/themes/themeable.py,sha256=aMm5MxbsmEV1XX87AFPPBfAgkJP3Vm0_z9_tKZwsxxY,71321
209
+ plotnine/themes/themeable.py,sha256=Px91yh5pft24mC-4uUFLYpU-45iX6cE64j3t-a20wug,71744
207
210
  plotnine/themes/elements/__init__.py,sha256=xV1la_mTv1BQ3zQp7ZGVGPdV6KohvEVhKAi1uPTeAs0,327
208
211
  plotnine/themes/elements/element_base.py,sha256=D7cfEglzsSuhW91KpZVAZ2MAHWZp64r9Aajoh8uMGZ4,832
209
212
  plotnine/themes/elements/element_blank.py,sha256=4r7-6HeR1494oWNIGQh0ASrFQ4SLvYa6aQHA85eH-Ds,187
210
- plotnine/themes/elements/element_line.py,sha256=xF6xW-iA66YEP_fN7ooqaYry8_8qZT-eT5wvKwXg3to,1838
213
+ plotnine/themes/elements/element_line.py,sha256=yZvj9B3M2a7a8o2J0n-q0uviNv34PtJVUr_UefZ-v5I,2005
211
214
  plotnine/themes/elements/element_rect.py,sha256=w5cLH-Sr4cTRXVdkRiu8kBqFt3TXHhIb1MUITfi89gE,1767
212
215
  plotnine/themes/elements/element_text.py,sha256=8yhwBa9s9JKCtBcqcBNybbCGK6ieDnZv4SHiC4Sy2qc,6255
213
216
  plotnine/themes/elements/margin.py,sha256=jMHe-UKHHer_VYwAVDC-Tz2-AP_4YDuXPTWAuacoqgU,4080
214
- plotnine-0.15.0a4.dist-info/licenses/LICENSE,sha256=GY4tQiUd17Tq3wWR42Zs9MRTFOTf6ahIXhZTcwAdOeU,1082
215
- plotnine-0.15.0a4.dist-info/METADATA,sha256=MCVzMIfRSdwYyq2UrgdS74Zc8QiMo9pp8GiwCVFZXCo,9407
216
- plotnine-0.15.0a4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
217
- plotnine-0.15.0a4.dist-info/top_level.txt,sha256=t340Mbko1ZbmvYPkQ81dIiPHcaQdTUszYz-bWUpr8ys,9
218
- plotnine-0.15.0a4.dist-info/RECORD,,
217
+ plotnine-0.15.0a6.dist-info/licenses/LICENSE,sha256=GY4tQiUd17Tq3wWR42Zs9MRTFOTf6ahIXhZTcwAdOeU,1082
218
+ plotnine-0.15.0a6.dist-info/METADATA,sha256=FDNoeUumEK4SXTfxin727GT3RL94ky46x9L5e5fNQ-g,9407
219
+ plotnine-0.15.0a6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
220
+ plotnine-0.15.0a6.dist-info/top_level.txt,sha256=t340Mbko1ZbmvYPkQ81dIiPHcaQdTUszYz-bWUpr8ys,9
221
+ plotnine-0.15.0a6.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- from ._compose import ADD, DIV, OR, Compose
2
- from ._spacer import spacer
3
-
4
- __all__ = (
5
- "Compose",
6
- "ADD",
7
- "DIV",
8
- "OR",
9
- "spacer",
10
- )
@@ -1,32 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from copy import deepcopy
4
-
5
- from plotnine import element_rect, ggplot, theme, theme_void
6
-
7
-
8
- class spacer(ggplot):
9
- """
10
- An empty plot
11
- """
12
-
13
- def __init__(self):
14
- super().__init__()
15
- self.theme = theme_void()
16
-
17
- def __add__(self, rhs) -> spacer: # pyright: ignore[reportIncompatibleMethodOverride]
18
- """
19
- Add to spacer
20
-
21
- All added objects are no ops except the plot_background,
22
- i.e.:
23
-
24
- theme(plot_background=element_rect(fill="red"))
25
- """
26
- self = deepcopy(self)
27
- if isinstance(rhs, theme):
28
- fill = rhs.getp(("plot_background", "facecolor"))
29
- self.theme += theme(
30
- plot_background=element_rect(fill=fill),
31
- )
32
- return self
File without changes