dataplot 0.1.6__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.
dataplot/setting.py ADDED
@@ -0,0 +1,232 @@
1
+ """
2
+ Contains dataclasses: PlotSettings, PlotSettable.
3
+
4
+ NOTE: this module is private. All functions and objects are available in the main
5
+ `dataplot` namespace - use that instead.
6
+
7
+ """
8
+
9
+ from dataclasses import asdict
10
+ from typing import Any, Optional, Self, Unpack
11
+
12
+ from validating import attr, dataclass
13
+
14
+ from ._typing import (
15
+ DefaultVar,
16
+ FontDict,
17
+ PlotSettableVar,
18
+ SettingDict,
19
+ SettingKey,
20
+ StyleName,
21
+ SubplotDict,
22
+ )
23
+
24
+ __all__ = ["PlotSettings", "PlotSettable"]
25
+
26
+
27
+ @dataclass(validate_methods=True)
28
+ class PlotSettings:
29
+ """Stores and manages settings for plotting."""
30
+
31
+ title: Optional[str] = None
32
+ xlabel: Optional[str] = None
33
+ ylabel: Optional[str] = None
34
+ alpha: Optional[float] = None
35
+ dpi: Optional[float] = None
36
+ grid: Optional[bool] = None
37
+ grid_alpha: Optional[float] = None
38
+ style: Optional[StyleName] = None
39
+ figsize: Optional[tuple[int, int]] = None
40
+ fontdict: Optional[FontDict] = None
41
+ legend_loc: Optional[str] = None
42
+ subplots_adjust: Optional[SubplotDict] = None
43
+
44
+ def __getitem__(self, __key: SettingKey) -> Any:
45
+ return getattr(self, __key)
46
+
47
+ def __setitem__(self, __key: SettingKey, __value: Any) -> None:
48
+ setattr(self, __key, __value)
49
+
50
+ def __repr__(self) -> str:
51
+ return self.__class__.__name__ + "(" + self.repr_not_none() + ")"
52
+
53
+ def repr_not_none(self) -> str:
54
+ """
55
+ Returns a string representation of attributes with not-None values.
56
+
57
+ Returns
58
+ -------
59
+ str
60
+ String representation.
61
+
62
+ """
63
+ diff = [f"{k}={repr(v)}" for k, v in asdict(self).items() if v is not None]
64
+ return ", ".join(diff)
65
+
66
+ def keys(self) -> list[SettingKey]:
67
+ """
68
+ Keys of settings.
69
+
70
+ Returns
71
+ -------
72
+ list[SettingKey]
73
+ Keys of the settings.
74
+
75
+ """
76
+ return getattr(self, "__match_args__")
77
+
78
+ def reset(self) -> None:
79
+ """
80
+ Reset all the settings to None.
81
+
82
+ """
83
+ for k in self.keys():
84
+ self[k] = None
85
+
86
+
87
+ @dataclass(init=False)
88
+ class PlotSettable:
89
+ """Contains an attribute of plot settings, and provides methods for
90
+ handling these settings.
91
+
92
+ """
93
+
94
+ settings: PlotSettings = attr(default_factory=PlotSettings, init=False)
95
+
96
+ def _set(
97
+ self, *, inplace: bool = False, **kwargs: Unpack[SettingDict]
98
+ ) -> Self | None:
99
+ obj = self if inplace else self.copy()
100
+ keys = obj.settings.keys()
101
+ for k, v in kwargs.items():
102
+ if v is None or k not in keys:
103
+ continue
104
+ obj.setting_check(k, v)
105
+ if isinstance(v, dict) and isinstance(d := obj.settings[k], dict):
106
+ d.update(v)
107
+ else:
108
+ obj.settings[k] = v
109
+ if not inplace:
110
+ return obj
111
+
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
+ def load(self, settings: "PlotSettings | SettingDict") -> None:
145
+ """
146
+ Load in the settings.
147
+
148
+ Parameters
149
+ ----------
150
+ settings : PlotSettings | SettingDict
151
+ An instance of `PlotSettings` or a dict.
152
+
153
+ """
154
+ if isinstance(settings, PlotSettings):
155
+ settings = asdict(settings)
156
+ self._set(inplace=True, **settings)
157
+
158
+ def get_setting(
159
+ self, key: SettingKey, default: Optional[DefaultVar] = None
160
+ ) -> "DefaultVar | Any":
161
+ """
162
+ Returns the value of a setting if it is not None, otherwise returns the
163
+ default value.
164
+
165
+ Parameters
166
+ ----------
167
+ key : SettingKey
168
+ Key of the setting.
169
+ default : DefaultVar, optional
170
+ Specifies the default value to be returned if the requested value
171
+ is None, by default None.
172
+
173
+ Returns
174
+ -------
175
+ DefaultVar | Any
176
+ Value of the setting.
177
+
178
+ """
179
+ return default if (value := self.settings[key]) is None else value
180
+
181
+ def customize(
182
+ self, cls: type["PlotSettableVar"], *args, **kwargs
183
+ ) -> "PlotSettableVar":
184
+ """
185
+ Initialize another instance with the same settings as `self`.
186
+
187
+ Parameters
188
+ ----------
189
+ cls : type[PlotSetableVar]
190
+ Type of the new instance.
191
+ *args :
192
+ Positional arguments.
193
+ **kwargs :
194
+ Keyword arguments.
195
+
196
+ Returns
197
+ -------
198
+ PlotSetableVar
199
+ The new instance.
200
+
201
+ Raises
202
+ ------
203
+ ValueError
204
+ Raised when `cls` cannot be customized.
205
+
206
+ """
207
+ if not issubclass(cls, PlotSettable):
208
+ raise ValueError(f"type {cls} cannot be customized")
209
+ matched: dict[str, Any] = {}
210
+ unmatched: dict[str, Any] = {}
211
+ for k, v in kwargs.items():
212
+ if k in cls.__init__.__code__.co_varnames[1:]:
213
+ matched[k] = v
214
+ else:
215
+ unmatched[k] = v
216
+ obj = cls(*args, **matched)
217
+ obj.settings = PlotSettings(**asdict(self.settings))
218
+ for k, v in unmatched.items():
219
+ setattr(obj, k, v)
220
+ return obj
221
+
222
+ def copy(self) -> Self:
223
+ """
224
+ Copy the instance of self (but not deepcopy).
225
+
226
+ Returns
227
+ -------
228
+ Self
229
+ A new instance of self.
230
+
231
+ """
232
+ raise TypeError(f"cannot copy instance of {self.__class__.__name__!r}")
@@ -0,0 +1,6 @@
1
+ """
2
+ Contains utils for dataplot.
3
+
4
+ """
5
+
6
+ __all__ = []
dataplot/utils/math.py ADDED
@@ -0,0 +1,80 @@
1
+ """
2
+ The core of math: linear_regression_1d(), get_quantile(), get_prob(), etc.
3
+
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ __all__ = ["linear_regression_1d", "get_quantile", "get_prob"]
9
+
10
+
11
+ def linear_regression_1d(y: np.ndarray, x: np.ndarray) -> tuple[float, float]:
12
+ """
13
+ Implements a 1-demensional linear regression of y on x (y = ax + b), and
14
+ returns the regression coefficients (a, b). Nan-values and inf-values are
15
+ handled smartly.
16
+
17
+ Parameters
18
+ ----------
19
+ y : np.ndarray
20
+ The dependent variable.
21
+ x : np.ndarray
22
+ The independent variable.
23
+
24
+ Returns
25
+ -------
26
+ tuple[float, float]
27
+ The regression coefficients (a, b).
28
+
29
+ """
30
+ x, y = (
31
+ np.nan_to_num(x, posinf=np.nan, neginf=np.nan),
32
+ np.nan_to_num(y, posinf=np.nan, neginf=np.nan),
33
+ )
34
+ xy_mean = np.nanmean(x * y)
35
+ x_mean = np.nanmean(x)
36
+ y_mean = np.nanmean(y)
37
+ b = (xy_mean - x_mean * y_mean) / np.nanvar(x)
38
+ a = y_mean - x_mean * b
39
+ return a, b
40
+
41
+
42
+ def get_quantile(data: np.ndarray, p: np.ndarray) -> np.ndarray:
43
+ """
44
+ Get quantiles from cummulative probabilities. Nan-values and inf-values
45
+ are handled smartly.
46
+
47
+ Parameters
48
+ ----------
49
+ data : np.ndarray
50
+ Original data.
51
+ p : np.ndarray
52
+ Cummulative probabilities.
53
+
54
+ Returns
55
+ -------
56
+ np.ndarray
57
+ Quantiles.
58
+
59
+ """
60
+ return np.nanquantile(np.nan_to_num(data, posinf=np.nan, neginf=np.nan), p)
61
+
62
+
63
+ def get_prob(data: np.ndarray, q: np.ndarray) -> np.ndarray:
64
+ """
65
+ Get cummulative probabilities from quantiles.
66
+
67
+ Parameters
68
+ ----------
69
+ data : np.ndarray
70
+ Original data.
71
+ q : np.ndarray
72
+ Quantiles.
73
+
74
+ Returns
75
+ -------
76
+ np.ndarray
77
+ Cummulative probabilities.
78
+
79
+ """
80
+ return np.array([np.sum(data <= x) for x in q]) / np.isfinite(data).sum()
@@ -0,0 +1,269 @@
1
+ """
2
+ The core of multi: multi(), multipartial(), etc.
3
+
4
+ """
5
+
6
+ from typing import Any, Callable, Generic, Iterable, LiteralString, Optional, TypeVar
7
+
8
+ from validating import dataclass
9
+
10
+ T = TypeVar("T")
11
+ S = TypeVar("S", bound=LiteralString)
12
+
13
+
14
+ __all__ = [
15
+ "MultiObject",
16
+ "multipartial",
17
+ "single",
18
+ "multiple",
19
+ "REMAIN",
20
+ "UNSUBSCRIPTABLE",
21
+ ]
22
+
23
+
24
+ class MultiObject(Generic[T]):
25
+ """
26
+ A basic object that enables multi-element attribute-getting,
27
+ attribute-setting, calling, etc. This object maintains a list of items,
28
+ and if a method (including some magic methods, see below) is called, each
29
+ item's method with the same name will be called instead, and the results
30
+ come as a new MultiObject.
31
+
32
+ Here are the methods that will be overloaded:
33
+ * __getattr__()
34
+ * __setattr__()
35
+ * __call__()
36
+ * __getitem__()
37
+ * __setitem__()
38
+ * All public methods
39
+ * All private methods that starts with only one "_"
40
+
41
+ And here is the only property that is exposed outside:
42
+ * __multiobjects__ : returns the items
43
+
44
+ Parameters
45
+ ----------
46
+ __iterable : Iterable, optional
47
+ If not given, the constructor creates a new empty MultiObject. If
48
+ specified, the argument must be an iterable (the same as what is needed
49
+ for creating a list). By default None.
50
+ call_reducer : Callable[[list], Any], optional
51
+ Specifies a reducer for the return values of `__call__()`. If specified,
52
+ should be a callable that receives the list of original returns, and
53
+ gives back a reduced value. If None, the reduced value will always be a
54
+ new MultiObject. By default None.
55
+ call_reflex : bool, optional
56
+ If True, the return values of a previous element's `__call__()` will be
57
+ provided to the next element as a keyword argument named
58
+ '__multi_prev_returned__', by default False.
59
+ attr_reducer: Callable[[str], Callable[[list], Any]], optional
60
+ Specifies a reducer for the return values of `__getattr__()`. If
61
+ specified, should be a callable that receives the attribute name, and
62
+ gives back a new callable. The new callable will receive the list of
63
+ original return values, and gives back a reduced value. If None, the
64
+ reduced value will always be a new MultiObject. By default None.
65
+
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ __iterable: Optional[Iterable] = None,
71
+ *,
72
+ call_reducer: Optional[Callable[[list], Any]] = None,
73
+ call_reflex: bool = False,
74
+ attr_reducer: Optional[Callable[[str], Callable[[list], Any]]] = None,
75
+ ) -> None:
76
+ self.__call_reducer = call_reducer
77
+ self.__call_reflex = call_reflex
78
+ self.__attr_reducer = attr_reducer
79
+ self.__items: list[S] = [] if __iterable is None else list(__iterable)
80
+
81
+ def __getattr__(self, __name: str) -> "MultiObject | Any":
82
+ if __name.startswith("__"):
83
+ raise AttributeError(f"cannot reach attribute '{__name}'")
84
+ attrs = [getattr(x, __name) for x in self.__items]
85
+ if self.__attr_reducer:
86
+ reduced = self.__attr_reducer(__name)(attrs)
87
+ if reduced != REMAIN:
88
+ return reduced
89
+ return MultiObject(attrs)
90
+
91
+ def __setattr__(self, __name: Any, __value: Any) -> None:
92
+ if isinstance(__name, str) and __name.startswith("_"):
93
+ super().__setattr__(__name, __value)
94
+ else:
95
+ for i, obj in enumerate(self.__items):
96
+ setattr(obj, single(__name, n=i), single(__value, n=i))
97
+
98
+ def __call__(self, *args: Any, **kwargs: Any) -> "MultiObject | Any":
99
+ returns = []
100
+ len_items = len(self.__items)
101
+ r = None
102
+ for i, obj in enumerate(self.__items):
103
+ a = [single(x, n=i) for x in args]
104
+ kwd = {k: single(v, n=i) for k, v in kwargs.items()}
105
+ if self.__call_reflex:
106
+ kwd["__multi_is_final__"] = i == len_items - 1
107
+ kwd["__multi_prev_returned__"] = r if i > 0 else None
108
+ returns.append(r := obj(*a, **kwd))
109
+ if self.__call_reducer:
110
+ reduced = self.__call_reducer(returns)
111
+ if reduced != REMAIN:
112
+ return reduced
113
+ return MultiObject(returns)
114
+
115
+ def __getitem__(self, __key: Any) -> T | "MultiObject":
116
+ items = [x[__key] for x in self.__items]
117
+ if isinstance(__key, int) and UNSUBSCRIPTABLE in items:
118
+ return self.__items[__key]
119
+ return MultiObject(items)
120
+
121
+ def __setitem__(self, __key: Any, __value: Any) -> None:
122
+ for i, obj in enumerate(self.__items):
123
+ obj[single(__key, n=i)] = single(__value, n=i)
124
+
125
+ def __repr__(self) -> str:
126
+ return ("\n").join("- " + repr(x).replace("\n", "\n ") for x in self.__items)
127
+
128
+ def __str__(self) -> str:
129
+ signature = self.__class__.__name__ + repr_not_none(self)
130
+ return f"{signature}"
131
+
132
+ @property
133
+ def __multiobjects__(self) -> list[T]:
134
+ return self.__items
135
+
136
+
137
+ def repr_not_none(x: MultiObject) -> str:
138
+ """
139
+ Returns a string representation of the MultiObject's attributes with
140
+ not-None values. Attributes with values of None are ignored.
141
+
142
+ Parameters
143
+ ----------
144
+ x : MultiObject
145
+ Any object.
146
+
147
+ Returns
148
+ -------
149
+ str
150
+ String representation.
151
+
152
+ """
153
+ namelist = [n for n in x.__init__.__code__.co_varnames[1:] if not n.startswith("_")]
154
+ not_nones: list[str] = []
155
+ for n in namelist:
156
+ if not hasattr(x, p := f"_{type(x).__name__}__{n}"):
157
+ continue
158
+ if (v := getattr(x, p)) is None:
159
+ continue
160
+ if isinstance(v, Callable):
161
+ v = v.__name__
162
+ not_nones.append(f"{n}={v}")
163
+ return "" if len(not_nones) == 0 else "(" + ", ".join(not_nones) + ")"
164
+
165
+
166
+ @dataclass(validate_methods=True)
167
+ class MultiFlag:
168
+ """Flag for MultiObjects."""
169
+
170
+ flag: int
171
+ name: str
172
+ err: type | None = None
173
+ errmsg: str = ""
174
+
175
+ def __repr__(self) -> str:
176
+ if self.err is not None:
177
+ raise self.err(self.errmsg)
178
+ return self.name
179
+
180
+ def __eq__(self, __value: Any) -> bool:
181
+ if isinstance(__value, MultiFlag):
182
+ return self.flag == __value.flag
183
+ return False
184
+
185
+
186
+ REMAIN = MultiFlag(0, "REMAIN")
187
+ UNSUBSCRIPTABLE = MultiFlag(
188
+ -1, "UNSUBSCRIPTABLE", TypeError, "object is not subscriptable"
189
+ )
190
+
191
+
192
+ def multipartial(**kwargs) -> Callable[[list], MultiObject]:
193
+ """
194
+ Returns a MultiObject constructor with partial application of the
195
+ given arguments and keywords.
196
+
197
+ Returns
198
+ -------
199
+ Callable[[list], MultiObject]
200
+ A MultiObject constructor.
201
+
202
+ """
203
+
204
+ def multi_constructor(x: list):
205
+ return MultiObject(x, **kwargs)
206
+
207
+ return multi_constructor
208
+
209
+
210
+ def single(x: T, n: int = -1) -> T:
211
+ """
212
+ If a MultiObject is provided, return its n-th element, otherwise return
213
+ the input itself.
214
+
215
+ Parameters
216
+ ----------
217
+ x : T
218
+ Can be a MultiObject or anything else.
219
+ n : int, optional
220
+ Specifies which element to return if a MultiObject is provided, by
221
+ default -1.
222
+
223
+ Returns
224
+ -------
225
+ T
226
+ A single object.
227
+
228
+ """
229
+ return x.__multiobjects__[n] if isinstance(x, MultiObject) else x
230
+
231
+
232
+ def multiple(x: T) -> list[T]:
233
+ """
234
+ If a MultiObject is provided, return a list of its elements, otherwise
235
+ return `[x]`.
236
+
237
+ Parameters
238
+ ----------
239
+ x : T
240
+ Can be a MultiObject or anything else.
241
+
242
+ Returns
243
+ -------
244
+ list[T]
245
+ List of elements.
246
+
247
+ """
248
+ return x.__multiobjects__ if isinstance(x, MultiObject) else [x]
249
+
250
+
251
+ def cleaner(x: list) -> MultiObject | None:
252
+ """
253
+ If the list is consist of None's only, return None, otherwise return
254
+ a MultiObject instantiated by the list.
255
+
256
+ Parameters
257
+ ----------
258
+ x : list
259
+ List of objects.
260
+
261
+ Returns
262
+ -------
263
+ MultiObject | None
264
+ May be a MultiObject instantiated by the list or None.
265
+
266
+ """
267
+ if all(i is None for i in x):
268
+ return None
269
+ return MultiObject(x, call_reducer=cleaner, attr_reducer=lambda x: cleaner)
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.4
2
+ Name: dataplot
3
+ Version: 0.1.6
4
+ Summary: Provides plotting tools useful in datascience.
5
+ Project-URL: Documentation, https://github.com/Chitaoji/dataplot/blob/main/README.md
6
+ Project-URL: Repository, https://github.com/Chitaoji/dataplot/
7
+ Author-email: Chitaoji <2360742040@qq.com>
8
+ Maintainer-email: Chitaoji <2360742040@qq.com>
9
+ License-Expression: BSD-3-Clause
10
+ License-File: LICENSE
11
+ Keywords: plot
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: >=3.13
15
+ Requires-Dist: lazyr
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: numpy
18
+ Requires-Dist: pandas
19
+ Requires-Dist: scipy
20
+ Requires-Dist: seaborn
21
+ Requires-Dist: validating
22
+ Description-Content-Type: text/markdown
23
+
24
+ # dataplot
25
+ Provides plotting tools useful in datascience.
26
+
27
+ ## Installation
28
+ ```sh
29
+ $ pip install dataplot
30
+ ```
31
+
32
+ ## Requirements
33
+ ```txt
34
+ validating
35
+ lazyr
36
+ matplotlib
37
+ numpy
38
+ pandas
39
+ scipy
40
+ seaborn
41
+ ```
42
+
43
+ ## See Also
44
+ ### Github repository
45
+ * https://github.com/Chitaoji/dataplot/
46
+
47
+ ### PyPI project
48
+ * https://pypi.org/project/dataplot/
49
+
50
+ ## License
51
+ This project falls under the BSD 3-Clause License.
52
+
53
+ ## History
54
+ ### v0.1.6
55
+ * New method `PlotDataSet.scatter()` to draw true scatter charts while keeping `PlotDataSet.plot()` as line chart behavior.
56
+ * Improved automatic label inference for `dp.data(...)`, plotting labels, and x-axis labels in interactive contexts.
57
+ * Plot builders now use deferred drawing; removed `dp.show()` and improved axis/figure rendering in object representations.
58
+ * Refined rendering stability with fixes for empty-axis cleanup and figure re-rendering in `FigWrapper.__repr__`.
59
+ * Updated minimum required Python version to >=3.13.
60
+
61
+ ### v0.1.5
62
+ * Fixed issue: unworking figure settings in the artist methods.
63
+
64
+ ### v0.1.4
65
+ * Fixed issue: incorrectly displayed histogram statistics when the x-label had been modified by the user.
66
+
67
+ ### v0.1.3
68
+ * Allowed users to set the plot-settings by kwargs in artist methods like `PlotDataSet.hist()`, `PlotDataSet.plot()`, etc.
69
+ * New operation methods `PlotDataSet.signedpow()` and `PlotDataSet.log10()`.
70
+ * Renamed `PlotDataSet.signlog()` to `.signedlog()`; renamed `PlotDataSet.opclear()` to `.undo_all()`; removed `PlotDataSet.opclear_records_only()`.
71
+ * New optional parameter `format_label=` for `PlotDataSet.set_plot()` to decide whether to format the label when painting on the axes.
72
+ * When defining the data classes, used *dataclasses* instead of *attrs* for a faster import.
73
+
74
+ ### v0.1.2
75
+ * New methods `PlotDataSet.corrmap()`, `PlotDataSet.ppplot()`, and `PlotDataSet.resample()`.
76
+ * New optional parameter `fmt=` for `PlotDataSet.plot()`, `PlotDataSet.qqplot()`, `PlotDataSet.ppplot()`, and `PlotDataSet.ksplot()`.
77
+ * Bugfix.
78
+
79
+ ### v0.1.1
80
+ * New module-level function `dp.show()`.
81
+ * New methods `PlotDataSet.qqplot()`, `PlotDataSet.ksplot()` and `PlotDataSet.abs()`.
82
+ * All the plotting method (e.g., `.hist()`) will now return an `Artist` object instead of None.
83
+ * New plot settings: `grid` and `grid_alpha`.
84
+ * Parameters of `FigWrapper.set_figure()`, `AxesWrapper.set_axes()` and `PlotDataSet.set_plot()` are keyword-only now.
85
+ * The returns of `.set_figure()` and `.set_axes()` will be None (instead of `self`) to avoid misunderstandings.
86
+ * New optional parameter `inplace=` for `PlotDataSet.set_plot()` to decide whether the changes will happen in-place (which is the only option before) or in a new copy.
87
+ * Parameter `ticks=` for `PlotDataSet.plot()` can be set to a `PlotDataSet` object now.
88
+
89
+ ### v0.1.0
90
+ * `PlotDataSet` now supports binary operations including +, -, *, /, and **.
91
+ * New methods `FigWrapper.set_figure()` and `AxesWrapper.set_axes()` - use them instead of `*.set_plot()`.
92
+ * Simplified the usage of `AxesWrapper`.
93
+ * New plot settings: `subplots_adjust=`, `fontdict=` and `dpi=`.
94
+ * After this version, the required Python version is updated to >=3.11.9. Download and install v0.0.2 if the user is under lower Python version (>=3.8.13).
95
+
96
+ ### v0.0.2
97
+ * Updated the meta-data.
98
+
99
+ ### v0.0.1
100
+ * Initial release.