Metadata-Version: 2.4
Name: plotilleresample
Version: 1.2.0
Summary: Python module to resample datasets before plotting with Plotille.
Author-email: "Carlos A. Planchón" <carlosandresplanchonprestes@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/carlosplanchon/plotilleresample
Keywords: plotting,ascii,math,resample,plotille
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Topic :: Terminals
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# plotilleresample
![plotilleresample banner](https://raw.githubusercontent.com/carlosplanchon/plotilleresample/master/assets/banner-v2.jpg)

*Python module to resample datasets before plotting with [Plotille](https://github.com/tammoippen/plotille).*

[![CI](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml/badge.svg)](https://github.com/carlosplanchon/plotilleresample/actions/workflows/ci.yml)
[![PyPI version](https://img.shields.io/pypi/v/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
[![Python versions](https://img.shields.io/pypi/pyversions/plotilleresample.svg)](https://pypi.org/project/plotilleresample/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Why resample?

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:

| Function               | Strategy           | Best for                          |
| ---------------------- | ------------------ | --------------------------------- |
| `resample_plot`        | uniform stride     | smooth lines, cheapest reduction  |
| `resample_plot_minmax` | min/max per bucket | peaks, oscillations, time series  |
| `resample_plot_lttb`   | largest triangle per bucket | shape-faithful single line |
| `resample_plot_minmax_lttb` | minmax preselection + LTTB | shape-faithful line, large inputs |
| `resample_scatter`     | uniform stride     | large scatter inputs              |
| `resample_scatter_minmax` | stride + min/max per bucket | scatter inputs with outliers |

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:

```python
X = list(range(10000))
Y = [0.0] * 10000
Y[33] = 1000.0  # a narrow spike

_, y_stride = plotilleresample.resample_plot(X, Y)
_, y_minmax = plotilleresample.resample_plot_minmax(X, Y)

1000.0 in y_stride  # False: the spike vanished
1000.0 in y_minmax  # True: the envelope survives
```

`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.

`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.

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). X may hold numbers or dates: the min/max and stride resamplers only index it, and the LTTB ones measure their triangles on the spacing between its values. 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. `resample_scatter_minmax` adds the minimum and the maximum of every bucket, and the last point, to that stride: an isolated outlier survives and the axes keep their true range, for at most `width * 4 + 1` points more.

## Benchmark

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:

| Points  | plotille alone | stride + plotille | minmax + plotille | lttb + plotille | mmlttb + plotille |
| ------- | -------------- | ----------------- | ----------------- | --------------- | ----------------- |
| 10,000  | 202 ms         | 23 ms             | 22 ms             | 27 ms           | 24 ms             |
| 100,000 | 1.75 s         | 39 ms             | 52 ms             | 90 ms           | 56 ms             |

Reproduce it from the repository root with:

```
uv run --group bench benchmarks/bench.py
```

The resampling-pass figure quoted in Why resample? (pure LTTB vs MinMaxLTTB at 1,000,000 points) has its own script:

```
uv run benchmarks/bench_resamplers.py
```

## Installation
### Install with UV:
```
uv add plotilleresample
```
### Install with pip:
```
pip install plotilleresample
```

plotilleresample has no runtime dependencies, not even plotille: it only reduces sequences. To run the example below, install [plotille](https://pypi.org/project/plotille/) as well (`uv add plotille` or `pip install plotille`).

## Usage
```python
import math

import plotille

from plotilleresample import resample_plot_minmax_lttb

r = 100_000
X = list(range(r))
Y = [math.sin(i / 500) * 100 for i in range(r)]

X, Y = resample_plot_minmax_lttb(X, Y, width=80, height=40)
print(plotille.plot(X, Y, width=80, height=40))
```

The full interactive demo, running every resampler on the same dataset, lives in [`examples/demo.py`](https://github.com/carlosplanchon/plotilleresample/blob/master/examples/demo.py).

## Acknowledgements

plotilleresample has a narrow purpose, giving Tammo Ippen's [plotille](https://github.com/tammoippen/plotille) only the points its terminal canvas can actually show. plotille now points back to this project from its own README. The min/max strategy came out of the discussion in [plotille#22](https://github.com/tammoippen/plotille/issues/22), opened in 2019.
