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.
Files changed (26) hide show
  1. {dataplot-0.1.6 → dataplot-0.1.8}/PKG-INFO +17 -2
  2. {dataplot-0.1.6 → dataplot-0.1.8}/README.md +14 -0
  3. {dataplot-0.1.6 → dataplot-0.1.8}/examples/test.ipynb +1 -1
  4. {dataplot-0.1.6 → dataplot-0.1.8}/pyproject.toml +3 -2
  5. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/_typing.py +0 -1
  6. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/corrmap.py +1 -1
  7. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/histogram.py +5 -5
  8. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/ksplot.py +4 -4
  9. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/linechart.py +1 -1
  10. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/ppplot.py +4 -4
  11. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/qqplot.py +4 -4
  12. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/scatterchart.py +1 -1
  13. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/container.py +82 -51
  14. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/core.py +74 -24
  15. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/dataset.py +1 -1
  16. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/setting.py +154 -46
  17. {dataplot-0.1.6 → dataplot-0.1.8}/.github/workflows/python-publish.yml +0 -0
  18. {dataplot-0.1.6 → dataplot-0.1.8}/.gitignore +0 -0
  19. {dataplot-0.1.6 → dataplot-0.1.8}/LICENSE +0 -0
  20. {dataplot-0.1.6 → dataplot-0.1.8}/install.py +0 -0
  21. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/__init__.py +0 -0
  22. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/__init__.py +0 -0
  23. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/artist/base.py +0 -0
  24. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/utils/__init__.py +0 -0
  25. {dataplot-0.1.6 → dataplot-0.1.8}/src/dataplot/utils/math.py +0 -0
  26. {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.6
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.12
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
- " [a0, a1, a2, a3, a4, a5], title=\"Figure Collections\", subplots_adjust={\"top\": 0.92}\n",
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.6"
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.12"
32
+ "Programming Language :: Python :: 3.13"
32
33
  ]
33
34
 
34
35
  [project.urls]
@@ -12,7 +12,6 @@ if TYPE_CHECKING:
12
12
 
13
13
 
14
14
  PlotSettableVar = TypeVar("PlotSettableVar", bound="PlotSettable")
15
- DefaultVar = TypeVar("DefaultVar")
16
15
  StyleName = Literal[
17
16
  "Solarize_Light2",
18
17
  "_classic_test_patch",
@@ -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.set_default(title="Correlation Heatmap")
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.set_default(
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.set_default(
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)
@@ -34,7 +34,7 @@ class LineChart(Plotter):
34
34
  sorted: bool
35
35
 
36
36
  def paint(self, ax: "AxesWrapper", **_) -> None:
37
- ax.set_default(title="Line Chart")
37
+ ax.set_axes(title=ax.get_setting("title", "Line Chart"))
38
38
  ax.load(self.settings)
39
39
  self.__plot(ax)
40
40
 
@@ -26,10 +26,10 @@ class PPPlot(QQPlot):
26
26
  """
27
27
 
28
28
  def paint(self, ax: "AxesWrapper", **_) -> None:
29
- ax.set_default(
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.set_default(
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)
@@ -33,7 +33,7 @@ class ScatterChart(Plotter):
33
33
  sorted: bool
34
34
 
35
35
  def paint(self, ax: "AxesWrapper", **_) -> None:
36
- ax.set_default(title="Scatter Chart")
36
+ ax.set_axes(title=ax.get_setting("title", "Scatter Chart"))
37
37
  ax.load(self.settings)
38
38
  self.__plot(ax)
39
39
 
@@ -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 logging
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, SettingKey
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.settings.legend_loc)
78
- if self.get_setting("grid", True):
79
- alpha = self.get_setting("alpha", 1.0)
80
- self.ax.grid(alpha=self.get_setting("grid_alpha", alpha / 2))
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(self.settings.title, **self.get_setting("fontdict", {}))
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 self.active:
115
- return self
116
- if self.entered:
126
+ if self._copy is not None:
117
127
  raise DoubleEnteredError(
118
- f"can't enter an instance of {self.__class__.__name__!r} for twice; "
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.set_default(
123
- style="seaborn-v0_8-darkgrid",
124
- figsize=(10 * self.ncols, 5 * self.nrows),
125
- subplots_adjust={"hspace": 0.5},
126
- fontdict={"fontsize": "x-large"},
127
- )
128
- plt.style.use(self.settings.style)
129
- self.fig, axes = plt.subplots(self.nrows, self.ncols)
130
- self.axes: list[AxesWrapper] = [AxesWrapper(x) for x in np.reshape(axes, -1)]
131
- self.entered = True
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
- if not self.active:
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
- if len(self.axes) > 1:
143
- self.fig.suptitle(self.settings.title, **self.settings.fontdict)
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
- self.axes[0].ax.set_title(self.settings.title, **self.settings.fontdict)
161
+ figw.axes[0].ax.set_title(figw.settings.title, **fontdict)
146
162
 
147
- self.fig.set_size_inches(*self.settings.figsize)
148
- self.fig.subplots_adjust(**self.settings.subplots_adjust)
149
- self.fig.set_dpi(self.get_setting("dpi", 100))
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 self.axes:
171
+ for ax in figw.axes:
152
172
  ax.exit()
153
173
  if not ax.ax.has_data():
154
- self.fig.delaxes(ax.ax)
174
+ figw.fig.delaxes(ax.ax)
155
175
 
156
176
  plt.show()
157
- plt.close(self.fig)
177
+ plt.close(figw.fig)
158
178
  plt.style.use("default")
159
179
 
160
- self.entered = False
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 `.set_axis()`.
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._set(inplace=True, **kwargs)
192
-
193
- def setting_check(self, key: SettingKey, value: Any) -> None:
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 _infer_var_names(*values: Any) -> list[Optional[str]]:
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
- search_frame = sys._getframe(1)
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
- try:
55
- frame = sys._getframe(2)
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
- if len(x) > 1:
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
- lb if lb is not None else f"x{i}"
133
- for i, lb in enumerate(_infer_var_names(*x), start=1)
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(x):
140
- raise ValueError(
141
- f"label should have the same length as x ({len(x)}), got {len(label)}"
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
- label = _infer_assigned_name() or _infer_var_names(x[0])[0] or "x1"
153
- return PlotDataSet(np.array(x[0]), label=label)
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
- artist: "Artist | list[Artist]",
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 | list[Artist]
169
- Artist or list of artists.
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
- if not isinstance(artist, list):
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 = artist
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.repr_not_none()
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.repr_not_none() + ")"
121
+ return self.__class__.__name__ + "(" + self._repr_changes() + ")"
52
122
 
53
- def repr_not_none(self) -> str:
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
- Keys of the settings.
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: SettingKey, default: Optional[DefaultVar] = None
160
- ) -> "DefaultVar | Any":
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 : DefaultVar, optional
270
+ default : Any, optional
170
271
  Specifies the default value to be returned if the requested value
171
- is None, by default None.
272
+ is None. If None, falls back to ``defaults[key]``. By default None.
172
273
 
173
274
  Returns
174
275
  -------
175
- DefaultVar | Any
276
+ Any
176
277
  Value of the setting.
177
278
 
178
279
  """
179
- return default if (value := self.settings[key]) is None else value
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