plotnine 0.15.0a4__py3-none-any.whl → 0.15.0a5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plotnine/geoms/geom.py +29 -17
- plotnine/mapping/_atomic.py +178 -0
- {plotnine-0.15.0a4.dist-info → plotnine-0.15.0a5.dist-info}/METADATA +1 -1
- {plotnine-0.15.0a4.dist-info → plotnine-0.15.0a5.dist-info}/RECORD +7 -6
- {plotnine-0.15.0a4.dist-info → plotnine-0.15.0a5.dist-info}/WHEEL +0 -0
- {plotnine-0.15.0a4.dist-info → plotnine-0.15.0a5.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0a4.dist-info → plotnine-0.15.0a5.dist-info}/top_level.txt +0 -0
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
|
|
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
|
|
270
|
+
# Aesthetics set as parameters in the geom/stat
|
|
265
271
|
for ae, value in self.aes_params.items():
|
|
266
|
-
|
|
272
|
+
if isinstance(value, (str, int, float, np.integer, np.floating)):
|
|
267
273
|
data[ae] = value
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
@@ -69,7 +69,7 @@ plotnine/geoms/__init__.py,sha256=HEfhNmmNH4xm4rpXnFRXY4eLkJha3XPM72IIwVjv5Lc,26
|
|
|
69
69
|
plotnine/geoms/annotate.py,sha256=T5RxepV55HVNzPfkq43BWxduNIZPslRfPD1yx4bJtoo,4165
|
|
70
70
|
plotnine/geoms/annotation_logticks.py,sha256=6iGdo5szck0_nXdHnvMaRMZuRbH8Tg87tJ_aan_frqg,8969
|
|
71
71
|
plotnine/geoms/annotation_stripes.py,sha256=4Cw7TJ4SZChm_ioqfiiku0cPNnLruGuAP-4vyRao-9Y,6080
|
|
72
|
-
plotnine/geoms/geom.py,sha256=
|
|
72
|
+
plotnine/geoms/geom.py,sha256=ayhBEoPc-9MLpu18HkwLoby4NIKC68ED4Pq0ioa4I9c,17687
|
|
73
73
|
plotnine/geoms/geom_abline.py,sha256=6oxAJl_yFKKmf7OTHvACw6fg6kgJEN54hGKkyWOLr6o,3188
|
|
74
74
|
plotnine/geoms/geom_area.py,sha256=wvQ4nNvhJNN3nfn6Bv1gCARC6IWTjOjOfHPfSmg6Sxc,818
|
|
75
75
|
plotnine/geoms/geom_bar.py,sha256=SnqS4hPTfqXzdPh1U-kNuBg0LNX9_tQC9OKhIlB7cy0,1732
|
|
@@ -120,6 +120,7 @@ plotnine/guides/guide_colorbar.py,sha256=gL0218k3iJPNEpj6hWKxau3R8qRpT2bPkS1q9Pt
|
|
|
120
120
|
plotnine/guides/guide_legend.py,sha256=CrcV3iCAcEfUnSbkGtsS31c3OFQMWKiHqZZXejxOdno,14212
|
|
121
121
|
plotnine/guides/guides.py,sha256=cV7CwoYNrjkeaDHZ2AGcS2Dij5RpPovSiB-v47E7vhQ,15471
|
|
122
122
|
plotnine/mapping/__init__.py,sha256=DLu9E0kwwuHxzTUenoVjCNTTdkWMwIDtkExLleBq1MI,205
|
|
123
|
+
plotnine/mapping/_atomic.py,sha256=TbobHVJlHRoSHibi6OOWMVM2J1r_kKQJMS6G5zvEhrg,4029
|
|
123
124
|
plotnine/mapping/_env.py,sha256=ZXlTt2THRIcWb2WGk9fCpCMdVynlUX_BpG0Uwj7axi0,6072
|
|
124
125
|
plotnine/mapping/_eval_environment.py,sha256=PTrnnqrxMXqjt23t2NGRcU9i8Jie3ZaMe6W5aKtI7bI,2502
|
|
125
126
|
plotnine/mapping/aes.py,sha256=eqNTBHqFnSBPoVNdrUB7pYM-ShlUTYvmwdQRXh9beV4,16717
|
|
@@ -211,8 +212,8 @@ plotnine/themes/elements/element_line.py,sha256=xF6xW-iA66YEP_fN7ooqaYry8_8qZT-e
|
|
|
211
212
|
plotnine/themes/elements/element_rect.py,sha256=w5cLH-Sr4cTRXVdkRiu8kBqFt3TXHhIb1MUITfi89gE,1767
|
|
212
213
|
plotnine/themes/elements/element_text.py,sha256=8yhwBa9s9JKCtBcqcBNybbCGK6ieDnZv4SHiC4Sy2qc,6255
|
|
213
214
|
plotnine/themes/elements/margin.py,sha256=jMHe-UKHHer_VYwAVDC-Tz2-AP_4YDuXPTWAuacoqgU,4080
|
|
214
|
-
plotnine-0.15.
|
|
215
|
-
plotnine-0.15.
|
|
216
|
-
plotnine-0.15.
|
|
217
|
-
plotnine-0.15.
|
|
218
|
-
plotnine-0.15.
|
|
215
|
+
plotnine-0.15.0a5.dist-info/licenses/LICENSE,sha256=GY4tQiUd17Tq3wWR42Zs9MRTFOTf6ahIXhZTcwAdOeU,1082
|
|
216
|
+
plotnine-0.15.0a5.dist-info/METADATA,sha256=HJHt2oWR1vudsK80OUWXBGCkU_d_Q6TWUlvo2d9w-nQ,9407
|
|
217
|
+
plotnine-0.15.0a5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
218
|
+
plotnine-0.15.0a5.dist-info/top_level.txt,sha256=t340Mbko1ZbmvYPkQ81dIiPHcaQdTUszYz-bWUpr8ys,9
|
|
219
|
+
plotnine-0.15.0a5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|