dataplot 0.1.6__tar.gz → 0.1.8__tar.gz
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.
- {dataplot-0.1.6 → dataplot-0.1.8}/PKG-INFO +17 -2
- {dataplot-0.1.6 → dataplot-0.1.8}/README.md +14 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/examples/test.ipynb +1 -1
- {dataplot-0.1.6 → dataplot-0.1.8}/pyproject.toml +3 -2
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/_typing.py +0 -1
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/corrmap.py +1 -1
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/histogram.py +5 -5
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/ksplot.py +4 -4
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/linechart.py +1 -1
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/ppplot.py +4 -4
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/qqplot.py +4 -4
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/scatterchart.py +1 -1
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/container.py +82 -51
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/core.py +74 -24
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/dataset.py +1 -1
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/setting.py +154 -46
- {dataplot-0.1.6 → dataplot-0.1.8}/.github/workflows/python-publish.yml +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/.gitignore +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/LICENSE +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/install.py +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/__init__.py +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/__init__.py +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/base.py +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/utils/__init__.py +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/utils/math.py +0 -0
- {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/utils/multi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Provides plotting tools useful in datascience.
|
|
5
5
|
Project-URL: Documentation, https://github.com/Chitaoji/dataplot/blob/main/README.md
|
|
6
6
|
Project-URL: Repository, https://github.com/Chitaoji/dataplot/
|
|
@@ -10,9 +10,10 @@ License-Expression: BSD-3-Clause
|
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: plot
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Python: >=3.13
|
|
15
15
|
Requires-Dist: lazyr
|
|
16
|
+
Requires-Dist: loggings
|
|
16
17
|
Requires-Dist: matplotlib
|
|
17
18
|
Requires-Dist: numpy
|
|
18
19
|
Requires-Dist: pandas
|
|
@@ -33,6 +34,7 @@ $ pip install dataplot
|
|
|
33
34
|
```txt
|
|
34
35
|
validating
|
|
35
36
|
lazyr
|
|
37
|
+
loggings
|
|
36
38
|
matplotlib
|
|
37
39
|
numpy
|
|
38
40
|
pandas
|
|
@@ -51,6 +53,19 @@ seaborn
|
|
|
51
53
|
This project falls under the BSD 3-Clause License.
|
|
52
54
|
|
|
53
55
|
## History
|
|
56
|
+
### v0.1.8
|
|
57
|
+
* Improved naming inference in `dp.data(...)` when decorators from `validating` are involved.
|
|
58
|
+
* Refined module exports around artist helpers to make wildcard-style artist imports behave consistently.
|
|
59
|
+
* Refactored plot-setting fallback behavior to use `dp.defaults` as the global source of defaults.
|
|
60
|
+
* Enhanced `get_setting(...)` (including overload/type-hint coverage) so default values align with the corresponding setting types, and dict defaults are handled more safely.
|
|
61
|
+
* Removed legacy `set_default()` usage and simplified setting application paths across artists/containers.
|
|
62
|
+
* Added dependency `loggings` and updated warning emission in figure setting flows.
|
|
63
|
+
|
|
64
|
+
### v0.1.7
|
|
65
|
+
* `dp.data(...)` can accept `PlotDataSet` objects now.
|
|
66
|
+
* `FigWrapper.__enter__()` now returns a copy safely via `_entered_copy`.
|
|
67
|
+
* Internal maintenance and stability refinements.
|
|
68
|
+
|
|
54
69
|
### v0.1.6
|
|
55
70
|
* New method `PlotDataSet.scatter()` to draw true scatter charts while keeping `PlotDataSet.plot()` as line chart behavior.
|
|
56
71
|
* Improved automatic label inference for `dp.data(...)`, plotting labels, and x-axis labels in interactive contexts.
|
|
@@ -10,6 +10,7 @@ $ pip install dataplot
|
|
|
10
10
|
```txt
|
|
11
11
|
validating
|
|
12
12
|
lazyr
|
|
13
|
+
loggings
|
|
13
14
|
matplotlib
|
|
14
15
|
numpy
|
|
15
16
|
pandas
|
|
@@ -28,6 +29,19 @@ seaborn
|
|
|
28
29
|
This project falls under the BSD 3-Clause License.
|
|
29
30
|
|
|
30
31
|
## History
|
|
32
|
+
### v0.1.8
|
|
33
|
+
* Improved naming inference in `dp.data(...)` when decorators from `validating` are involved.
|
|
34
|
+
* Refined module exports around artist helpers to make wildcard-style artist imports behave consistently.
|
|
35
|
+
* Refactored plot-setting fallback behavior to use `dp.defaults` as the global source of defaults.
|
|
36
|
+
* Enhanced `get_setting(...)` (including overload/type-hint coverage) so default values align with the corresponding setting types, and dict defaults are handled more safely.
|
|
37
|
+
* Removed legacy `set_default()` usage and simplified setting application paths across artists/containers.
|
|
38
|
+
* Added dependency `loggings` and updated warning emission in figure setting flows.
|
|
39
|
+
|
|
40
|
+
### v0.1.7
|
|
41
|
+
* `dp.data(...)` can accept `PlotDataSet` objects now.
|
|
42
|
+
* `FigWrapper.__enter__()` now returns a copy safely via `_entered_copy`.
|
|
43
|
+
* Internal maintenance and stability refinements.
|
|
44
|
+
|
|
31
45
|
### v0.1.6
|
|
32
46
|
* New method `PlotDataSet.scatter()` to draw true scatter charts while keeping `PlotDataSet.plot()` as line chart behavior.
|
|
33
47
|
* Improved automatic label inference for `dp.data(...)`, plotting labels, and x-axis labels in interactive contexts.
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"outputs": [],
|
|
127
127
|
"source": [
|
|
128
128
|
"dp.figure(\n",
|
|
129
|
-
"
|
|
129
|
+
" a0, a1, a2, a3, a4, a5, title=\"Figure Collections\", subplots_adjust={\"top\": 0.92}\n",
|
|
130
130
|
")"
|
|
131
131
|
]
|
|
132
132
|
}
|
|
@@ -4,10 +4,11 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dataplot"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"validating",
|
|
10
10
|
"lazyr",
|
|
11
|
+
"loggings",
|
|
11
12
|
"matplotlib",
|
|
12
13
|
"numpy",
|
|
13
14
|
"pandas",
|
|
@@ -28,7 +29,7 @@ license-files = ["LICENSE"]
|
|
|
28
29
|
keywords = ["plot"]
|
|
29
30
|
classifiers = [
|
|
30
31
|
"Programming Language :: Python :: 3",
|
|
31
|
-
"Programming Language :: Python :: 3.
|
|
32
|
+
"Programming Language :: Python :: 3.13"
|
|
32
33
|
]
|
|
33
34
|
|
|
34
35
|
[project.urls]
|
|
@@ -45,7 +45,7 @@ class CorrMap(Plotter):
|
|
|
45
45
|
arrays.append(self.data)
|
|
46
46
|
labels.append(self.label)
|
|
47
47
|
if __multi_is_final__:
|
|
48
|
-
ax.
|
|
48
|
+
ax.set_axes(title=ax.get_setting("title", "Correlation Heatmap"))
|
|
49
49
|
ax.load(self.settings)
|
|
50
50
|
self.__plot(ax, arrays, labels)
|
|
51
51
|
return arrays, labels
|
|
@@ -41,11 +41,11 @@ class Histogram(Plotter):
|
|
|
41
41
|
__multi_prev_returned__: Optional[tuple[str, np.ndarray]] = None,
|
|
42
42
|
__multi_is_final__: bool = True,
|
|
43
43
|
) -> list[float]:
|
|
44
|
-
ax.
|
|
45
|
-
title="Histogram",
|
|
46
|
-
alpha=0.8,
|
|
47
|
-
xlabel="value",
|
|
48
|
-
ylabel="density" if self.density else "count",
|
|
44
|
+
ax.set_axes(
|
|
45
|
+
title=ax.get_setting("title", "Histogram"),
|
|
46
|
+
alpha=ax.get_setting("alpha", 0.8),
|
|
47
|
+
xlabel=ax.get_setting("xlabel", "value"),
|
|
48
|
+
ylabel=ax.get_setting("ylabel", "density" if self.density else "count"),
|
|
49
49
|
)
|
|
50
50
|
ax.load(self.settings)
|
|
51
51
|
if __multi_prev_returned__ is None:
|
|
@@ -27,10 +27,10 @@ class KSPlot(QQPlot):
|
|
|
27
27
|
"""
|
|
28
28
|
|
|
29
29
|
def paint(self, ax: "AxesWrapper", **_) -> None:
|
|
30
|
-
ax.
|
|
31
|
-
title="Kolmogorov-Smirnov Plot",
|
|
32
|
-
xlabel="value",
|
|
33
|
-
ylabel="cummulative probability",
|
|
30
|
+
ax.set_axes(
|
|
31
|
+
title=ax.get_setting("title", "Kolmogorov-Smirnov Plot"),
|
|
32
|
+
xlabel=ax.get_setting("xlabel", "value"),
|
|
33
|
+
ylabel=ax.get_setting("ylabel", "cummulative probability"),
|
|
34
34
|
)
|
|
35
35
|
ax.load(self.settings)
|
|
36
36
|
self.__plot(ax)
|
|
@@ -26,10 +26,10 @@ class PPPlot(QQPlot):
|
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
def paint(self, ax: "AxesWrapper", **_) -> None:
|
|
29
|
-
ax.
|
|
30
|
-
title="Probability-Probability Plot",
|
|
31
|
-
xlabel="cumulative probility",
|
|
32
|
-
ylabel="cumulative probility",
|
|
29
|
+
ax.set_axes(
|
|
30
|
+
title=ax.get_setting("title", "Probability-Probability Plot"),
|
|
31
|
+
xlabel=ax.get_setting("xlabel", "cumulative probility"),
|
|
32
|
+
ylabel=ax.get_setting("ylabel", "cumulative probility"),
|
|
33
33
|
)
|
|
34
34
|
ax.load(self.settings)
|
|
35
35
|
self.__plot(ax)
|
|
@@ -37,10 +37,10 @@ class QQPlot(Plotter):
|
|
|
37
37
|
fmt: str
|
|
38
38
|
|
|
39
39
|
def paint(self, ax: "AxesWrapper", **_) -> None:
|
|
40
|
-
ax.
|
|
41
|
-
title="Quantile-Quantile Plot",
|
|
42
|
-
xlabel="quantiles",
|
|
43
|
-
ylabel="quantiles",
|
|
40
|
+
ax.set_axes(
|
|
41
|
+
title=ax.get_setting("title", "Quantile-Quantile Plot"),
|
|
42
|
+
xlabel=ax.get_setting("xlabel", "quantiles"),
|
|
43
|
+
ylabel=ax.get_setting("ylabel", "quantiles"),
|
|
44
44
|
)
|
|
45
45
|
ax.load(self.settings)
|
|
46
46
|
self.__plot(ax)
|
|
@@ -6,17 +6,17 @@ NOTE: this module is private. All functions and objects are available in the mai
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Self, Unpack
|
|
9
|
+
from typing import TYPE_CHECKING, Self, Unpack
|
|
11
10
|
|
|
11
|
+
import loggings
|
|
12
12
|
import matplotlib.pyplot as plt
|
|
13
13
|
import numpy as np
|
|
14
14
|
from matplotlib.figure import Figure
|
|
15
15
|
from matplotlib.pyplot import Axes
|
|
16
16
|
from validating import attr, dataclass
|
|
17
17
|
|
|
18
|
-
from ._typing import AxesSettingDict, FigureSettingDict
|
|
19
|
-
from .setting import PlotSettable
|
|
18
|
+
from ._typing import AxesSettingDict, FigureSettingDict
|
|
19
|
+
from .setting import PlotSettable, defaults
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from .artist import Artist
|
|
@@ -74,13 +74,19 @@ class AxesWrapper(PlotSettable):
|
|
|
74
74
|
self.ax.set_xlabel(self.settings.xlabel)
|
|
75
75
|
self.ax.set_ylabel(self.settings.ylabel)
|
|
76
76
|
if len(self.ax.get_legend_handles_labels()[0]):
|
|
77
|
-
self.ax.legend(loc=self.
|
|
78
|
-
if self.get_setting("grid"
|
|
79
|
-
alpha = self.get_setting("alpha"
|
|
80
|
-
|
|
77
|
+
self.ax.legend(loc=self.get_setting("legend_loc"))
|
|
78
|
+
if self.get_setting("grid"):
|
|
79
|
+
alpha = self.get_setting("alpha")
|
|
80
|
+
default_grid_alpha = (
|
|
81
|
+
alpha / 2 if defaults.grid_alpha is None else defaults.grid_alpha
|
|
82
|
+
)
|
|
83
|
+
self.ax.grid(alpha=self.get_setting("grid_alpha", default_grid_alpha))
|
|
81
84
|
else:
|
|
82
85
|
self.ax.grid(False)
|
|
83
|
-
self.ax.set_title(
|
|
86
|
+
self.ax.set_title(
|
|
87
|
+
self.settings.title,
|
|
88
|
+
**(self.get_setting("fontdict") or {}),
|
|
89
|
+
)
|
|
84
90
|
|
|
85
91
|
|
|
86
92
|
@dataclass(validate_methods=True)
|
|
@@ -96,10 +102,16 @@ class FigWrapper(PlotSettable):
|
|
|
96
102
|
nrows: int = 1
|
|
97
103
|
ncols: int = 1
|
|
98
104
|
active: bool = attr(repr=False, default=True)
|
|
99
|
-
entered: bool = attr(init=False, repr=False, default=False)
|
|
100
105
|
fig: Figure = attr(init=False, repr=False)
|
|
101
106
|
axes: list[AxesWrapper] = attr(init=False, repr=False)
|
|
102
107
|
artists: "list[Artist]" = attr(default_factory=list, init=False, repr=False)
|
|
108
|
+
_copy: "FigWrapper | None" = attr(init=False, repr=False, default=None)
|
|
109
|
+
|
|
110
|
+
def __repr__(self) -> str:
|
|
111
|
+
with self as fig:
|
|
112
|
+
for artist, ax in zip(self.artists, fig.axes[: len(self.artists)]):
|
|
113
|
+
artist.paint(ax)
|
|
114
|
+
return f"<{self.__class__.__name__} {self.nrows}x{self.ncols}>"
|
|
103
115
|
|
|
104
116
|
def __enter__(self) -> Self:
|
|
105
117
|
"""
|
|
@@ -111,59 +123,61 @@ class FigWrapper(PlotSettable):
|
|
|
111
123
|
An instance of self.
|
|
112
124
|
|
|
113
125
|
"""
|
|
114
|
-
if not
|
|
115
|
-
return self
|
|
116
|
-
if self.entered:
|
|
126
|
+
if self._copy is not None:
|
|
117
127
|
raise DoubleEnteredError(
|
|
118
|
-
f"
|
|
119
|
-
"please do all the operations in one single context manager"
|
|
128
|
+
f"calling {self.__class__.__name__}.__enter__() for twice"
|
|
120
129
|
)
|
|
121
130
|
|
|
122
|
-
self.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)
|
|
128
|
-
plt.
|
|
129
|
-
|
|
130
|
-
self.
|
|
131
|
-
|
|
132
|
-
return self
|
|
131
|
+
figw = self.copy()
|
|
132
|
+
if not figw.active:
|
|
133
|
+
self._copy = figw
|
|
134
|
+
return figw
|
|
135
|
+
|
|
136
|
+
plt.style.use(figw.get_setting("style"))
|
|
137
|
+
figw.fig, axes = plt.subplots(figw.nrows, figw.ncols)
|
|
138
|
+
figw.axes = [AxesWrapper(x) for x in np.reshape(axes, -1)]
|
|
139
|
+
self._copy = figw
|
|
140
|
+
return figw
|
|
133
141
|
|
|
134
142
|
def __exit__(self, *args) -> None:
|
|
135
143
|
"""
|
|
136
144
|
Set various properties for the figure and paint it.
|
|
137
145
|
|
|
138
146
|
"""
|
|
139
|
-
|
|
147
|
+
figw = self._copy
|
|
148
|
+
if figw is None:
|
|
149
|
+
raise NotEnteredError(
|
|
150
|
+
f"calling {self.__class__.__name__}.__exit__(...) before entering"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if not figw.active:
|
|
154
|
+
self._copy = None
|
|
140
155
|
return
|
|
141
156
|
|
|
142
|
-
|
|
143
|
-
|
|
157
|
+
fontdict = figw.get_setting("fontdict") or {}
|
|
158
|
+
if len(figw.axes) > 1:
|
|
159
|
+
figw.fig.suptitle(figw.settings.title, **fontdict)
|
|
144
160
|
else:
|
|
145
|
-
|
|
161
|
+
figw.axes[0].ax.set_title(figw.settings.title, **fontdict)
|
|
146
162
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
default_figsize = (
|
|
164
|
+
defaults.figsize[0] * figw.ncols if defaults.figsize else 10 * figw.ncols,
|
|
165
|
+
defaults.figsize[1] * figw.nrows if defaults.figsize else 5 * figw.nrows,
|
|
166
|
+
)
|
|
167
|
+
figw.fig.set_size_inches(*figw.get_setting("figsize", default_figsize))
|
|
168
|
+
figw.fig.subplots_adjust(**(figw.get_setting("subplots_adjust") or {}))
|
|
169
|
+
figw.fig.set_dpi(figw.get_setting("dpi"))
|
|
150
170
|
|
|
151
|
-
for ax in
|
|
171
|
+
for ax in figw.axes:
|
|
152
172
|
ax.exit()
|
|
153
173
|
if not ax.ax.has_data():
|
|
154
|
-
|
|
174
|
+
figw.fig.delaxes(ax.ax)
|
|
155
175
|
|
|
156
176
|
plt.show()
|
|
157
|
-
plt.close(
|
|
177
|
+
plt.close(figw.fig)
|
|
158
178
|
plt.style.use("default")
|
|
159
179
|
|
|
160
|
-
self.
|
|
161
|
-
|
|
162
|
-
def __repr__(self) -> str:
|
|
163
|
-
with self as fig:
|
|
164
|
-
for artist, ax in zip(self.artists, fig.axes[: len(self.artists)]):
|
|
165
|
-
artist.paint(ax)
|
|
166
|
-
return f"<{self.__class__.__name__}(nrows={self.nrows}, ncols={self.ncols})>"
|
|
180
|
+
self._copy = None
|
|
167
181
|
|
|
168
182
|
def set_figure(self, **kwargs: Unpack[FigureSettingDict]) -> None:
|
|
169
183
|
"""
|
|
@@ -173,7 +187,7 @@ class FigWrapper(PlotSettable):
|
|
|
173
187
|
----------
|
|
174
188
|
title : str, optional
|
|
175
189
|
Title of figure. Please note that there's another parameter with
|
|
176
|
-
the same name in `.
|
|
190
|
+
the same name in `.set_axes()`.
|
|
177
191
|
dpi : float, optional
|
|
178
192
|
Sets the resolution of figure in dots-per-inch.
|
|
179
193
|
style : StyleName, optional
|
|
@@ -188,16 +202,33 @@ class FigWrapper(PlotSettable):
|
|
|
188
202
|
top, wspace, and hspace. See `SubplotDict` for more details.
|
|
189
203
|
|
|
190
204
|
"""
|
|
191
|
-
self.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if self.entered and key == "style":
|
|
195
|
-
logging.warning(
|
|
196
|
-
"setting the '%s' of a figure has no effect unless it's done "
|
|
205
|
+
if "style" in kwargs and (self._copy is not None):
|
|
206
|
+
loggings.warning(
|
|
207
|
+
"setting the 'style' of a figure has no effect unless it's done "
|
|
197
208
|
"before invoking context manager",
|
|
198
|
-
key,
|
|
199
209
|
)
|
|
210
|
+
self._set(inplace=True, **kwargs)
|
|
211
|
+
|
|
212
|
+
def copy(self) -> Self:
|
|
213
|
+
"""Get a copy of self."""
|
|
214
|
+
obj = self.customize(
|
|
215
|
+
self.__class__,
|
|
216
|
+
nrows=self.nrows,
|
|
217
|
+
ncols=self.ncols,
|
|
218
|
+
active=self.active,
|
|
219
|
+
)
|
|
220
|
+
obj.artists = self.artists
|
|
221
|
+
obj._copy = None
|
|
222
|
+
return obj
|
|
223
|
+
|
|
224
|
+
def set_artists(self, *artist: "Artist") -> None:
|
|
225
|
+
"""Set artists."""
|
|
226
|
+
self.artists = list(artist)
|
|
200
227
|
|
|
201
228
|
|
|
202
229
|
class DoubleEnteredError(Exception):
|
|
203
230
|
"""Raised when entering a Figwrapper for twice."""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class NotEnteredError(Exception):
|
|
234
|
+
"""Raised when exiting a Figwrapper that is not entered yet."""
|
|
@@ -10,9 +10,11 @@ import dis
|
|
|
10
10
|
import re
|
|
11
11
|
import sys
|
|
12
12
|
from math import ceil, sqrt
|
|
13
|
+
from types import FrameType
|
|
13
14
|
from typing import TYPE_CHECKING, Any, Optional, Unpack
|
|
14
15
|
|
|
15
16
|
import numpy as np
|
|
17
|
+
from validating import validate
|
|
16
18
|
|
|
17
19
|
from ._typing import FigureSettingDict
|
|
18
20
|
from .container import FigWrapper
|
|
@@ -25,10 +27,32 @@ if TYPE_CHECKING:
|
|
|
25
27
|
__all__ = ["data", "figure"]
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
def
|
|
30
|
+
def _find_user_frame(start_depth: int = 1) -> FrameType | None:
|
|
31
|
+
"""
|
|
32
|
+
Find the nearest frame belonging to user code.
|
|
33
|
+
|
|
34
|
+
This skips internal dataplot core frames and validating wrappers so helper
|
|
35
|
+
functions keep working when `data()`/`figure()` are decorated.
|
|
36
|
+
"""
|
|
29
37
|
try:
|
|
30
|
-
|
|
38
|
+
current = sys._getframe(start_depth)
|
|
31
39
|
except ValueError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
while current is not None:
|
|
44
|
+
module_name = str(current.f_globals.get("__name__", ""))
|
|
45
|
+
if module_name != __name__ and not module_name.startswith("validating"):
|
|
46
|
+
return current
|
|
47
|
+
current = current.f_back
|
|
48
|
+
return None
|
|
49
|
+
finally:
|
|
50
|
+
del current
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _infer_var_names(*values: Any) -> list[Optional[str]]:
|
|
54
|
+
search_frame = _find_user_frame(start_depth=2)
|
|
55
|
+
if search_frame is None:
|
|
32
56
|
return [None] * len(values)
|
|
33
57
|
|
|
34
58
|
labels: list[Optional[str]] = []
|
|
@@ -51,9 +75,8 @@ def _infer_var_names(*values: Any) -> list[Optional[str]]:
|
|
|
51
75
|
|
|
52
76
|
def _infer_assigned_name() -> Optional[str]:
|
|
53
77
|
"""Try inferring assignment target name from call-site."""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
except ValueError:
|
|
78
|
+
frame = _find_user_frame(start_depth=2)
|
|
79
|
+
if frame is None:
|
|
57
80
|
return None
|
|
58
81
|
|
|
59
82
|
# Bytecode inspection works in REPL/notebook contexts where source code file
|
|
@@ -101,6 +124,7 @@ def _infer_assigned_name() -> Optional[str]:
|
|
|
101
124
|
return m.group(1) if m else None
|
|
102
125
|
|
|
103
126
|
|
|
127
|
+
@validate
|
|
104
128
|
def data(*x: Any, label: Optional[str | list[str]] = None) -> PlotDataSet:
|
|
105
129
|
"""
|
|
106
130
|
Initializes a dataset interface which provides methods for mathematical
|
|
@@ -126,21 +150,43 @@ def data(*x: Any, label: Optional[str | list[str]] = None) -> PlotDataSet:
|
|
|
126
150
|
if not x:
|
|
127
151
|
raise ValueError("at least one dataset should be provided")
|
|
128
152
|
|
|
129
|
-
|
|
153
|
+
expanded_data: list[Any] = []
|
|
154
|
+
expanded_names: list[Optional[str]] = []
|
|
155
|
+
inferred_names = _infer_var_names(*x)
|
|
156
|
+
for i, value in enumerate(x):
|
|
157
|
+
if isinstance(value, PlotDataSets):
|
|
158
|
+
expanded_data.extend(value.__multiobjects__)
|
|
159
|
+
expanded_names.extend([None] * len(value.__multiobjects__))
|
|
160
|
+
else:
|
|
161
|
+
expanded_data.append(value)
|
|
162
|
+
expanded_names.append(inferred_names[i])
|
|
163
|
+
|
|
164
|
+
normalized_data: list[np.ndarray] = []
|
|
165
|
+
for value in expanded_data:
|
|
166
|
+
if isinstance(value, PlotDataSet):
|
|
167
|
+
normalized_data.append(np.array(value.data))
|
|
168
|
+
else:
|
|
169
|
+
normalized_data.append(np.array(value))
|
|
170
|
+
|
|
171
|
+
if len(expanded_data) > 1:
|
|
130
172
|
if label is None:
|
|
131
|
-
label = [
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
173
|
+
label = []
|
|
174
|
+
for i, (d, inferred_name) in enumerate(
|
|
175
|
+
zip(expanded_data, expanded_names), start=1
|
|
176
|
+
):
|
|
177
|
+
if isinstance(d, PlotDataSet):
|
|
178
|
+
label.append(d.formatted_label())
|
|
179
|
+
else:
|
|
180
|
+
label.append(
|
|
181
|
+
inferred_name if inferred_name is not None else f"x{i}"
|
|
182
|
+
)
|
|
135
183
|
elif isinstance(label, str):
|
|
136
184
|
raise ValueError(
|
|
137
185
|
"for multiple datasets, please provide labels as a list of strings"
|
|
138
186
|
)
|
|
139
|
-
elif len(label) != len(
|
|
140
|
-
raise ValueError(
|
|
141
|
-
|
|
142
|
-
)
|
|
143
|
-
datas = [PlotDataSet(np.array(d), lb) for d, lb in zip(x, label)]
|
|
187
|
+
elif len(label) != len(expanded_data):
|
|
188
|
+
raise ValueError(f"expected {len(expanded_data)} labels, got {len(label)}")
|
|
189
|
+
datas = [PlotDataSet(d, lb) for d, lb in zip(normalized_data, label)]
|
|
144
190
|
return PlotDataSets(*datas)
|
|
145
191
|
|
|
146
192
|
if isinstance(label, list):
|
|
@@ -149,12 +195,18 @@ def data(*x: Any, label: Optional[str | list[str]] = None) -> PlotDataSet:
|
|
|
149
195
|
"the data has only one dimension"
|
|
150
196
|
)
|
|
151
197
|
if label is None:
|
|
152
|
-
|
|
153
|
-
|
|
198
|
+
original_label = (
|
|
199
|
+
expanded_data[0].label
|
|
200
|
+
if isinstance(expanded_data[0], PlotDataSet)
|
|
201
|
+
else None
|
|
202
|
+
)
|
|
203
|
+
label = original_label or _infer_assigned_name() or expanded_names[0] or "x1"
|
|
204
|
+
return PlotDataSet(normalized_data[0], label=label)
|
|
154
205
|
|
|
155
206
|
|
|
207
|
+
@validate
|
|
156
208
|
def figure(
|
|
157
|
-
|
|
209
|
+
*artists: "Artist",
|
|
158
210
|
nrows: int | None = None,
|
|
159
211
|
ncols: int | None = None,
|
|
160
212
|
**kwargs: Unpack[FigureSettingDict],
|
|
@@ -165,8 +217,8 @@ def figure(
|
|
|
165
217
|
|
|
166
218
|
Parameters
|
|
167
219
|
----------
|
|
168
|
-
artist : Artist
|
|
169
|
-
|
|
220
|
+
*artist : Artist
|
|
221
|
+
List of artists.
|
|
170
222
|
nrows : int, optional
|
|
171
223
|
Determines how many subplots can be arranged vertically in the figure,
|
|
172
224
|
If None, will be automatically set according to ``len(artist)``. By default
|
|
@@ -184,9 +236,7 @@ def figure(
|
|
|
184
236
|
A wrapper of figure.
|
|
185
237
|
|
|
186
238
|
"""
|
|
187
|
-
|
|
188
|
-
artist = [artist]
|
|
189
|
-
len_a = max(len(artist), 1)
|
|
239
|
+
len_a = max(len(artists), 1)
|
|
190
240
|
if nrows is None and ncols is None:
|
|
191
241
|
ncols = int(sqrt(len_a))
|
|
192
242
|
nrows = ceil(len_a / ncols)
|
|
@@ -198,5 +248,5 @@ def figure(
|
|
|
198
248
|
nrows = ceil(len_a / ncols)
|
|
199
249
|
figw = FigWrapper(nrows=nrows, ncols=ncols)
|
|
200
250
|
figw.set_figure(**kwargs)
|
|
201
|
-
figw.artists
|
|
251
|
+
figw.set_artists(*artists)
|
|
202
252
|
return figw
|
|
@@ -131,7 +131,7 @@ class PlotDataSet(PlotSettable, metaclass=ABCMeta):
|
|
|
131
131
|
A string indicating the data label and the plot settings.
|
|
132
132
|
|
|
133
133
|
"""
|
|
134
|
-
not_none = self.settings.
|
|
134
|
+
not_none = self.settings._repr_changes()
|
|
135
135
|
return f"{self.formatted_label()}{': ' if not_none else ''}{not_none}"
|
|
136
136
|
|
|
137
137
|
def __getitem__(self, __key: int) -> Self | Any:
|
|
@@ -7,12 +7,11 @@ NOTE: this module is private. All functions and objects are available in the mai
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from dataclasses import asdict
|
|
10
|
-
from typing import Any, Optional, Self, Unpack
|
|
10
|
+
from typing import Any, Literal, Optional, Self, Unpack, overload
|
|
11
11
|
|
|
12
12
|
from validating import attr, dataclass
|
|
13
13
|
|
|
14
14
|
from ._typing import (
|
|
15
|
-
DefaultVar,
|
|
16
15
|
FontDict,
|
|
17
16
|
PlotSettableVar,
|
|
18
17
|
SettingDict,
|
|
@@ -21,7 +20,7 @@ from ._typing import (
|
|
|
21
20
|
SubplotDict,
|
|
22
21
|
)
|
|
23
22
|
|
|
24
|
-
__all__ = ["PlotSettings", "PlotSettable"]
|
|
23
|
+
__all__ = ["PlotSettings", "PlotSettable", "defaults"]
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
@dataclass(validate_methods=True)
|
|
@@ -32,7 +31,7 @@ class PlotSettings:
|
|
|
32
31
|
xlabel: Optional[str] = None
|
|
33
32
|
ylabel: Optional[str] = None
|
|
34
33
|
alpha: Optional[float] = None
|
|
35
|
-
dpi: Optional[float] = None
|
|
34
|
+
dpi: Optional[int | float] = None
|
|
36
35
|
grid: Optional[bool] = None
|
|
37
36
|
grid_alpha: Optional[float] = None
|
|
38
37
|
style: Optional[StyleName] = None
|
|
@@ -41,16 +40,87 @@ class PlotSettings:
|
|
|
41
40
|
legend_loc: Optional[str] = None
|
|
42
41
|
subplots_adjust: Optional[SubplotDict] = None
|
|
43
42
|
|
|
43
|
+
@overload
|
|
44
|
+
def __getitem__(
|
|
45
|
+
self, __key: Literal["title", "xlabel", "ylabel"]
|
|
46
|
+
) -> Optional[str]: ...
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
def __getitem__(self, __key: Literal["alpha", "grid_alpha"]) -> Optional[float]: ...
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def __getitem__(self, __key: Literal["dpi"]) -> Optional[int | float]: ...
|
|
53
|
+
|
|
54
|
+
@overload
|
|
55
|
+
def __getitem__(self, __key: Literal["grid"]) -> Optional[bool]: ...
|
|
56
|
+
|
|
57
|
+
@overload
|
|
58
|
+
def __getitem__(self, __key: Literal["style"]) -> Optional[StyleName]: ...
|
|
59
|
+
|
|
60
|
+
@overload
|
|
61
|
+
def __getitem__(self, __key: Literal["figsize"]) -> Optional[tuple[int, int]]: ...
|
|
62
|
+
|
|
63
|
+
@overload
|
|
64
|
+
def __getitem__(self, __key: Literal["fontdict"]) -> Optional[FontDict]: ...
|
|
65
|
+
|
|
66
|
+
@overload
|
|
67
|
+
def __getitem__(self, __key: Literal["legend_loc"]) -> Optional[str]: ...
|
|
68
|
+
|
|
69
|
+
@overload
|
|
70
|
+
def __getitem__(
|
|
71
|
+
self, __key: Literal["subplots_adjust"]
|
|
72
|
+
) -> Optional[SubplotDict]: ...
|
|
73
|
+
|
|
44
74
|
def __getitem__(self, __key: SettingKey) -> Any:
|
|
45
75
|
return getattr(self, __key)
|
|
46
76
|
|
|
77
|
+
@overload
|
|
78
|
+
def __setitem__(
|
|
79
|
+
self,
|
|
80
|
+
__key: Literal["title", "xlabel", "ylabel", "legend_loc"],
|
|
81
|
+
__value: Optional[str],
|
|
82
|
+
) -> None: ...
|
|
83
|
+
|
|
84
|
+
@overload
|
|
85
|
+
def __setitem__(
|
|
86
|
+
self, __key: Literal["alpha", "grid_alpha"], __value: Optional[float]
|
|
87
|
+
) -> None: ...
|
|
88
|
+
|
|
89
|
+
@overload
|
|
90
|
+
def __setitem__(
|
|
91
|
+
self, __key: Literal["dpi"], __value: Optional[int | float]
|
|
92
|
+
) -> None: ...
|
|
93
|
+
|
|
94
|
+
@overload
|
|
95
|
+
def __setitem__(self, __key: Literal["grid"], __value: Optional[bool]) -> None: ...
|
|
96
|
+
|
|
97
|
+
@overload
|
|
98
|
+
def __setitem__(
|
|
99
|
+
self, __key: Literal["style"], __value: Optional[StyleName]
|
|
100
|
+
) -> None: ...
|
|
101
|
+
|
|
102
|
+
@overload
|
|
103
|
+
def __setitem__(
|
|
104
|
+
self, __key: Literal["figsize"], __value: Optional[tuple[int, int]]
|
|
105
|
+
) -> None: ...
|
|
106
|
+
|
|
107
|
+
@overload
|
|
108
|
+
def __setitem__(
|
|
109
|
+
self, __key: Literal["fontdict"], __value: Optional[FontDict]
|
|
110
|
+
) -> None: ...
|
|
111
|
+
|
|
112
|
+
@overload
|
|
113
|
+
def __setitem__(
|
|
114
|
+
self, __key: Literal["subplots_adjust"], __value: Optional[SubplotDict]
|
|
115
|
+
) -> None: ...
|
|
116
|
+
|
|
47
117
|
def __setitem__(self, __key: SettingKey, __value: Any) -> None:
|
|
48
118
|
setattr(self, __key, __value)
|
|
49
119
|
|
|
50
120
|
def __repr__(self) -> str:
|
|
51
|
-
return self.__class__.__name__ + "(" + self.
|
|
121
|
+
return self.__class__.__name__ + "(" + self._repr_changes() + ")"
|
|
52
122
|
|
|
53
|
-
def
|
|
123
|
+
def _repr_changes(self) -> str:
|
|
54
124
|
"""
|
|
55
125
|
Returns a string representation of attributes with not-None values.
|
|
56
126
|
|
|
@@ -70,11 +140,23 @@ class PlotSettings:
|
|
|
70
140
|
Returns
|
|
71
141
|
-------
|
|
72
142
|
list[SettingKey]
|
|
73
|
-
|
|
143
|
+
List of keys.
|
|
74
144
|
|
|
75
145
|
"""
|
|
76
146
|
return getattr(self, "__match_args__")
|
|
77
147
|
|
|
148
|
+
def values(self) -> list:
|
|
149
|
+
"""
|
|
150
|
+
Values of settings.
|
|
151
|
+
|
|
152
|
+
Returns
|
|
153
|
+
-------
|
|
154
|
+
list
|
|
155
|
+
List of values.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
return [self[x] for x in self.keys()]
|
|
159
|
+
|
|
78
160
|
def reset(self) -> None:
|
|
79
161
|
"""
|
|
80
162
|
Reset all the settings to None.
|
|
@@ -84,6 +166,18 @@ class PlotSettings:
|
|
|
84
166
|
self[k] = None
|
|
85
167
|
|
|
86
168
|
|
|
169
|
+
defaults = PlotSettings(
|
|
170
|
+
alpha=1.0,
|
|
171
|
+
dpi=100,
|
|
172
|
+
grid=True,
|
|
173
|
+
grid_alpha=0.5,
|
|
174
|
+
style="seaborn-v0_8-darkgrid",
|
|
175
|
+
figsize=(10, 5),
|
|
176
|
+
fontdict={"fontsize": "x-large"},
|
|
177
|
+
subplots_adjust={"hspace": 0.5},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
87
181
|
@dataclass(init=False)
|
|
88
182
|
class PlotSettable:
|
|
89
183
|
"""Contains an attribute of plot settings, and provides methods for
|
|
@@ -101,7 +195,6 @@ class PlotSettable:
|
|
|
101
195
|
for k, v in kwargs.items():
|
|
102
196
|
if v is None or k not in keys:
|
|
103
197
|
continue
|
|
104
|
-
obj.setting_check(k, v)
|
|
105
198
|
if isinstance(v, dict) and isinstance(d := obj.settings[k], dict):
|
|
106
199
|
d.update(v)
|
|
107
200
|
else:
|
|
@@ -109,38 +202,6 @@ class PlotSettable:
|
|
|
109
202
|
if not inplace:
|
|
110
203
|
return obj
|
|
111
204
|
|
|
112
|
-
def setting_check(self, key: SettingKey, value: Any) -> None:
|
|
113
|
-
"""
|
|
114
|
-
Checks if a new setting is legal.
|
|
115
|
-
|
|
116
|
-
Parameters
|
|
117
|
-
----------
|
|
118
|
-
key : SettingKey
|
|
119
|
-
Key of the setting.
|
|
120
|
-
value : Any
|
|
121
|
-
Value of the setting.
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
def set_default(self, **kwargs: Unpack[SettingDict]) -> None:
|
|
126
|
-
"""
|
|
127
|
-
Sets the default settings.
|
|
128
|
-
|
|
129
|
-
Parameters
|
|
130
|
-
----------
|
|
131
|
-
**kwargs : Unpack[SettingDict]
|
|
132
|
-
Specifies the settings.
|
|
133
|
-
|
|
134
|
-
"""
|
|
135
|
-
keys = self.settings.keys()
|
|
136
|
-
for k, v in kwargs.items():
|
|
137
|
-
if k not in keys:
|
|
138
|
-
continue
|
|
139
|
-
if self.settings[k] is None:
|
|
140
|
-
self.settings[k] = v
|
|
141
|
-
elif isinstance(d := self.settings[k], dict):
|
|
142
|
-
self.settings[k] = {**v, **d}
|
|
143
|
-
|
|
144
205
|
def load(self, settings: "PlotSettings | SettingDict") -> None:
|
|
145
206
|
"""
|
|
146
207
|
Load in the settings.
|
|
@@ -155,9 +216,49 @@ class PlotSettable:
|
|
|
155
216
|
settings = asdict(settings)
|
|
156
217
|
self._set(inplace=True, **settings)
|
|
157
218
|
|
|
219
|
+
@overload
|
|
220
|
+
def get_setting(
|
|
221
|
+
self,
|
|
222
|
+
key: Literal["title", "xlabel", "ylabel", "legend_loc"],
|
|
223
|
+
default: Optional[str] = None,
|
|
224
|
+
) -> Optional[str]: ...
|
|
225
|
+
|
|
226
|
+
@overload
|
|
158
227
|
def get_setting(
|
|
159
|
-
self, key:
|
|
160
|
-
) ->
|
|
228
|
+
self, key: Literal["alpha", "grid_alpha"], default: Optional[float] = None
|
|
229
|
+
) -> Optional[float]: ...
|
|
230
|
+
|
|
231
|
+
@overload
|
|
232
|
+
def get_setting(
|
|
233
|
+
self, key: Literal["dpi"], default: Optional[int | float] = None
|
|
234
|
+
) -> Optional[int | float]: ...
|
|
235
|
+
|
|
236
|
+
@overload
|
|
237
|
+
def get_setting(
|
|
238
|
+
self, key: Literal["grid"], default: Optional[bool] = None
|
|
239
|
+
) -> Optional[bool]: ...
|
|
240
|
+
|
|
241
|
+
@overload
|
|
242
|
+
def get_setting(
|
|
243
|
+
self, key: Literal["style"], default: Optional[StyleName] = None
|
|
244
|
+
) -> Optional[StyleName]: ...
|
|
245
|
+
|
|
246
|
+
@overload
|
|
247
|
+
def get_setting(
|
|
248
|
+
self, key: Literal["figsize"], default: Optional[tuple[int, int]] = None
|
|
249
|
+
) -> Optional[tuple[int, int]]: ...
|
|
250
|
+
|
|
251
|
+
@overload
|
|
252
|
+
def get_setting(
|
|
253
|
+
self, key: Literal["fontdict"], default: Optional[FontDict] = None
|
|
254
|
+
) -> Optional[FontDict]: ...
|
|
255
|
+
|
|
256
|
+
@overload
|
|
257
|
+
def get_setting(
|
|
258
|
+
self, key: Literal["subplots_adjust"], default: Optional[SubplotDict] = None
|
|
259
|
+
) -> Optional[SubplotDict]: ...
|
|
260
|
+
|
|
261
|
+
def get_setting(self, key: SettingKey, default: Any = None) -> Any:
|
|
161
262
|
"""
|
|
162
263
|
Returns the value of a setting if it is not None, otherwise returns the
|
|
163
264
|
default value.
|
|
@@ -166,17 +267,24 @@ class PlotSettable:
|
|
|
166
267
|
----------
|
|
167
268
|
key : SettingKey
|
|
168
269
|
Key of the setting.
|
|
169
|
-
default :
|
|
270
|
+
default : Any, optional
|
|
170
271
|
Specifies the default value to be returned if the requested value
|
|
171
|
-
is None,
|
|
272
|
+
is None. If None, falls back to ``defaults[key]``. By default None.
|
|
172
273
|
|
|
173
274
|
Returns
|
|
174
275
|
-------
|
|
175
|
-
|
|
276
|
+
Any
|
|
176
277
|
Value of the setting.
|
|
177
278
|
|
|
178
279
|
"""
|
|
179
|
-
|
|
280
|
+
if default is None:
|
|
281
|
+
default = defaults[key]
|
|
282
|
+
|
|
283
|
+
if (value := self.settings[key]) is None:
|
|
284
|
+
return default
|
|
285
|
+
if isinstance(value, dict) and isinstance(default, dict):
|
|
286
|
+
return {**default, **value}
|
|
287
|
+
return value
|
|
180
288
|
|
|
181
289
|
def customize(
|
|
182
290
|
self, cls: type["PlotSettableVar"], *args, **kwargs
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|