gsplot 0.0.1__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.
@@ -0,0 +1,272 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import Any, Callable, TypeVar, cast
5
+
6
+ import numpy as np
7
+ from numpy.typing import NDArray
8
+
9
+ from ..color.colormap import Colormap
10
+
11
+ F = TypeVar("F", bound=Callable[..., Any])
12
+
13
+ __all__: list[str] = []
14
+
15
+
16
+ class NumLines:
17
+ """
18
+ A thread-safe singleton class to track the number of lines plotted on each axis.
19
+
20
+ This class maintains a count of the number of lines plotted on specific axes in a
21
+ Matplotlib figure. It uses a thread-safe singleton pattern to ensure a single instance
22
+ across the application. It also provides a decorator to automatically increment the
23
+ line count when a plotting function is called.
24
+
25
+ Attributes
26
+ --------------------
27
+ num_lines : list[int]
28
+ A list where each index represents an axis, and the value is the number of lines
29
+ plotted on that axis.
30
+
31
+ Methods
32
+ --------------------
33
+ num_lines_axis(axis_index)
34
+ Retrieves the number of lines plotted on a specific axis.
35
+ increment(axis_index)
36
+ Increments the line count for a specific axis.
37
+ count(func)
38
+ A decorator to increment the line count whenever a plotting function is called.
39
+ reset()
40
+ Resets the singleton instance, clearing all line counts.
41
+
42
+ Examples
43
+ --------------------
44
+ >>> num_lines = NumLines()
45
+ >>> print(num_lines.num_lines_axis(0))
46
+ 0 # No lines plotted yet
47
+
48
+ >>> num_lines.increment(0) # Increment the count for axis 0
49
+ >>> print(num_lines.num_lines_axis(0))
50
+ 1
51
+
52
+ >>> @NumLines.count
53
+ ... def plot_line(axis_index):
54
+ ... print(f"Plotting on axis {axis_index}")
55
+ >>> plot_line(0)
56
+ Plotting on axis 0
57
+ >>> print(num_lines.num_lines_axis(0))
58
+ 2
59
+ """
60
+
61
+ _instance: NumLines | None = None
62
+ _lock: threading.Lock = threading.Lock() # Lock to ensure thread safety
63
+
64
+ def __new__(cls) -> "NumLines":
65
+ with cls._lock: # Ensure thread safety
66
+ if cls._instance is None:
67
+ cls._instance = super(NumLines, cls).__new__(cls)
68
+ cls._instance._initialize_num_lines()
69
+ return cls._instance
70
+
71
+ def _initialize_num_lines(self) -> None:
72
+ """
73
+ Initializes the line count to its default value ([0]).
74
+ """
75
+ # Explicitly initialize the instance variable with a type hint
76
+ self._num_lines: list[int] = [0]
77
+
78
+ def update_num_lines(self, axis_index: int) -> None:
79
+ """
80
+ Ensures the line count list is large enough to include the given axis index.
81
+
82
+ Parameters
83
+ --------------------
84
+ axis_index : int
85
+ The index of the axis to update.
86
+ """
87
+ length = len(self._num_lines)
88
+ if axis_index + 1 > length:
89
+ self._num_lines.extend([0] * (axis_index - length + 1))
90
+
91
+ @property
92
+ def num_lines(self) -> list[int]:
93
+ """
94
+ Retrieves the list of line counts for all axes.
95
+
96
+ Returns
97
+ --------------------
98
+ list[int]
99
+ The list of line counts, where each index corresponds to an axis.
100
+
101
+ Examples
102
+ --------------------
103
+ >>> num_lines = NumLines()
104
+ >>> print(num_lines.num_lines)
105
+ [0]
106
+ """
107
+ return self._num_lines
108
+
109
+ def num_lines_axis(self, axis_index: int) -> int:
110
+ """
111
+ Retrieves the number of lines plotted on a specific axis.
112
+
113
+ Parameters
114
+ --------------------
115
+ axis_index : int
116
+ The index of the axis.
117
+
118
+ Returns
119
+ --------------------
120
+ int
121
+ The number of lines plotted on the specified axis.
122
+
123
+ Examples
124
+ --------------------
125
+ >>> num_lines = NumLines()
126
+ >>> print(num_lines.num_lines_axis(0))
127
+ 0
128
+ """
129
+ self.update_num_lines(axis_index)
130
+ return self._num_lines[axis_index]
131
+
132
+ def increment(self, axis_index: int) -> None:
133
+ """
134
+ Increments the line count for a specific axis.
135
+
136
+ Parameters
137
+ --------------------
138
+ axis_index : int
139
+ The index of the axis.
140
+
141
+ Examples
142
+ --------------------
143
+ >>> num_lines = NumLines()
144
+ >>> num_lines.increment(0)
145
+ >>> print(num_lines.num_lines_axis(0))
146
+ 1
147
+ """
148
+ self.update_num_lines(axis_index)
149
+ self._num_lines[axis_index] += 1
150
+
151
+ @classmethod
152
+ def count(cls, func: F) -> F:
153
+ """
154
+ A decorator to increment the line count whenever a plotting function is called.
155
+
156
+ Parameters
157
+ --------------------
158
+ func : Callable
159
+ The function to decorate.
160
+
161
+ Returns
162
+ --------------------
163
+ Callable
164
+ The decorated function.
165
+
166
+ Examples
167
+ --------------------
168
+ >>> @NumLines.count
169
+ ... def plot_line(axis_index):
170
+ ... print(f"Plotting on axis {axis_index}")
171
+ >>> plot_line(0)
172
+ Plotting on axis 0
173
+ >>> num_lines = NumLines()
174
+ >>> print(num_lines.num_lines_axis(0))
175
+ 1
176
+ """
177
+
178
+ def wrapper(self, *args: Any, **kwargs: Any) -> Any:
179
+ cls().increment(self.axis_index)
180
+ result = func(self, *args, **kwargs)
181
+ return result
182
+
183
+ return cast(F, wrapper)
184
+
185
+ @classmethod
186
+ def reset(cls) -> None:
187
+ """
188
+ Resets the singleton instance, clearing all line counts.
189
+
190
+ Examples
191
+ --------------------
192
+ >>> num_lines = NumLines()
193
+ >>> num_lines.increment(0)
194
+ >>> print(num_lines.num_lines_axis(0))
195
+ 1
196
+ >>> NumLines.reset()
197
+ >>> num_lines = NumLines()
198
+ >>> print(num_lines.num_lines_axis(0))
199
+ 0
200
+ """
201
+ cls._instance = None
202
+
203
+
204
+ class AutoColor:
205
+ """
206
+ A utility class for generating colors automatically based on the axis index and line count.
207
+
208
+ This class uses a predefined colormap and cycles through colors based on the number of lines
209
+ already plotted on the target axis. The default colormap is "viridis", and it is divided
210
+ into a specified number of discrete colors.
211
+
212
+ Parameters
213
+ --------------------
214
+ axis_index : int
215
+ The index of the target axis in the current figure.
216
+
217
+ Attributes
218
+ --------------------
219
+ COLORMAP_LENGTH : int
220
+ The number of discrete colors in the colormap (default is 5).
221
+ CMAP : str
222
+ The name of the Matplotlib colormap to use (default is "viridis").
223
+ colormap : numpy.ndarray
224
+ An array of RGB colors derived from the specified colormap.
225
+ num_lines_axis : int
226
+ The number of lines already plotted on the target axis.
227
+ cycle_color_index : int
228
+ The index of the color to use for the next line, calculated using modulo arithmetic.
229
+
230
+ Methods
231
+ --------------------
232
+ get_color()
233
+ Retrieves the next color from the colormap based on the current line count.
234
+
235
+ Examples
236
+ --------------------
237
+ >>> auto_color = AutoColor(axis_index=0)
238
+ >>> color = auto_color.get_color()
239
+ >>> print(color)
240
+ array([0.267004, 0.004874, 0.329415, 1.0]) # Example RGBA color from the colormap
241
+ """
242
+
243
+ def __init__(self, axis_index: int) -> None:
244
+ self.COLORMAP_LENGTH: int = 5
245
+ self.CMAP = "viridis"
246
+ self.colormap: NDArray[Any] = Colormap(
247
+ cmap=self.CMAP, N=self.COLORMAP_LENGTH
248
+ ).get_split_cmap()
249
+
250
+ self.num_lines_axis: int = NumLines().num_lines_axis(axis_index)
251
+ self.cycle_color_index: int = self.num_lines_axis % self.COLORMAP_LENGTH
252
+
253
+ def get_color(self) -> NDArray[Any]:
254
+ """
255
+ Retrieves the next color from the colormap based on the current line count.
256
+
257
+ This method determines the appropriate color for the next line to be plotted
258
+ on the target axis by cycling through the discrete colormap.
259
+
260
+ Returns
261
+ --------------------
262
+ numpy.ndarray
263
+ An array representing the RGBA color for the next line.
264
+
265
+ Examples
266
+ --------------------
267
+ >>> auto_color = AutoColor(axis_index=0)
268
+ >>> color = auto_color.get_color()
269
+ >>> print(color)
270
+ array([0.267004, 0.004874, 0.329415, 1.0]) # Example RGBA color
271
+ """
272
+ return np.array(self.colormap[self.cycle_color_index])
@@ -0,0 +1,120 @@
1
+ from typing import Any
2
+
3
+ import numpy as np
4
+ from matplotlib.colors import Normalize
5
+ from numpy.typing import ArrayLike, NDArray
6
+
7
+ __all__: list[str] = []
8
+
9
+
10
+ class LineColormapBase:
11
+ """
12
+ A base class for creating colormaps and segments for line collections.
13
+
14
+ This class provides utility methods to create line segments for plotting with
15
+ individual colors and to normalize data for applying colormaps.
16
+
17
+ Methods
18
+ --------------------
19
+ _create_segment(x, y)
20
+ Creates a set of line segments for line collections, enabling individual
21
+ segment coloring.
22
+ _create_cmap(cmapdata)
23
+ Creates a normalization object for mapping data points to colors.
24
+
25
+ Examples
26
+ --------------------
27
+ >>> line_base = LineColormapBase()
28
+ >>> x = np.array([0, 1, 2, 3])
29
+ >>> y = np.array([1, 2, 3, 4])
30
+ >>> segments = line_base._create_segment(x, y)
31
+ >>> print(segments.shape)
32
+ (3, 2, 2) # Shape: (numlines, points per line, x and y)
33
+
34
+ >>> cmapdata = np.array([0.1, 0.4, 0.6, 0.9])
35
+ >>> norm = line_base._create_cmap(cmapdata)
36
+ >>> print(norm(cmapdata))
37
+ [0. 0.5 0.83333333 1. ] # Normalized data
38
+ """
39
+
40
+ def _create_segment(self, x: ArrayLike, y: ArrayLike) -> NDArray[np.float64]:
41
+ """
42
+ Creates a set of line segments for line collections, enabling individual segment coloring.
43
+
44
+ The method converts the input x and y arrays into a collection of segments
45
+ suitable for use with Matplotlib's `LineCollection`. Each segment connects two
46
+ adjacent points from the input arrays.
47
+
48
+ Parameters
49
+ --------------------
50
+ x : ArrayLike
51
+ The x-coordinates of the points.
52
+ y : ArrayLike
53
+ The y-coordinates of the points.
54
+
55
+ Returns
56
+ --------------------
57
+ numpy.ndarray
58
+ An array of shape `(numlines, 2, 2)` representing the line segments,
59
+ where each segment is defined by two points `(x, y)`.
60
+
61
+ Examples
62
+ --------------------
63
+ >>> x = np.array([0, 1, 2, 3])
64
+ >>> y = np.array([1, 2, 3, 4])
65
+ >>> line_base = LineColormapBase()
66
+ >>> segments = line_base._create_segment(x, y)
67
+ >>> print(segments.shape)
68
+ (3, 2, 2) # Shape: (numlines, points per line, x and y)
69
+ """
70
+
71
+ # ╭──────────────────────────────────────────────────────────╮
72
+ # │ Create a set of line segments so that we can color them │
73
+ # │ individually │
74
+ # │ This creates the points as an N x 1 x 2 array so that │
75
+ # │ we can stack points │
76
+ # │ together easily to get the segments. The segments array │
77
+ # │ for line collection │
78
+ # │ needs to be (numlines) x (points per line) x 2 (for x │
79
+ # │ and y) │
80
+ # ╰──────────────────────────────────────────────────────────╯
81
+ points = np.array([x, y], dtype=np.float64).T.reshape(-1, 1, 2)
82
+ segments: NDArray[np.float64] = np.concatenate(
83
+ [points[:-1], points[1:]], axis=1
84
+ )
85
+ return segments
86
+
87
+ def _create_cmap(self, cmapdata: NDArray[Any]) -> Normalize:
88
+ """
89
+ Creates a normalization object for mapping data points to colors.
90
+
91
+ This method generates a `Normalize` object from Matplotlib, which scales
92
+ input data to the range `[0, 1]` for use in colormaps. If the input data has
93
+ at least two elements, the maximum value is removed to prevent color saturation.
94
+
95
+ Parameters
96
+ --------------------
97
+ cmapdata : numpy.ndarray
98
+ The input data for normalization.
99
+
100
+ Returns
101
+ --------------------
102
+ matplotlib.colors.Normalize
103
+ A normalization object mapping `cmapdata.min()` to 0 and `cmapdata.max()` to 1.
104
+
105
+ Examples
106
+ --------------------
107
+ >>> cmapdata = np.array([0.1, 0.4, 0.6, 0.9])
108
+ >>> line_base = LineColormapBase()
109
+ >>> norm = line_base._create_cmap(cmapdata)
110
+ >>> print(norm(cmapdata))
111
+ [0. 0.5 0.83333333 1. ] # Normalized data
112
+ """
113
+
114
+ # Create a continuous norm to map from data points to colors
115
+ if len(cmapdata) >= 2:
116
+ # delete maximun data
117
+ cmapdata = np.delete(cmapdata, np.where(cmapdata == np.max(cmapdata)))
118
+ norm = Normalize(cmapdata.min(), cmapdata.max())
119
+
120
+ return norm