plotilleresample 0.4__tar.gz → 1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Carlos A. Planchón
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: plotilleresample
3
+ Version: 1.0
4
+ Summary: Python module to resample datasets before plotting with Plotille.
5
+ Author-email: "Carlos A. Planchón" <carlosandresplanchonprestes@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/carlosplanchon/plotilleresample
8
+ Keywords: plotting,ascii,math,resample
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Scientific/Engineering :: Visualization
11
+ Classifier: Topic :: Terminals
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # plotilleresample
24
+ ![plotilleresample banner](assets/banner-v2.jpg)
25
+
26
+ *Python module to resample datasets before plotting with Plotille.*
27
+
28
+ [![CI](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml/badge.svg)](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml)
29
+ [![PyPI version](https://img.shields.io/pypi/v/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
30
+ [![Python versions](https://img.shields.io/pypi/pyversions/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
+
33
+ ## Why resample?
34
+
35
+ Plotille rasterizes to a terminal canvas of braille dots: `width * 2` columns by `height * 4` rows. Feeding it far more points than that costs interpolation time on detail the canvas cannot show. Plotille rasterizes; plotilleresample decides what information deserves to reach the rasterizer:
36
+
37
+ | Function | Strategy | Best for |
38
+ | ---------------------- | ------------------ | --------------------------------- |
39
+ | `resample_plot` | uniform stride | smooth lines, cheapest reduction |
40
+ | `resample_plot_minmax` | min/max per bucket | peaks, oscillations, time series |
41
+ | `resample_plot_lttb` | largest triangle per bucket | shape-faithful single line |
42
+ | `resample_plot_minmax_lttb` | minmax preselection + LTTB | shape-faithful line, large inputs |
43
+ | `resample_scatter` | uniform stride | large scatter inputs |
44
+
45
+ The uniform stride keeps one point every N: fast and predictable, but a narrow peak that falls between two kept points disappears from the plot. `resample_plot_minmax` instead makes one bucket per braille dot column and keeps the minimum and the maximum Y of each bucket, so the envelope of the signal — spikes included — always survives:
46
+
47
+ ```python
48
+ X = list(range(10000))
49
+ Y = [0.0] * 10000
50
+ Y[33] = 1000.0 # a narrow spike
51
+
52
+ _, y_stride = plotilleresample.resample_plot(X, Y)
53
+ _, y_minmax = plotilleresample.resample_plot_minmax(X, Y)
54
+
55
+ 1000.0 in y_stride # False: the spike vanished
56
+ 1000.0 in y_minmax # True: the envelope survives
57
+ ```
58
+
59
+ `resample_plot_lttb` implements Largest-Triangle-Three-Buckets (Steinarsson, 2013): it always keeps the first and the last point and picks the most shape-representative point of each bucket, giving a single clean line that looks like the original. It keeps one point per bucket, so — unlike min/max — one of two opposing extremes falling in the same bucket can be dropped: shape fidelity instead of envelope guarantee.
60
+
61
+ `resample_plot_minmax_lttb` is the hybrid (MinMaxLTTB, Van der Donckt et al., 2023; the plotly-resampler default): on large inputs a minmax pass preselects the per bucket extremes and LTTB runs over those candidates only. Visually close to pure LTTB and faster, with a gap that grows with input size — about 1.6x end to end at 100,000 points, and about 3.4x on the resampling pass alone at 1,000,000 — and the true extremes are always among the candidates.
62
+
63
+ All four plot resamplers work in sample order — they bucket by index, so X is expected to be already sorted, as in a time series (`resample_scatter` makes no ordering assumption). They keep at most `width * 4` points and `resample_scatter` keeps at most `width * 2 * height`, so plotille only receives what the canvas can actually display.
64
+
65
+ ## Benchmark
66
+
67
+ End to end times: building the plot string with plotille alone versus resampling first. Measured with `benchmarks/bench.py` (canvas 80x40, best of 3) on Python 3.14, Linux, Intel Core i5-1135G7:
68
+
69
+ | Points | plotille alone | stride + plotille | minmax + plotille | lttb + plotille | mmlttb + plotille |
70
+ | ------- | -------------- | ----------------- | ----------------- | --------------- | ----------------- |
71
+ | 10,000 | 202 ms | 23 ms | 22 ms | 27 ms | 24 ms |
72
+ | 100,000 | 1.75 s | 39 ms | 52 ms | 90 ms | 56 ms |
73
+
74
+ Reproduce it from the repository root with:
75
+
76
+ ```
77
+ uv run --group bench benchmarks/bench.py
78
+ ```
79
+
80
+ The resampling-pass figure quoted in Why resample? (pure LTTB vs MinMaxLTTB at 1,000,000 points) has its own script:
81
+
82
+ ```
83
+ uv run benchmarks/bench_resamplers.py
84
+ ```
85
+
86
+ ## Installation
87
+ ### Install with UV:
88
+ ```
89
+ uv add plotilleresample
90
+ ```
91
+ ### Install with pip:
92
+ ```
93
+ pip install plotilleresample
94
+ ```
95
+
96
+ plotilleresample has no runtime dependencies — not even plotille: it only reduces sequences. To run the example below, install plotille as well (`uv add plotille` or `pip install plotille`).
97
+
98
+ ## Usage
99
+ ```python
100
+ import math
101
+
102
+ import plotille
103
+
104
+ from plotilleresample import resample_plot_minmax_lttb
105
+
106
+ r = 100_000
107
+ X = list(range(r))
108
+ Y = [math.sin(i / 500) * 100 for i in range(r)]
109
+
110
+ X, Y = resample_plot_minmax_lttb(X, Y, width=80, height=40)
111
+ print(plotille.plot(X, Y, width=80, height=40))
112
+ ```
113
+
114
+ The full interactive demo, running every resampler on the same dataset, lives in [`examples/demo.py`](examples/demo.py).
@@ -0,0 +1,92 @@
1
+ # plotilleresample
2
+ ![plotilleresample banner](assets/banner-v2.jpg)
3
+
4
+ *Python module to resample datasets before plotting with Plotille.*
5
+
6
+ [![CI](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml/badge.svg)](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml)
7
+ [![PyPI version](https://img.shields.io/pypi/v/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
8
+ [![Python versions](https://img.shields.io/pypi/pyversions/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
10
+
11
+ ## Why resample?
12
+
13
+ Plotille rasterizes to a terminal canvas of braille dots: `width * 2` columns by `height * 4` rows. Feeding it far more points than that costs interpolation time on detail the canvas cannot show. Plotille rasterizes; plotilleresample decides what information deserves to reach the rasterizer:
14
+
15
+ | Function | Strategy | Best for |
16
+ | ---------------------- | ------------------ | --------------------------------- |
17
+ | `resample_plot` | uniform stride | smooth lines, cheapest reduction |
18
+ | `resample_plot_minmax` | min/max per bucket | peaks, oscillations, time series |
19
+ | `resample_plot_lttb` | largest triangle per bucket | shape-faithful single line |
20
+ | `resample_plot_minmax_lttb` | minmax preselection + LTTB | shape-faithful line, large inputs |
21
+ | `resample_scatter` | uniform stride | large scatter inputs |
22
+
23
+ The uniform stride keeps one point every N: fast and predictable, but a narrow peak that falls between two kept points disappears from the plot. `resample_plot_minmax` instead makes one bucket per braille dot column and keeps the minimum and the maximum Y of each bucket, so the envelope of the signal — spikes included — always survives:
24
+
25
+ ```python
26
+ X = list(range(10000))
27
+ Y = [0.0] * 10000
28
+ Y[33] = 1000.0 # a narrow spike
29
+
30
+ _, y_stride = plotilleresample.resample_plot(X, Y)
31
+ _, y_minmax = plotilleresample.resample_plot_minmax(X, Y)
32
+
33
+ 1000.0 in y_stride # False: the spike vanished
34
+ 1000.0 in y_minmax # True: the envelope survives
35
+ ```
36
+
37
+ `resample_plot_lttb` implements Largest-Triangle-Three-Buckets (Steinarsson, 2013): it always keeps the first and the last point and picks the most shape-representative point of each bucket, giving a single clean line that looks like the original. It keeps one point per bucket, so — unlike min/max — one of two opposing extremes falling in the same bucket can be dropped: shape fidelity instead of envelope guarantee.
38
+
39
+ `resample_plot_minmax_lttb` is the hybrid (MinMaxLTTB, Van der Donckt et al., 2023; the plotly-resampler default): on large inputs a minmax pass preselects the per bucket extremes and LTTB runs over those candidates only. Visually close to pure LTTB and faster, with a gap that grows with input size — about 1.6x end to end at 100,000 points, and about 3.4x on the resampling pass alone at 1,000,000 — and the true extremes are always among the candidates.
40
+
41
+ All four plot resamplers work in sample order — they bucket by index, so X is expected to be already sorted, as in a time series (`resample_scatter` makes no ordering assumption). They keep at most `width * 4` points and `resample_scatter` keeps at most `width * 2 * height`, so plotille only receives what the canvas can actually display.
42
+
43
+ ## Benchmark
44
+
45
+ End to end times: building the plot string with plotille alone versus resampling first. Measured with `benchmarks/bench.py` (canvas 80x40, best of 3) on Python 3.14, Linux, Intel Core i5-1135G7:
46
+
47
+ | Points | plotille alone | stride + plotille | minmax + plotille | lttb + plotille | mmlttb + plotille |
48
+ | ------- | -------------- | ----------------- | ----------------- | --------------- | ----------------- |
49
+ | 10,000 | 202 ms | 23 ms | 22 ms | 27 ms | 24 ms |
50
+ | 100,000 | 1.75 s | 39 ms | 52 ms | 90 ms | 56 ms |
51
+
52
+ Reproduce it from the repository root with:
53
+
54
+ ```
55
+ uv run --group bench benchmarks/bench.py
56
+ ```
57
+
58
+ The resampling-pass figure quoted in Why resample? (pure LTTB vs MinMaxLTTB at 1,000,000 points) has its own script:
59
+
60
+ ```
61
+ uv run benchmarks/bench_resamplers.py
62
+ ```
63
+
64
+ ## Installation
65
+ ### Install with UV:
66
+ ```
67
+ uv add plotilleresample
68
+ ```
69
+ ### Install with pip:
70
+ ```
71
+ pip install plotilleresample
72
+ ```
73
+
74
+ plotilleresample has no runtime dependencies — not even plotille: it only reduces sequences. To run the example below, install plotille as well (`uv add plotille` or `pip install plotille`).
75
+
76
+ ## Usage
77
+ ```python
78
+ import math
79
+
80
+ import plotille
81
+
82
+ from plotilleresample import resample_plot_minmax_lttb
83
+
84
+ r = 100_000
85
+ X = list(range(r))
86
+ Y = [math.sin(i / 500) * 100 for i in range(r)]
87
+
88
+ X, Y = resample_plot_minmax_lttb(X, Y, width=80, height=40)
89
+ print(plotille.plot(X, Y, width=80, height=40))
90
+ ```
91
+
92
+ The full interactive demo, running every resampler on the same dataset, lives in [`examples/demo.py`](examples/demo.py).
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from plotilleresample.plotilleresample import resample_plot
4
+ from plotilleresample.plotilleresample import resample_plot_lttb
5
+ from plotilleresample.plotilleresample import resample_plot_minmax
6
+ from plotilleresample.plotilleresample import resample_plot_minmax_lttb
7
+ from plotilleresample.plotilleresample import resample_scatter
8
+
9
+ __all__ = [
10
+ "resample_plot",
11
+ "resample_plot_lttb",
12
+ "resample_plot_minmax",
13
+ "resample_plot_minmax_lttb",
14
+ "resample_scatter",
15
+ ]
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from collections.abc import Sequence
4
+ from math import ceil
5
+
6
+ # Historically chosen to reduce Nyquist/undersampling-related artifacts.
7
+ plot_multiplier = 4
8
+
9
+ # Preselection ratio for resample_plot_minmax_lttb, as in plotly-resampler.
10
+ minmax_ratio = 4
11
+
12
+
13
+ def _check_input(
14
+ X: Sequence[float],
15
+ Y: Sequence[float],
16
+ width: int
17
+ ) -> None:
18
+ if len(X) != len(Y):
19
+ raise ValueError(
20
+ "X and Y must have the same number of entries: "
21
+ f"{len(X)} != {len(Y)}"
22
+ )
23
+
24
+ if width <= 0:
25
+ raise ValueError(f"width must be positive: {width}")
26
+
27
+
28
+ def _minmax_indices(
29
+ Y: Sequence[float],
30
+ buckets: int
31
+ ) -> list[int]:
32
+ n = len(Y)
33
+
34
+ idxs = []
35
+ for b in range(buckets):
36
+ start = b * n // buckets
37
+ stop = (b + 1) * n // buckets
38
+
39
+ i_min = i_max = start
40
+ for i in range(start + 1, stop):
41
+ if Y[i] < Y[i_min]:
42
+ i_min = i
43
+ elif Y[i] > Y[i_max]:
44
+ i_max = i
45
+
46
+ # In index order so a sorted X stays sorted.
47
+ idxs.extend(sorted({i_min, i_max}))
48
+
49
+ return idxs
50
+
51
+
52
+ def _lttb(
53
+ X: Sequence[float],
54
+ Y: Sequence[float],
55
+ n_out: int
56
+ ) -> tuple[list[float], list[float]]:
57
+ n = len(X)
58
+
59
+ # First and last point always survive; one point per bucket
60
+ # in between, for exactly n_out points.
61
+ m = n_out - 2
62
+
63
+ new_X = [X[0]]
64
+ new_Y = [Y[0]]
65
+
66
+ a = 0
67
+ for b in range(m):
68
+ start = 1 + b * (n - 2) // m
69
+ stop = 1 + (b + 1) * (n - 2) // m
70
+
71
+ # A is the previously selected point; C is the average of
72
+ # the next bucket (the last point for the final bucket).
73
+ if b + 1 < m:
74
+ next_stop = 1 + (b + 2) * (n - 2) // m
75
+ span = next_stop - stop
76
+ c_x = sum(X[stop:next_stop]) / span
77
+ c_y = sum(Y[stop:next_stop]) / span
78
+ else:
79
+ c_x = X[n - 1]
80
+ c_y = Y[n - 1]
81
+
82
+ a_x = X[a]
83
+ a_y = Y[a]
84
+
85
+ best = start
86
+ best_area = -1.0
87
+ for i in range(start, stop):
88
+ area = abs(
89
+ (a_x - c_x) * (Y[i] - a_y)
90
+ - (a_x - X[i]) * (c_y - a_y)
91
+ )
92
+ if area > best_area:
93
+ best_area = area
94
+ best = i
95
+
96
+ new_X.append(X[best])
97
+ new_Y.append(Y[best])
98
+ a = best
99
+
100
+ new_X.append(X[n - 1])
101
+ new_Y.append(Y[n - 1])
102
+
103
+ return new_X, new_Y
104
+
105
+
106
+ def resample_plot(
107
+ X: Sequence[float],
108
+ Y: Sequence[float],
109
+ width: int = 80,
110
+ height: int = 40
111
+ ) -> tuple[Sequence[float], Sequence[float]]:
112
+ """
113
+ Works in sample order: X is expected to be already sorted,
114
+ as in a time series.
115
+
116
+ :param X: Sequence[float]: X values.
117
+ :param Y: Sequence[float]: Y values.
118
+ :param width: int: Width of the plot. (Default value = 80)
119
+ :param height: int: Unused; kept for signature symmetry
120
+ with resample_scatter. (Default value = 40)
121
+
122
+ """
123
+ _check_input(X, Y, width)
124
+
125
+ if len(X) > width * plot_multiplier:
126
+ step = ceil(len(X) / (width * plot_multiplier))
127
+
128
+ X = [
129
+ X[i] for i in range(
130
+ 0, len(X), step
131
+ )
132
+ ]
133
+ Y = [
134
+ Y[i] for i in range(
135
+ 0, len(Y), step
136
+ )
137
+ ]
138
+
139
+ return X, Y
140
+
141
+
142
+ def resample_plot_minmax(
143
+ X: Sequence[float],
144
+ Y: Sequence[float],
145
+ width: int = 80,
146
+ height: int = 40
147
+ ) -> tuple[Sequence[float], Sequence[float]]:
148
+ """
149
+ Like resample_plot, but it keeps the minimum and the maximum
150
+ Y of each bucket, so peaks in high frequency data survive
151
+ the resampling. Buckets are formed in sample order: X is
152
+ expected to be already sorted, as in a time series.
153
+
154
+ :param X: Sequence[float]: X values.
155
+ :param Y: Sequence[float]: Y values.
156
+ :param width: int: Width of the plot. (Default value = 80)
157
+ :param height: int: Unused; kept for signature symmetry
158
+ with resample_scatter. (Default value = 40)
159
+
160
+ """
161
+ _check_input(X, Y, width)
162
+
163
+ if len(X) > width * plot_multiplier:
164
+ # One bucket per braille dot column (2 per char); min and max
165
+ # per bucket keep the output at most width * plot_multiplier.
166
+ idxs = _minmax_indices(Y, width * plot_multiplier // 2)
167
+
168
+ X = [X[i] for i in idxs]
169
+ Y = [Y[i] for i in idxs]
170
+
171
+ return X, Y
172
+
173
+
174
+ def resample_plot_lttb(
175
+ X: Sequence[float],
176
+ Y: Sequence[float],
177
+ width: int = 80,
178
+ height: int = 40
179
+ ) -> tuple[Sequence[float], Sequence[float]]:
180
+ """
181
+ Like resample_plot, but it applies Largest-Triangle-Three-Buckets
182
+ (Steinarsson, 2013): it always keeps the first and the last point
183
+ and picks the most shape-representative point of each bucket, so
184
+ the reduced line looks like the original. Unlike
185
+ resample_plot_minmax it keeps one point per bucket, so one of two
186
+ opposing extremes falling in the same bucket can be dropped.
187
+ Buckets are formed in sample order: X is expected to be already
188
+ sorted, as in a time series.
189
+
190
+ :param X: Sequence[float]: X values.
191
+ :param Y: Sequence[float]: Y values.
192
+ :param width: int: Width of the plot. (Default value = 80)
193
+ :param height: int: Unused; kept for signature symmetry
194
+ with resample_scatter. (Default value = 40)
195
+
196
+ """
197
+ _check_input(X, Y, width)
198
+
199
+ if len(X) > width * plot_multiplier:
200
+ X, Y = _lttb(X, Y, width * plot_multiplier)
201
+
202
+ return X, Y
203
+
204
+
205
+ def resample_plot_minmax_lttb(
206
+ X: Sequence[float],
207
+ Y: Sequence[float],
208
+ width: int = 80,
209
+ height: int = 40
210
+ ) -> tuple[Sequence[float], Sequence[float]]:
211
+ """
212
+ Like resample_plot_lttb, but on large inputs it first preselects
213
+ the per bucket extremes with a minmax pass at minmax_ratio finer
214
+ granularity and runs LTTB over those candidates (MinMaxLTTB,
215
+ Van der Donckt et al., 2023; the plotly-resampler default).
216
+ Faster than pure LTTB, with a gap that grows with input size;
217
+ the true extremes are always among the candidates, and the
218
+ result stays visually close to pure LTTB. Buckets are
219
+ formed in sample order: X is expected to be already sorted,
220
+ as in a time series.
221
+
222
+ :param X: Sequence[float]: X values.
223
+ :param Y: Sequence[float]: Y values.
224
+ :param width: int: Width of the plot. (Default value = 80)
225
+ :param height: int: Unused; kept for signature symmetry
226
+ with resample_scatter. (Default value = 40)
227
+
228
+ """
229
+ _check_input(X, Y, width)
230
+
231
+ n_out = width * plot_multiplier
232
+ if len(X) > n_out:
233
+ if len(X) > n_out * minmax_ratio:
234
+ idxs = _minmax_indices(Y, n_out * minmax_ratio // 2)
235
+
236
+ # LTTB anchors on the endpoints, so force them in.
237
+ if idxs[0] != 0:
238
+ idxs.insert(0, 0)
239
+ if idxs[-1] != len(X) - 1:
240
+ idxs.append(len(X) - 1)
241
+
242
+ X = [X[i] for i in idxs]
243
+ Y = [Y[i] for i in idxs]
244
+
245
+ X, Y = _lttb(X, Y, n_out)
246
+
247
+ return X, Y
248
+
249
+
250
+ def resample_scatter(
251
+ X: Sequence[float],
252
+ Y: Sequence[float],
253
+ width: int = 80,
254
+ height: int = 40
255
+ ) -> tuple[Sequence[float], Sequence[float]]:
256
+ """
257
+
258
+ :param X: Sequence[float]: X values.
259
+ :param Y: Sequence[float]: Y values.
260
+ :param width: int: Width of the plot. (Default value = 80)
261
+ :param height: int: Height of the plot. (Default value = 40)
262
+
263
+ """
264
+ _check_input(X, Y, width)
265
+
266
+ if height <= 0:
267
+ raise ValueError(f"height must be positive: {height}")
268
+
269
+ scatter_multiplier = width * 2 * height
270
+ if len(X) > scatter_multiplier:
271
+ step = ceil(len(X) / scatter_multiplier)
272
+
273
+ X = [
274
+ X[i] for i in range(
275
+ 0, len(X), step
276
+ )
277
+ ]
278
+ Y = [
279
+ Y[i] for i in range(
280
+ 0, len(Y), step
281
+ )
282
+ ]
283
+
284
+ return X, Y
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: plotilleresample
3
+ Version: 1.0
4
+ Summary: Python module to resample datasets before plotting with Plotille.
5
+ Author-email: "Carlos A. Planchón" <carlosandresplanchonprestes@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/carlosplanchon/plotilleresample
8
+ Keywords: plotting,ascii,math,resample
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Scientific/Engineering :: Visualization
11
+ Classifier: Topic :: Terminals
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # plotilleresample
24
+ ![plotilleresample banner](assets/banner-v2.jpg)
25
+
26
+ *Python module to resample datasets before plotting with Plotille.*
27
+
28
+ [![CI](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml/badge.svg)](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml)
29
+ [![PyPI version](https://img.shields.io/pypi/v/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
30
+ [![Python versions](https://img.shields.io/pypi/pyversions/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
+
33
+ ## Why resample?
34
+
35
+ Plotille rasterizes to a terminal canvas of braille dots: `width * 2` columns by `height * 4` rows. Feeding it far more points than that costs interpolation time on detail the canvas cannot show. Plotille rasterizes; plotilleresample decides what information deserves to reach the rasterizer:
36
+
37
+ | Function | Strategy | Best for |
38
+ | ---------------------- | ------------------ | --------------------------------- |
39
+ | `resample_plot` | uniform stride | smooth lines, cheapest reduction |
40
+ | `resample_plot_minmax` | min/max per bucket | peaks, oscillations, time series |
41
+ | `resample_plot_lttb` | largest triangle per bucket | shape-faithful single line |
42
+ | `resample_plot_minmax_lttb` | minmax preselection + LTTB | shape-faithful line, large inputs |
43
+ | `resample_scatter` | uniform stride | large scatter inputs |
44
+
45
+ The uniform stride keeps one point every N: fast and predictable, but a narrow peak that falls between two kept points disappears from the plot. `resample_plot_minmax` instead makes one bucket per braille dot column and keeps the minimum and the maximum Y of each bucket, so the envelope of the signal — spikes included — always survives:
46
+
47
+ ```python
48
+ X = list(range(10000))
49
+ Y = [0.0] * 10000
50
+ Y[33] = 1000.0 # a narrow spike
51
+
52
+ _, y_stride = plotilleresample.resample_plot(X, Y)
53
+ _, y_minmax = plotilleresample.resample_plot_minmax(X, Y)
54
+
55
+ 1000.0 in y_stride # False: the spike vanished
56
+ 1000.0 in y_minmax # True: the envelope survives
57
+ ```
58
+
59
+ `resample_plot_lttb` implements Largest-Triangle-Three-Buckets (Steinarsson, 2013): it always keeps the first and the last point and picks the most shape-representative point of each bucket, giving a single clean line that looks like the original. It keeps one point per bucket, so — unlike min/max — one of two opposing extremes falling in the same bucket can be dropped: shape fidelity instead of envelope guarantee.
60
+
61
+ `resample_plot_minmax_lttb` is the hybrid (MinMaxLTTB, Van der Donckt et al., 2023; the plotly-resampler default): on large inputs a minmax pass preselects the per bucket extremes and LTTB runs over those candidates only. Visually close to pure LTTB and faster, with a gap that grows with input size — about 1.6x end to end at 100,000 points, and about 3.4x on the resampling pass alone at 1,000,000 — and the true extremes are always among the candidates.
62
+
63
+ All four plot resamplers work in sample order — they bucket by index, so X is expected to be already sorted, as in a time series (`resample_scatter` makes no ordering assumption). They keep at most `width * 4` points and `resample_scatter` keeps at most `width * 2 * height`, so plotille only receives what the canvas can actually display.
64
+
65
+ ## Benchmark
66
+
67
+ End to end times: building the plot string with plotille alone versus resampling first. Measured with `benchmarks/bench.py` (canvas 80x40, best of 3) on Python 3.14, Linux, Intel Core i5-1135G7:
68
+
69
+ | Points | plotille alone | stride + plotille | minmax + plotille | lttb + plotille | mmlttb + plotille |
70
+ | ------- | -------------- | ----------------- | ----------------- | --------------- | ----------------- |
71
+ | 10,000 | 202 ms | 23 ms | 22 ms | 27 ms | 24 ms |
72
+ | 100,000 | 1.75 s | 39 ms | 52 ms | 90 ms | 56 ms |
73
+
74
+ Reproduce it from the repository root with:
75
+
76
+ ```
77
+ uv run --group bench benchmarks/bench.py
78
+ ```
79
+
80
+ The resampling-pass figure quoted in Why resample? (pure LTTB vs MinMaxLTTB at 1,000,000 points) has its own script:
81
+
82
+ ```
83
+ uv run benchmarks/bench_resamplers.py
84
+ ```
85
+
86
+ ## Installation
87
+ ### Install with UV:
88
+ ```
89
+ uv add plotilleresample
90
+ ```
91
+ ### Install with pip:
92
+ ```
93
+ pip install plotilleresample
94
+ ```
95
+
96
+ plotilleresample has no runtime dependencies — not even plotille: it only reduces sequences. To run the example below, install plotille as well (`uv add plotille` or `pip install plotille`).
97
+
98
+ ## Usage
99
+ ```python
100
+ import math
101
+
102
+ import plotille
103
+
104
+ from plotilleresample import resample_plot_minmax_lttb
105
+
106
+ r = 100_000
107
+ X = list(range(r))
108
+ Y = [math.sin(i / 500) * 100 for i in range(r)]
109
+
110
+ X, Y = resample_plot_minmax_lttb(X, Y, width=80, height=40)
111
+ print(plotille.plot(X, Y, width=80, height=40))
112
+ ```
113
+
114
+ The full interactive demo, running every resampler on the same dataset, lives in [`examples/demo.py`](examples/demo.py).
@@ -1,8 +1,10 @@
1
+ LICENSE
1
2
  README.md
2
- setup.py
3
+ pyproject.toml
3
4
  plotilleresample/__init__.py
4
5
  plotilleresample/plotilleresample.py
5
6
  plotilleresample.egg-info/PKG-INFO
6
7
  plotilleresample.egg-info/SOURCES.txt
7
8
  plotilleresample.egg-info/dependency_links.txt
8
- plotilleresample.egg-info/top_level.txt
9
+ plotilleresample.egg-info/top_level.txt
10
+ tests/test_plotilleresample.py
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "plotilleresample"
7
+ version = "1.0"
8
+ description = "Python module to resample datasets before plotting with Plotille."
9
+ readme = "README.md"
10
+
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+
14
+ authors = [
15
+ {name = "Carlos A. Planchón", email = "carlosandresplanchonprestes@gmail.com"}
16
+ ]
17
+ keywords = ["plotting", "ascii", "math", "resample"]
18
+ classifiers = [
19
+ "Intended Audience :: Developers",
20
+ "Topic :: Scientific/Engineering :: Visualization",
21
+ "Topic :: Terminals",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14"
28
+
29
+ ]
30
+ requires-python = ">=3.10"
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/carlosplanchon/plotilleresample"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["."]
37
+ include = ["plotilleresample*"]
38
+
39
+ [dependency-groups]
40
+ dev = ["pytest>=8"]
41
+ bench = ["plotille"]
42
+
43
+ [tool.pytest.ini_options]
44
+ testpaths = ["tests"]
45
+
46
+ [tool.ruff.lint]
47
+ # Select the classic default ruleset explicitly (pyflakes + a pycodestyle
48
+ # subset) so linting stays deterministic across ruff releases. ruff 0.16
49
+ # widened its implicit default, which broke `uvx ruff check` in CI.
50
+ select = ["E4", "E7", "E9", "F"]
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ Tests for plotilleresample.
5
+
6
+ Run with pytest, or directly:
7
+ python tests/test_plotilleresample.py
8
+ """
9
+
10
+ import math
11
+ import sys
12
+
13
+ from pathlib import Path
14
+
15
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
16
+
17
+ from plotilleresample import resample_plot
18
+ from plotilleresample import resample_plot_lttb
19
+ from plotilleresample import resample_plot_minmax
20
+ from plotilleresample import resample_plot_minmax_lttb
21
+ from plotilleresample import resample_scatter
22
+
23
+
24
+ def test_resample_plot_size():
25
+ r = 10000
26
+ X = list(range(r))
27
+ Y = [math.sin(i / 100) * 100 for i in range(r)]
28
+ xp, yp = resample_plot(X, Y, 80, 40)
29
+ assert len(xp) == len(yp)
30
+ assert len(xp) <= 80 * 4
31
+
32
+
33
+ def test_resample_scatter_size():
34
+ for r in (9000, 10000):
35
+ xs, ys = resample_scatter(list(range(r)), list(range(r)), 80, 40)
36
+ assert len(xs) == len(ys)
37
+ assert len(xs) <= 80 * 2 * 40
38
+
39
+
40
+ def test_minmax_passthrough_short_input():
41
+ X = [1.0, 2.0]
42
+ Y = [3.0, 4.0]
43
+ assert resample_plot_minmax(X, Y, 80, 40) == (X, Y)
44
+ assert resample_plot_minmax([], [], 80, 40) == ([], [])
45
+
46
+
47
+ def test_minmax_size_within_target():
48
+ r = 10000
49
+ X = list(range(r))
50
+ Y = [math.sin(i / 100) * 100 for i in range(r)]
51
+ xm, ym = resample_plot_minmax(X, Y, 80, 40)
52
+ assert len(xm) == len(ym)
53
+ assert len(xm) <= 80 * 4
54
+
55
+
56
+ def test_minmax_x_stays_sorted():
57
+ r = 10000
58
+ X = list(range(r))
59
+ Y = [math.sin(i / 7) * 100 for i in range(r)]
60
+ xm, _ = resample_plot_minmax(X, Y, 80, 40)
61
+ assert all(a <= b for a, b in zip(xm, xm[1:]))
62
+
63
+
64
+ def test_minmax_preserves_envelope():
65
+ r = 10000
66
+ X = list(range(r))
67
+ Y = [math.sin(i / 100) * 100 for i in range(r)]
68
+ _, ym = resample_plot_minmax(X, Y, 80, 40)
69
+ assert max(ym) == max(Y)
70
+ assert min(ym) == min(Y)
71
+
72
+
73
+ def test_spike_survives_minmax_but_not_stride():
74
+ # A flat signal with one spike placed off the stride grid:
75
+ # resample_plot with 10000 points and width 80 keeps indices
76
+ # 0, 32, 64, ... (step = ceil(10000 / 320) = 32), so the spike
77
+ # at index 33 is dropped. The minmax bucket containing index 33
78
+ # keeps it as the bucket maximum.
79
+ r = 10000
80
+ X = list(range(r))
81
+ Y = [0.0] * r
82
+ Y[33] = 1000.0
83
+
84
+ _, y_stride = resample_plot(X, Y, 80, 40)
85
+ _, y_minmax = resample_plot_minmax(X, Y, 80, 40)
86
+
87
+ assert 1000.0 not in y_stride
88
+ assert 1000.0 in y_minmax
89
+
90
+
91
+ def test_minmax_constant_data_dedups():
92
+ # min and max coincide in every bucket -> one point per bucket.
93
+ r = 10000
94
+ X = list(range(r))
95
+ Y = [5.0] * r
96
+ xm, ym = resample_plot_minmax(X, Y, 80, 40)
97
+ assert len(xm) == 80 * 4 // 2
98
+ assert set(ym) == {5.0}
99
+
100
+
101
+ def test_lttb_size_exact():
102
+ r = 10000
103
+ X = list(range(r))
104
+ Y = [math.sin(i / 100) * 100 for i in range(r)]
105
+ xl, yl = resample_plot_lttb(X, Y, 80, 40)
106
+ assert len(xl) == len(yl) == 80 * 4
107
+
108
+
109
+ def test_lttb_passthrough_short_input():
110
+ X = [1.0, 2.0]
111
+ Y = [3.0, 4.0]
112
+ assert resample_plot_lttb(X, Y, 80, 40) == (X, Y)
113
+ assert resample_plot_lttb([], [], 80, 40) == ([], [])
114
+
115
+
116
+ def test_lttb_keeps_endpoints_and_sorted_x():
117
+ r = 10000
118
+ X = list(range(r))
119
+ Y = [math.sin(i / 7) * 100 for i in range(r)]
120
+ xl, yl = resample_plot_lttb(X, Y, 80, 40)
121
+ assert xl[0] == X[0] and yl[0] == Y[0]
122
+ assert xl[-1] == X[-1] and yl[-1] == Y[-1]
123
+ assert all(a <= b for a, b in zip(xl, xl[1:]))
124
+
125
+
126
+ def test_lttb_keeps_a_solitary_spike():
127
+ r = 10000
128
+ X = list(range(r))
129
+ Y = [0.0] * r
130
+ Y[33] = 1000.0
131
+ _, yl = resample_plot_lttb(X, Y, 80, 40)
132
+ assert 1000.0 in yl
133
+
134
+
135
+ def test_lttb_drops_one_of_two_opposing_extremes():
136
+ # The documented trade-off: with a peak and a valley falling in
137
+ # the same bucket, LTTB keeps exactly one shape point while
138
+ # minmax keeps both extremes.
139
+ r = 10000
140
+ X = list(range(r))
141
+ Y = [0.0] * r
142
+ Y[33] = 1000.0
143
+ Y[40] = -1000.0
144
+
145
+ _, yl = resample_plot_lttb(X, Y, 80, 40)
146
+ _, ym = resample_plot_minmax(X, Y, 80, 40)
147
+
148
+ assert (1000.0 in yl) != (-1000.0 in yl)
149
+ assert 1000.0 in ym and -1000.0 in ym
150
+
151
+
152
+ def test_minmax_lttb_size_endpoints_and_sorted_x():
153
+ r = 10000
154
+ X = list(range(r))
155
+ Y = [math.sin(i / 100) * 100 for i in range(r)]
156
+ xh, yh = resample_plot_minmax_lttb(X, Y, 80, 40)
157
+ assert len(xh) == len(yh) == 80 * 4
158
+ assert xh[0] == X[0] and yh[0] == Y[0]
159
+ assert xh[-1] == X[-1] and yh[-1] == Y[-1]
160
+ assert all(a <= b for a, b in zip(xh, xh[1:]))
161
+
162
+
163
+ def test_minmax_lttb_passthrough_short_input():
164
+ X = [1.0, 2.0]
165
+ Y = [3.0, 4.0]
166
+ assert resample_plot_minmax_lttb(X, Y, 80, 40) == (X, Y)
167
+ assert resample_plot_minmax_lttb([], [], 80, 40) == ([], [])
168
+
169
+
170
+ def test_minmax_lttb_keeps_a_solitary_spike():
171
+ r = 10000
172
+ X = list(range(r))
173
+ Y = [0.0] * r
174
+ Y[33] = 1000.0
175
+ _, yh = resample_plot_minmax_lttb(X, Y, 80, 40)
176
+ assert 1000.0 in yh
177
+
178
+
179
+ def test_minmax_lttb_matches_pure_lttb_below_ratio_threshold():
180
+ # With n_out < len(X) <= n_out * minmax_ratio there is no
181
+ # preselection, so hybrid and pure LTTB must return the same points.
182
+ r = 1000
183
+ X = list(range(r))
184
+ Y = [math.sin(i / 7) * 100 for i in range(r)]
185
+ assert (
186
+ resample_plot_minmax_lttb(X, Y, 80, 40)
187
+ == resample_plot_lttb(X, Y, 80, 40)
188
+ )
189
+
190
+
191
+ def _raises_value_error(fn, *args):
192
+ try:
193
+ fn(*args)
194
+ except ValueError:
195
+ return True
196
+ return False
197
+
198
+
199
+ def test_length_mismatch_raises():
200
+ X = list(range(1000))
201
+ Y = list(range(500))
202
+ for fn in (
203
+ resample_plot, resample_plot_lttb, resample_plot_minmax,
204
+ resample_plot_minmax_lttb, resample_scatter,
205
+ ):
206
+ assert _raises_value_error(fn, X, Y, 80, 40), fn.__name__
207
+ # Fail fast even when the input is below the resampling threshold.
208
+ assert _raises_value_error(resample_plot, [1.0], [1.0, 2.0], 80, 40)
209
+
210
+
211
+ def test_non_positive_width_raises():
212
+ X = list(range(1000))
213
+ for fn in (
214
+ resample_plot, resample_plot_lttb, resample_plot_minmax,
215
+ resample_plot_minmax_lttb, resample_scatter,
216
+ ):
217
+ for w in (0, -5):
218
+ assert _raises_value_error(fn, X, X, w, 40), fn.__name__
219
+
220
+
221
+ def test_non_positive_height_raises_only_in_scatter():
222
+ X = list(range(1000))
223
+ for h in (0, -5):
224
+ assert _raises_value_error(resample_scatter, X, X, 80, h)
225
+ # height is unused in the plot resamplers, so it stays permissive.
226
+ assert not _raises_value_error(resample_plot, X, X, 80, h)
227
+ assert not _raises_value_error(resample_plot_lttb, X, X, 80, h)
228
+ assert not _raises_value_error(resample_plot_minmax, X, X, 80, h)
229
+ assert not _raises_value_error(resample_plot_minmax_lttb, X, X, 80, h)
230
+
231
+
232
+ if __name__ == "__main__":
233
+ tests = [
234
+ fn for name, fn in sorted(globals().items())
235
+ if name.startswith("test_")
236
+ ]
237
+ for fn in tests:
238
+ fn()
239
+ print(f"{fn.__name__} OK")
240
+ print(f"--- {len(tests)} tests OK ---")
@@ -1,81 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: plotilleresample
3
- Version: 0.4
4
- Summary: Python3 module to resample datasetsbefore plotting with Plotille.
5
- Home-page: https://github.com/carlosplanchon/plotilleresample
6
- Author: Carlos A. Planchón
7
- Author-email: bubbledoloresuruguay2@gmail.com
8
- License: GPL3
9
- Download-URL: https://github.com/carlosplanchon/plotilleresample/archive/v0.4.tar.gz
10
- Description: # plotilleresample
11
- *Python3 module to resample datasets before plotting with Plotille.*
12
-
13
- # Rationale
14
- I want to optimize plot and scatter function of plotille.
15
-
16
- ## Installation
17
- ### Install with pip
18
- ```
19
- pip3 install -U servusresample
20
- ```
21
-
22
- ## Usage
23
- ```
24
- #!/usr/bin/env python3
25
-
26
- import plotille
27
-
28
- import plotilledimreduction
29
-
30
- import math
31
-
32
- from shutil import get_terminal_size
33
-
34
- from vtclear import clear_screen
35
-
36
- import numpy as np
37
-
38
-
39
- w = get_terminal_size().columns - 20
40
- h = get_terminal_size().lines - 7
41
-
42
- r = 10000
43
- res = np.random.normal(size=r)
44
-
45
- # Here I'm testing stuffs with histograms.
46
- # input("Histogram:")
47
- # print(plotille.histogram(res, bins=w*2, width=w, height=h))
48
-
49
- X = [i for i in range(r)]
50
- Y = [math.sin(i / 100) * 100 for i in range(r)]
51
-
52
- print(" · Scatter...")
53
- xs, ys = plotilledimreduction.dim_reduction_scatter(X, Y, w, h)
54
- print(" · Plot...")
55
- xp, yp = plotilledimreduction.dim_reduction_plot(X, Y, w, h)
56
- print(" --- READY ---")
57
-
58
- print(f"Len plot.x {len(xp)}")
59
-
60
- print(f"Len scatter.x {len(xs)}")
61
-
62
- input("Plot:")
63
- clear_screen()
64
- print(plotille.plot(xp, yp, w, h))
65
-
66
- input("Scatter:")
67
- clear_screen()
68
- print(plotille.plot(xs, ys, w, h))
69
- ```
70
-
71
- Keywords: plotting,ascii,math,resample
72
- Platform: UNKNOWN
73
- Classifier: Intended Audience :: Developers
74
- Classifier: Topic :: Software Development :: Build Tools
75
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
76
- Classifier: Programming Language :: Python :: 3
77
- Classifier: Programming Language :: Python :: 3.4
78
- Classifier: Programming Language :: Python :: 3.5
79
- Classifier: Programming Language :: Python :: 3.6
80
- Classifier: Programming Language :: Python :: 3.7
81
- Description-Content-Type: text/markdown
@@ -1,60 +0,0 @@
1
- # plotilleresample
2
- *Python3 module to resample datasets before plotting with Plotille.*
3
-
4
- # Rationale
5
- I want to optimize plot and scatter function of plotille.
6
-
7
- ## Installation
8
- ### Install with pip
9
- ```
10
- pip3 install -U servusresample
11
- ```
12
-
13
- ## Usage
14
- ```
15
- #!/usr/bin/env python3
16
-
17
- import plotille
18
-
19
- import plotilledimreduction
20
-
21
- import math
22
-
23
- from shutil import get_terminal_size
24
-
25
- from vtclear import clear_screen
26
-
27
- import numpy as np
28
-
29
-
30
- w = get_terminal_size().columns - 20
31
- h = get_terminal_size().lines - 7
32
-
33
- r = 10000
34
- res = np.random.normal(size=r)
35
-
36
- # Here I'm testing stuffs with histograms.
37
- # input("Histogram:")
38
- # print(plotille.histogram(res, bins=w*2, width=w, height=h))
39
-
40
- X = [i for i in range(r)]
41
- Y = [math.sin(i / 100) * 100 for i in range(r)]
42
-
43
- print(" · Scatter...")
44
- xs, ys = plotilledimreduction.dim_reduction_scatter(X, Y, w, h)
45
- print(" · Plot...")
46
- xp, yp = plotilledimreduction.dim_reduction_plot(X, Y, w, h)
47
- print(" --- READY ---")
48
-
49
- print(f"Len plot.x {len(xp)}")
50
-
51
- print(f"Len scatter.x {len(xs)}")
52
-
53
- input("Plot:")
54
- clear_screen()
55
- print(plotille.plot(xp, yp, w, h))
56
-
57
- input("Scatter:")
58
- clear_screen()
59
- print(plotille.plot(xs, ys, w, h))
60
- ```
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from plotilleresample.plotilleresample import resample_plot
4
- from plotilleresample.plotilleresample import resample_scatter
@@ -1,75 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from typing import List, Tuple
4
-
5
- # This value is used to avoid Nyquist related problems.
6
- plot_multiplier = 4
7
-
8
- list_val = List[int]
9
-
10
-
11
- def resample_plot(
12
- X: list_val,
13
- Y: list_val,
14
- width: int = 80,
15
- height: int = 40
16
- ) -> Tuple[list_val, list_val]:
17
- """
18
-
19
- :param X: list_val: List of X values.
20
- :param Y: list_val: List of Y values.
21
- :param width: int: Width of the plot. (Default value = 80)
22
- :param height: int: Height of the plot. (Default value = 40)
23
-
24
- """
25
- if len(X) > width * plot_multiplier:
26
- step = round(len(X) / width / plot_multiplier)
27
- print(step)
28
-
29
- if step != 0:
30
- X = [
31
- X[i] for i in range(
32
- 0, len(X), step
33
- )
34
- ]
35
- Y = [
36
- Y[i] for i in range(
37
- 0, len(Y), step
38
- )
39
- ]
40
-
41
- return X, Y
42
-
43
-
44
- def resample_scatter(
45
- X: list_val,
46
- Y: list_val,
47
- width: int = 80,
48
- height: int = 40
49
- ) -> Tuple[list_val, list_val]:
50
- """
51
-
52
- :param X: list_val: List of X values.
53
- :param Y: list_val: List of Y values.
54
- :param width: int: Width of the plot. (Default value = 80)
55
- :param height: int: Height of the plot. (Default value = 40)
56
-
57
- """
58
- scatter_multiplier = width * 2 * height
59
- if len(X) > scatter_multiplier:
60
- step = round(len(X) / scatter_multiplier)
61
- print(step)
62
-
63
- if step != 0:
64
- X = [
65
- X[i] for i in range(
66
- 0, len(X), step
67
- )
68
- ]
69
- Y = [
70
- Y[i] for i in range(
71
- 0, len(Y), step
72
- )
73
- ]
74
-
75
- return X, Y
@@ -1,81 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: plotilleresample
3
- Version: 0.4
4
- Summary: Python3 module to resample datasetsbefore plotting with Plotille.
5
- Home-page: https://github.com/carlosplanchon/plotilleresample
6
- Author: Carlos A. Planchón
7
- Author-email: bubbledoloresuruguay2@gmail.com
8
- License: GPL3
9
- Download-URL: https://github.com/carlosplanchon/plotilleresample/archive/v0.4.tar.gz
10
- Description: # plotilleresample
11
- *Python3 module to resample datasets before plotting with Plotille.*
12
-
13
- # Rationale
14
- I want to optimize plot and scatter function of plotille.
15
-
16
- ## Installation
17
- ### Install with pip
18
- ```
19
- pip3 install -U servusresample
20
- ```
21
-
22
- ## Usage
23
- ```
24
- #!/usr/bin/env python3
25
-
26
- import plotille
27
-
28
- import plotilledimreduction
29
-
30
- import math
31
-
32
- from shutil import get_terminal_size
33
-
34
- from vtclear import clear_screen
35
-
36
- import numpy as np
37
-
38
-
39
- w = get_terminal_size().columns - 20
40
- h = get_terminal_size().lines - 7
41
-
42
- r = 10000
43
- res = np.random.normal(size=r)
44
-
45
- # Here I'm testing stuffs with histograms.
46
- # input("Histogram:")
47
- # print(plotille.histogram(res, bins=w*2, width=w, height=h))
48
-
49
- X = [i for i in range(r)]
50
- Y = [math.sin(i / 100) * 100 for i in range(r)]
51
-
52
- print(" · Scatter...")
53
- xs, ys = plotilledimreduction.dim_reduction_scatter(X, Y, w, h)
54
- print(" · Plot...")
55
- xp, yp = plotilledimreduction.dim_reduction_plot(X, Y, w, h)
56
- print(" --- READY ---")
57
-
58
- print(f"Len plot.x {len(xp)}")
59
-
60
- print(f"Len scatter.x {len(xs)}")
61
-
62
- input("Plot:")
63
- clear_screen()
64
- print(plotille.plot(xp, yp, w, h))
65
-
66
- input("Scatter:")
67
- clear_screen()
68
- print(plotille.plot(xs, ys, w, h))
69
- ```
70
-
71
- Keywords: plotting,ascii,math,resample
72
- Platform: UNKNOWN
73
- Classifier: Intended Audience :: Developers
74
- Classifier: Topic :: Software Development :: Build Tools
75
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
76
- Classifier: Programming Language :: Python :: 3
77
- Classifier: Programming Language :: Python :: 3.4
78
- Classifier: Programming Language :: Python :: 3.5
79
- Classifier: Programming Language :: Python :: 3.6
80
- Classifier: Programming Language :: Python :: 3.7
81
- Description-Content-Type: text/markdown
@@ -1,34 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- from setuptools import setup
4
-
5
-
6
- with open("README.md", "r") as f:
7
- readme = f.read()
8
-
9
- setup(
10
- name="plotilleresample",
11
- packages=["plotilleresample"],
12
- version="0.4",
13
- license="GPL3",
14
- description="Python3 module to resample datasets"
15
- "before plotting with Plotille.",
16
- long_description=readme,
17
- long_description_content_type="text/markdown",
18
- author="Carlos A. Planchón",
19
- author_email="bubbledoloresuruguay2@gmail.com",
20
- url="https://github.com/carlosplanchon/plotilleresample",
21
- download_url="https://github.com/carlosplanchon/"
22
- "plotilleresample/archive/v0.4.tar.gz",
23
- keywords=["plotting", "ascii", "math", "resample"],
24
- classifiers=[
25
- "Intended Audience :: Developers",
26
- "Topic :: Software Development :: Build Tools",
27
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
28
- "Programming Language :: Python :: 3",
29
- "Programming Language :: Python :: 3.4",
30
- "Programming Language :: Python :: 3.5",
31
- "Programming Language :: Python :: 3.6",
32
- "Programming Language :: Python :: 3.7",
33
- ],
34
- )
File without changes