scales-python 1.4.0.9000__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.
scales/minor_breaks.py ADDED
@@ -0,0 +1,197 @@
1
+ """
2
+ Minor-break generators for linear (non-log) scales.
3
+
4
+ Python port of ``R/minor_breaks.R`` from the R *scales* package
5
+ (https://github.com/r-lib/scales).
6
+
7
+ All public generators are closure factories: they return a callable with
8
+ signature ``(major_breaks, limits, n) -> numpy.ndarray``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Callable, Optional
14
+
15
+ import numpy as np
16
+ from numpy.typing import ArrayLike
17
+
18
+ __all__ = [
19
+ "minor_breaks_n",
20
+ "minor_breaks_width",
21
+ "regular_minor_breaks",
22
+ ]
23
+
24
+
25
+ def minor_breaks_n(
26
+ n: int = 2,
27
+ ) -> Callable[[np.ndarray, np.ndarray, int], np.ndarray]:
28
+ """Place *n* minor breaks between each pair of major breaks.
29
+
30
+ Parameters
31
+ ----------
32
+ n : int, optional
33
+ Number of minor breaks between consecutive major breaks
34
+ (default 2).
35
+
36
+ Returns
37
+ -------
38
+ callable
39
+ A function ``(major_breaks, limits, n_minor) -> numpy.ndarray``
40
+ of minor-break positions.
41
+
42
+ Examples
43
+ --------
44
+ >>> fn = minor_breaks_n(n=4)
45
+ >>> fn(np.array([0, 10, 20]), np.array([0, 20]), 4)
46
+ array([ 2., 4., 6., 8., 12., 14., 16., 18.])
47
+ """
48
+
49
+ def _minor_breaks(
50
+ major: np.ndarray,
51
+ limits: np.ndarray,
52
+ n_minor: int = n,
53
+ ) -> np.ndarray:
54
+ major = np.sort(np.asarray(major, dtype=float))
55
+ limits = np.asarray(limits, dtype=float)
56
+
57
+ if len(major) < 2:
58
+ return np.array([], dtype=float)
59
+
60
+ minors: list[float] = []
61
+ for i in range(len(major) - 1):
62
+ lo = major[i]
63
+ hi = major[i + 1]
64
+ step = (hi - lo) / (n_minor + 1)
65
+ for j in range(1, n_minor + 1):
66
+ val = lo + j * step
67
+ if limits[0] <= val <= limits[1]:
68
+ minors.append(val)
69
+
70
+ return np.array(sorted(set(minors)), dtype=float)
71
+
72
+ return _minor_breaks
73
+
74
+
75
+ def minor_breaks_width(
76
+ width: float,
77
+ offset: float = 0,
78
+ ) -> Callable[[np.ndarray, np.ndarray, int], np.ndarray]:
79
+ """Place minor breaks at fixed-width intervals.
80
+
81
+ Parameters
82
+ ----------
83
+ width : float
84
+ Spacing between consecutive minor breaks.
85
+ offset : float, optional
86
+ Offset from zero for the break grid (default 0). The grid is
87
+ placed at ``... , offset - width, offset, offset + width, ...``
88
+ and then clipped to the axis limits.
89
+
90
+ Returns
91
+ -------
92
+ callable
93
+ A function ``(major_breaks, limits, n) -> numpy.ndarray``
94
+ of minor-break positions.
95
+
96
+ Examples
97
+ --------
98
+ >>> fn = minor_breaks_width(2.5)
99
+ >>> fn(np.array([0, 10, 20]), np.array([0, 20]), 5)
100
+ array([ 0. , 2.5, 5. , 7.5, 10. , 12.5, 15. , 17.5, 20. ])
101
+ """
102
+
103
+ def _minor_breaks(
104
+ major: np.ndarray,
105
+ limits: np.ndarray,
106
+ n: int = 5,
107
+ ) -> np.ndarray:
108
+ limits = np.asarray(limits, dtype=float)
109
+ lo, hi = float(limits[0]), float(limits[1])
110
+
111
+ # Build grid aligned to offset
112
+ start = np.floor((lo - offset) / width) * width + offset
113
+ stop = np.ceil((hi - offset) / width) * width + offset
114
+ breaks = np.arange(start, stop + width / 2, width)
115
+
116
+ # Clip to limits
117
+ breaks = breaks[(breaks >= lo - 1e-10) & (breaks <= hi + 1e-10)]
118
+
119
+ return breaks
120
+
121
+ return _minor_breaks
122
+
123
+
124
+ def regular_minor_breaks(
125
+ reverse: bool = False,
126
+ ) -> Callable[[np.ndarray, np.ndarray, int], np.ndarray]:
127
+ """Default minor-break placement: ``n - 1`` evenly spaced between majors.
128
+
129
+ This is the standard minor-break strategy used by ggplot2. Between
130
+ each pair of consecutive major breaks, ``n - 1`` minor breaks are
131
+ inserted at equal spacing.
132
+
133
+ Parameters
134
+ ----------
135
+ reverse : bool, optional
136
+ If ``True``, the limits are internally reversed before
137
+ computing breaks and the result is reversed back. Useful for
138
+ reversed continuous scales (default ``False``).
139
+
140
+ Returns
141
+ -------
142
+ callable
143
+ A function ``(major_breaks, limits, n) -> numpy.ndarray``
144
+ of minor-break positions.
145
+
146
+ Examples
147
+ --------
148
+ >>> fn = regular_minor_breaks()
149
+ >>> fn(np.array([0, 5, 10]), np.array([0, 10]), 2)
150
+ array([ 2.5, 7.5])
151
+ """
152
+
153
+ def _minor_breaks(
154
+ major: np.ndarray,
155
+ limits: np.ndarray,
156
+ n: int = 2,
157
+ ) -> np.ndarray:
158
+ # Mirrors R's regular_minor_breaks (R/minor_breaks.R:65-93):
159
+ # drop NAs; if fewer than 2 majors, return empty; extend by one
160
+ # step past each edge depending on which side the limits reach;
161
+ # interpolate (n+1)-length ``seq`` between consecutive majors,
162
+ # dropping the last point, then re-append the final major.
163
+ b = np.asarray(major, dtype=float)
164
+ b = b[~np.isnan(b)]
165
+ b = np.sort(b)
166
+
167
+ if len(b) < 2 or n < 1:
168
+ return np.array([], dtype=float)
169
+
170
+ lim = np.asarray(limits, dtype=float)
171
+ lo_lim, hi_lim = float(np.min(lim)), float(np.max(lim))
172
+
173
+ bd = b[1] - b[0] # step
174
+
175
+ # R: when not reversed, extend low side if limits reach below,
176
+ # high side if limits reach above; reversed swaps the two.
177
+ if not reverse:
178
+ if lo_lim < b[0]:
179
+ b = np.concatenate([[b[0] - bd], b])
180
+ if hi_lim > b[-1]:
181
+ b = np.concatenate([b, [b[-1] + bd]])
182
+ else:
183
+ if hi_lim > b[-1]:
184
+ b = np.concatenate([[b[0] - bd], b])
185
+ if lo_lim < b[0]:
186
+ b = np.concatenate([b, [b[-1] + bd]])
187
+
188
+ # (n+1)-length seq between each consecutive pair, dropping last
189
+ # (that's the next major). Then re-append the final major.
190
+ pieces: list[np.ndarray] = []
191
+ for i in range(len(b) - 1):
192
+ seq = np.linspace(b[i], b[i + 1], n + 1)[:-1]
193
+ pieces.append(seq)
194
+ pieces.append(np.array([b[-1]], dtype=float))
195
+ return np.concatenate(pieces)
196
+
197
+ return _minor_breaks