Metadata-Version: 2.4
Name: ts_jupyter_viz
Version: 0.1.0
Summary: Fast, zoomable Apache ECharts plots for large time series DataFrames in Jupyter (dynamic kernel-side resampling)
Requires-Python: >=3.13
Requires-Dist: anywidget>=0.11.0
Requires-Dist: numpy>=2.4.6
Requires-Dist: pandas>=3.0.3
Description-Content-Type: text/markdown

# tsviz

Fast, zoomable [Apache ECharts](https://echarts.apache.org/) line charts for **large time series**
(tens of millions of points) from pandas DataFrames, inside Jupyter.

```python
import tsviz

tsviz.plot(df)                                  # all numeric columns vs the index
tsviz.plot(df, x="ts", y=["temp", "pressure"], title="Sensors", bins=5000)
```

## How it works

The full data never leaves the kernel. The browser only ever receives a
downsampled view (`bins` points per series, default 4000) as a compact binary
buffer — no JSON, no CDN:

```
DataFrame ──plot()──▶ kernel keeps full numpy arrays
                          │  initial view: MinMaxLTTB downsample, binary transfer
                          ▼
              ECharts chart (anywidget)
                          │  zoom/pan (mouse wheel, slider, box-select)
                          ▼  debounced window request
              kernel re-downsamples just the visible window  ──▶  chart updates
```

So zooming in progressively reveals true detail: once the visible window holds
fewer than `bins` points you are looking at the **raw data**, while a 50M-point
full view still renders instantly.

- **Downsampling** is MinMaxLTTB (vectorized min/max preselection + vectorized
  LTTB), fully numpy-vectorized — no Python loops on the hot path.
- **ECharts is vendored** into the package and inlined into the widget, so it
  works completely offline.
- Built on [anywidget](https://anywidget.dev), so it works in JupyterLab,
  Jupyter Notebook, and VS Code notebooks.

### Measured performance (this machine)

| operation | latency |
|---|---|
| `plot()` 10M rows × 3 columns | ~0.3 s |
| `plot()` 50M rows × 1 column | ~0.55 s |
| zoom to 50% of 10M×3 | ~100 ms |
| zoom to 5% | ~15 ms |
| deep zoom / restore full view | < 1 ms |

## Setup

```bash
uv sync                 # create .venv and install everything
uv run jupyter lab      # then open demo.ipynb
```

## API

```python
tsviz.plot(data, x=None, y=None, *, bins=4000, height=450, title=None,
           agg="lttb", max_gap=None, secondary_y=None)
```

| parameter | meaning |
|---|---|
| `data` | `DataFrame` or `Series`; every numeric column becomes a line |
| `x` | column for the x-axis; defaults to the index (datetime or numeric) |
| `y` | column name or list of names; defaults to all numeric columns |
| `bins` | max points per series shipped to the browser per zoom window |
| `height` | chart height in px |
| `title` | chart title |
| `agg` | `"lttb"` (best visual shape) or `"minmax"` (every extreme value guaranteed to survive) |
| `max_gap` | break the line across data gaps wider than this: `"auto"` (10× median spacing), a timedelta string like `"5min"`, or a number (ms for time axes, x units for numeric) |
| `secondary_y` | column(s) plotted against a second y-axis on the right — for series with very different scales |

Returns a `TimeSeriesChart` (an ipywidget) — display it as the last expression
of a cell.

Interactions: mouse-wheel / drag to zoom & pan, slider for the overview,
toolbox for box-zoom, restore, and save-as-PNG. Tz-aware timestamps are shown
as wall-clock time; NaNs render as gaps.

The downsamplers are also usable directly:

```python
from tsviz import lttb, lttb_indices, lttb_approx_indices, minmax_indices
```

## Notes & limitations

- A live kernel is required for zoom updates (the chart re-renders its last
  view from saved widget state on reload, but re-zooming needs the kernel).
- Data is held in memory as float64 numpy arrays (one x array + one per column).
- `x` must be datetime-like or numeric; parse strings first with `pd.to_datetime`.
