blazeplot 0.1.0

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +206 -0
  3. package/ROADMAP.md +263 -0
  4. package/dist/core/DataCursor.d.ts +9 -0
  5. package/dist/core/DataCursor.d.ts.map +1 -0
  6. package/dist/core/MinMaxPyramid.d.ts +15 -0
  7. package/dist/core/MinMaxPyramid.d.ts.map +1 -0
  8. package/dist/core/RingBuffer.d.ts +24 -0
  9. package/dist/core/RingBuffer.d.ts.map +1 -0
  10. package/dist/core/SeriesStore.d.ts +22 -0
  11. package/dist/core/SeriesStore.d.ts.map +1 -0
  12. package/dist/core/index.d.ts +6 -0
  13. package/dist/core/index.d.ts.map +1 -0
  14. package/dist/core/types.d.ts +33 -0
  15. package/dist/core/types.d.ts.map +1 -0
  16. package/dist/index.d.ts +10 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +669 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/interaction/AxisController.d.ts +12 -0
  21. package/dist/interaction/AxisController.d.ts.map +1 -0
  22. package/dist/interaction/Camera2D.d.ts +31 -0
  23. package/dist/interaction/Camera2D.d.ts.map +1 -0
  24. package/dist/interaction/InputController.d.ts +23 -0
  25. package/dist/interaction/InputController.d.ts.map +1 -0
  26. package/dist/interaction/index.d.ts +5 -0
  27. package/dist/interaction/index.d.ts.map +1 -0
  28. package/dist/interaction/types.d.ts +18 -0
  29. package/dist/interaction/types.d.ts.map +1 -0
  30. package/dist/render/ReglBackend.d.ts +22 -0
  31. package/dist/render/ReglBackend.d.ts.map +1 -0
  32. package/dist/render/Renderer.d.ts +20 -0
  33. package/dist/render/Renderer.d.ts.map +1 -0
  34. package/dist/render/ShaderPrograms.d.ts +11 -0
  35. package/dist/render/ShaderPrograms.d.ts.map +1 -0
  36. package/dist/render/WebGL2Resources.d.ts +3 -0
  37. package/dist/render/WebGL2Resources.d.ts.map +1 -0
  38. package/dist/render/index.d.ts +6 -0
  39. package/dist/render/index.d.ts.map +1 -0
  40. package/dist/render/types.d.ts +38 -0
  41. package/dist/render/types.d.ts.map +1 -0
  42. package/dist/ui/Axis.d.ts +3 -0
  43. package/dist/ui/Axis.d.ts.map +1 -0
  44. package/dist/ui/Chart.d.ts +55 -0
  45. package/dist/ui/Chart.d.ts.map +1 -0
  46. package/dist/ui/Grid.d.ts +3 -0
  47. package/dist/ui/Grid.d.ts.map +1 -0
  48. package/dist/ui/Legend.d.ts +3 -0
  49. package/dist/ui/Legend.d.ts.map +1 -0
  50. package/dist/ui/Tooltip.d.ts +3 -0
  51. package/dist/ui/Tooltip.d.ts.map +1 -0
  52. package/dist/ui/index.d.ts +7 -0
  53. package/dist/ui/index.d.ts.map +1 -0
  54. package/package.json +49 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BlazePlot contributors
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.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # BlazePlot
2
+
3
+ [![npm version](https://img.shields.io/npm/v/blazeplot.svg)](https://www.npmjs.com/package/blazeplot)
4
+ [![npm downloads](https://img.shields.io/npm/dt/blazeplot.svg)](https://www.npmjs.com/package/blazeplot)
5
+ [![license](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
+ [![build](https://img.shields.io/github/actions/workflow/status/Federicocervelli/blazeplot/release.yml?branch=master)](https://github.com/Federicocervelli/blazeplot/actions)
7
+
8
+ Real-time LOD time series rendering engine for the browser.
9
+
10
+ BlazePlot is GPU-native plotting engineered for high-frequency streaming data where standard charting libraries (Chart.js, ECharts, uPlot) fall over. Instead of drawing every sample, it keeps a resident ring buffer of millions of points, builds a min/max LOD pyramid, and renders only what each pixel needs — so frame cost is `O(pixels)`, not `O(samples)`.
11
+
12
+ Built on WebGL2 + [regl](https://github.com/regl-project/regl). No Canvas2D, no SVG, no DOM layout.
13
+
14
+ ## Target
15
+
16
+ - **10M+** resident points per series
17
+ - **60 Hz** smooth append + render
18
+ - **Zero allocations** in the frame loop
19
+ - **Multi-series** with independent buffers and LOD
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ bun install blazeplot
25
+ ```
26
+
27
+ ## Quick start
28
+
29
+ ```html
30
+ <canvas id="chart" style="width:100%;height:400px"></canvas>
31
+ ```
32
+
33
+ ```js
34
+ import { Chart } from "blazeplot";
35
+
36
+ const canvas = document.getElementById("chart");
37
+ const chart = new Chart(canvas);
38
+
39
+ const series = chart.addSeries(
40
+ { mode: "line", capacity: 1_000_000, downsample: "minmax" },
41
+ { color: [0.3, 0.6, 1.0, 1.0] },
42
+ );
43
+
44
+ chart.setViewport({ xMin: 0, xMax: 1000, yMin: -2, yMax: 2 });
45
+ chart.start();
46
+
47
+ // Append data periodically
48
+ function push() {
49
+ const n = 256;
50
+ const xs = new Float64Array(n);
51
+ const ys = new Float32Array(n);
52
+ for (let i = 0; i < n; i++) {
53
+ xs[i] = t++;
54
+ ys[i] = Math.sin(t * 0.01) * 0.5 + Math.random() * 0.01;
55
+ }
56
+ series.append(xs, ys);
57
+ requestAnimationFrame(push);
58
+ }
59
+ let t = 0;
60
+ push();
61
+ ```
62
+
63
+ ## Features
64
+
65
+ | | |
66
+ |---|---|
67
+ | **LOD downsampling** | Min/max pyramid automatically selects the right detail level for the visible range — sparse viewports show raw points, dense viewports show vertical min/max segments. |
68
+ | **Streaming append** | Fixed-capacity ring buffer wraps silently. No re-allocation. No memory growth. |
69
+ | **Pan & zoom** | Pointer/touch pan and wheel zoom via `Camera2D`. Customizable viewport policies (e.g. live-follow X while Y is free). |
70
+ | **Grid lines** | Data-anchored grid rendered as WebGL line lists. |
71
+ | **Multi-series** | Independent buffers, styles, and visibility per series. |
72
+ | **No DOM** | No axis DOM elements, no SVG overlay. The canvas owns everything. |
73
+ | **ResizeObserver** | Automatic DPR-aware canvas sizing. |
74
+
75
+ ## API
76
+
77
+ ### `Chart`
78
+
79
+ | Signature | Description |
80
+ |---|---|
81
+ | `new Chart(canvas, options?)` | Create a chart from an HTML canvas element. |
82
+ | `chart.addSeries(config, style?)` | Add a data series. Returns `SeriesStore`. |
83
+ | `chart.removeSeries(series)` | Remove a previously added series. |
84
+ | `chart.setViewport({ xMin, xMax, yMin, yMax })` | Set the visible data range. |
85
+ | `chart.resize(dpr?)` | Resize the canvas to match its CSS size × DPR. |
86
+ | `chart.start()` | Start the render loop (rAF). |
87
+ | `chart.stop()` | Stop the render loop. |
88
+ | `chart.getFrameStats(target?)` | Copy per-frame benchmark counters into a reusable object. |
89
+ | `chart.dispose()` | Dispose GPU resources, observers, and input handlers. |
90
+
91
+ ### `ChartOptions`
92
+
93
+ | Property | Default | Description |
94
+ |---|---|---|
95
+ | `viewportPolicy?` | — | Custom pan/zoom/viewport behavior hooks. |
96
+ | `grid?` | `true` | Show grid lines. |
97
+ | `gridStyle?` | `{ color: [0.22,0.30,0.44,0.45] }` | Grid line color and width. |
98
+
99
+ ### `ChartFrameStats`
100
+
101
+ | Field | Description |
102
+ |---|---|
103
+ | `fps` | Instantaneous render-loop FPS. |
104
+ | `frameMs` | Milliseconds spent in `render()`. |
105
+ | `pointsRendered` | Number of vertices drawn this frame. |
106
+ | `drawCalls` | Number of GPU draw calls this frame. |
107
+ | `uploadBytes` | Bytes uploaded to GPU this frame. |
108
+ | `renderMode` | `"none"` / `"raw"` / `"minmax"` / `"mixed"`. |
109
+
110
+ ### `SeriesStore`
111
+
112
+ | Signature | Description |
113
+ |---|---|
114
+ | `series.append(xs, ys)` | Append typed arrays of X (Float64) and Y (Float32) values. |
115
+ | `series.clear()` | Clear all data and reset LOD state. |
116
+ | `series.setVisible(v)` | Toggle visibility. |
117
+ | `series.visible` | Current visibility state. |
118
+ | `series.length` | Number of samples buffered. |
119
+
120
+ ### `SeriesConfig`
121
+
122
+ | Property | Description |
123
+ |---|---|
124
+ | `mode` | `"line"` / `"envelope"` / `"scatter"` (envelope and scatter roadmap-only). |
125
+ | `capacity` | Ring buffer capacity (samples). |
126
+ | `downsample` | `"minmax"` (the only LOD strategy). |
127
+
128
+ ### `SeriesStyle`
129
+
130
+ | Property | Default | Description |
131
+ |---|---|---|
132
+ | `color` | `[0.3, 0.6, 1.0, 1.0]` | RGBA float color. |
133
+ | `lineWidth` | `1` | Line width in pixels. |
134
+
135
+ ### `ViewportPolicy`
136
+
137
+ ```ts
138
+ interface ViewportPolicy {
139
+ beforePan?(camera: Camera2D, intent: PanIntent): PanIntent | null;
140
+ beforeZoom?(camera: Camera2D, intent: ZoomIntent): ZoomIntent | null;
141
+ beforeRender?(camera: Camera2D): void;
142
+ }
143
+ ```
144
+
145
+ Built-in data types: `Viewport`, `PanIntent`, `ZoomIntent`, `ZoomAxis`.
146
+
147
+ ### Lower-level primitives
148
+
149
+ `Camera2D`, `RingBuffer`, `MinMaxPyramid`, `AxisController` are exported for advanced use cases (custom pipelines, worker threads, offscreen rendering).
150
+
151
+ ## How it works
152
+
153
+ ```
154
+ Data stream ──► RingBuffer (resident, wraps at capacity)
155
+
156
+
157
+ MinMaxPyramid (full rebuild today, incremental roadmap)
158
+
159
+
160
+ SeriesStore.query() ──► LODView (buckets for visible range)
161
+
162
+
163
+ Renderer (regl / WebGL2)
164
+ ```
165
+
166
+ The render loop decides per-frame:
167
+ - **Few visible samples** → raw line strip from ring buffer
168
+ - **Many visible samples** → min/max vertical segments from the pyramid
169
+
170
+ ## Architecture
171
+
172
+ ```
173
+ src/
174
+ core/ # Data engine — no UI, no GPU
175
+ render/ # GPU abstraction + regl backend
176
+ interaction/ # Camera, input, axis ticks
177
+ ui/ # Orchestrator (Chart)
178
+ ```
179
+
180
+ ## Development
181
+
182
+ ```bash
183
+ bun install
184
+ bun run dev # Vite dev server → preview/
185
+ bun run build # Package build (JS + declarations)
186
+ bun run build:js # JS-only build
187
+ bun test # Core data-structure tests
188
+ bun run typecheck # TypeScript strict check
189
+ ```
190
+
191
+ ## Package build
192
+
193
+ ```bash
194
+ bun run build
195
+ ```
196
+
197
+ Output:
198
+
199
+ ```
200
+ dist/index.js ES module
201
+ dist/index.d.ts TypeScript declarations
202
+ dist/index.js.map Source map
203
+ dist/*.d.ts.map Declaration maps
204
+ ```
205
+
206
+
package/ROADMAP.md ADDED
@@ -0,0 +1,263 @@
1
+ # BlazePlot — Roadmap
2
+
3
+ **BlazePlot is a real-time LOD time series rendering engine, not a plotting library.**
4
+
5
+ Target: 10M points resident, 60 Hz append/update, pan/zoom fluid, render O(pixel) not O(samples), zero allocations in frame loop, multi-series, line/envelope/scatter.
6
+
7
+ ---
8
+
9
+ ## Architecture
10
+
11
+ ```
12
+ src/
13
+ core/ # Data engine — no UI, no GPU
14
+ render/ # GPU abstraction + regl V1 backend
15
+ interaction/ # Camera, input, axis ticks
16
+ ui/ # Orchestrator (Chart)
17
+ tests/ # bun test — RingBuffer, MinMaxPyramid, Camera2D
18
+ preview/ # Dev preview harness, detached from package build
19
+ ```
20
+
21
+ The core split: **data engine** and **renderer** are separate. A `SeriesStore` owns a `RingBuffer` + `MinMaxPyramid`. The renderer reads LOD views, never raw data.
22
+
23
+ ```
24
+ interface GpuBackend { … } # abstract GPU
25
+ class ReglBackend { … } # V1 implementation
26
+ class FutureWebGPUBackend { …} # V3
27
+ ```
28
+
29
+ **Camera2D** is the canonical viewport model (data-space xMin/xMax/yMin/yMax). Scale/offset for shader uniforms are derived getters. `ViewportPlanner` was removed — its pan/zoom live on `Camera2D` directly.
30
+
31
+ Package output is detached from the preview app:
32
+ - `bun run dev` serves `preview/`.
33
+ - `bun run build` emits `dist/index.js` and `dist/index.d.ts` from `src/index.ts`.
34
+ - `preview/` is excluded from npm package contents.
35
+
36
+ ---
37
+
38
+ ## Phase 1 — Vertical slice: line on screen
39
+
40
+ **Status: in progress**
41
+
42
+ Get one end-to-end path working: append → visible raw extraction → GPU upload → draw.
43
+
44
+ - [x] WebGL2 context + regl init
45
+ - [x] Canvas resize + DPR handling
46
+ - [x] Streaming append with debug overlay
47
+ - [x] `ReglBackend` — createBuffer, updateBuffer, createProgram, cached draw commands
48
+ - [x] Raw line strip draw via regl
49
+ - [x] Wire `Chart.render()`: clear → copy visible raw range → upload → draw
50
+ - [x] **Benchmark overlay**: fps, ms/frame, points rendered, draw calls, upload bytes
51
+
52
+ This is the shortest path to seeing data on screen. Benchmarking the full pipeline comes after.
53
+
54
+ ---
55
+
56
+ ## Phase 2 — Core data engine
57
+
58
+ **Status: basic scaffold implemented, correctness tests passing**
59
+
60
+ - [x] `RingBuffer` — append-only, Float64Array x / Float32Array y, logical index access, ring-wrap aware search
61
+ - [x] `MinMaxPyramid` — min/max per level (bucket size 2), correct higher-level aggregation, ring-wrap aware builds, `query()` returns `LODView`
62
+ - [x] `SeriesStore` — buffer + pyramid + style, dirty tracking
63
+ - [x] `Camera2D` — viewport model with pan, zoom, setViewport, clip/screen transforms
64
+ - [x] `DataCursor` — binary search by timestamp
65
+ - [x] Tests for `RingBuffer`, `MinMaxPyramid`, and `Camera2D` (bun test runner)
66
+ - [ ] **Incremental pyramid update** — current: full rebuild on every `build()`. Target: O(log N) per append, updating only closed buckets in the chain. This is the core competitive advantage — must be designed before release.
67
+
68
+ ---
69
+
70
+ ## Phase 2.5 — Worker pipeline
71
+
72
+ **Status: not started**
73
+
74
+ ```
75
+ producer thread / worker ──► SharedArrayBuffer ──► downsampling worker ──► main thread
76
+ ```
77
+
78
+ - [ ] Ingest worker — receives data, writes to ring buffer via SharedArrayBuffer
79
+ - [ ] Downsample worker — incremental pyramid update off main thread
80
+ - [ ] Main thread — reads coherent snapshot, uploads visible range to GPU
81
+ - [ ] `OffscreenCanvas` optional path
82
+
83
+ Data structures have correctness coverage, but the incremental pyramid API must be finalized before moving them off main thread.
84
+
85
+ ---
86
+
87
+ ## Phase 3 — regl renderer (full)
88
+
89
+ **Status: not started**
90
+
91
+ - [x] `ReglBackend` — createBuffer, updateBuffer (subdata), createProgram, draw command cache
92
+ - [ ] Persistent buffer pool (no Float32Array allocs per frame)
93
+ - [x] Raw line strip for few visible points
94
+ - [x] `MinMaxSegmentRenderer` — vertical min/max segments for dense viewports
95
+ - [ ] Instanced draw for segment mode
96
+ - [x] Camera transform as uniforms (scale/offset getters on Camera2D)
97
+ - [ ] Two shader modes: `line.vert/frag` and `segment.vert/frag`
98
+ - [x] `Renderer.drawMinMaxSegments`
99
+ - [ ] Draw call batching per shader mode
100
+
101
+ ---
102
+
103
+ ## Phase 4 — Interaction
104
+
105
+ **Status: implemented (camera + input)**
106
+
107
+ - [x] `Camera2D` — viewport model with pan, zoom, setViewport
108
+ - [x] `InputController` — pointer pan, wheel zoom, touch via Pointer Events
109
+ - [x] `ViewportPolicy` — transforms pan/zoom intents and can update camera before render
110
+ - [x] Preview synced-X policy — X stays live-followed while wheel zoom/pan affect Y only
111
+ - [x] Camera uniforms propagated to shaders per frame
112
+ - [ ] LOD re-query on pan/zoom (viewport change → new LODView)
113
+ - [x] `AxisController` — smart tick generation and label formatting
114
+ - [ ] Axis tick rendering (smart tick count, label formatting)
115
+ - [x] Grid line rendering
116
+
117
+ Camera modifies `Camera2D`, renderer reads it. No direct data access from interaction layer.
118
+
119
+ ---
120
+
121
+ ## Phase 5 — Multi-series
122
+
123
+ **Status: data model ready**
124
+
125
+ - [x] `Chart.addSeries()` supports multiple stores
126
+ - [x] Each `SeriesStore` has independent buffer + pyramid + style
127
+ - [ ] Batched draw calls (same shader → one draw per series group)
128
+ - [ ] Shared X axis optional, independent Y per series
129
+ - [x] Color/style per-series
130
+ - [x] Series visibility toggle
131
+
132
+ Limit: solid lines only, no markers, no antialias, no spline, no fill.
133
+
134
+ ---
135
+
136
+ ## Phase 6 — Public API
137
+
138
+ **Status: basic shape exists**
139
+
140
+ - [x] `new Chart(canvas)`
141
+ - [x] `new Chart(canvas, { viewportPolicy })`
142
+ - [x] `chart.addSeries(config, style)`
143
+ - [x] `chart.setViewport({ xMin, xMax, yMin, yMax })`
144
+ - [x] `chart.start()` / `chart.stop()`
145
+ - [x] `chart.resize()` — handle container resize with DPR
146
+ - [x] `series.append(x, y)` — accepts typed arrays
147
+ - [x] `series.clear()`
148
+ - [x] `chart.removeSeries(series)`
149
+ - [ ] Axis labels / tick rendering
150
+ - [x] Grid
151
+ - [ ] Legend
152
+ - [ ] Tooltip / hit testing
153
+ - [ ] Export image
154
+ - [ ] Theme system
155
+ - [x] ResizeObserver integration
156
+
157
+ Package status:
158
+ - [x] `exports`, `main`, `module`, and `types` point at `dist/`
159
+ - [x] Vite library build from `src/index.ts`
160
+ - [x] Declaration emit from `src/` only via Vite d.ts plugin
161
+ - [x] `bun pm pack --dry-run` includes package files only
162
+
163
+ ---
164
+
165
+ ## Downsampler — LOD engine
166
+
167
+ **Status: basic pyramid built, not incremental**
168
+
169
+ Current: `MinMaxPyramid.build()` does a full bottom-up rebuild. Target:
170
+
171
+ ```
172
+ raw level: x: Float64Array, y: Float32Array
173
+ level 1: minY/maxY per bucket of 2
174
+ level 2: minY/maxY per bucket of 4
175
+ level 3: minY/maxY per bucket of 8
176
+
177
+ ```
178
+
179
+ **Incremental append**: each append updates only the chain of closed buckets. Query is O(buckets in viewport).
180
+
181
+ Planned incremental design:
182
+ - Raw samples are addressed by monotonically increasing logical sample index, not physical ring position.
183
+ - Level `n` bucket width is `bucketSize ** (n + 1)` raw samples.
184
+ - Appending a sample updates only level 0 while its bucket is open.
185
+ - When a bucket closes, its min/max pair is propagated upward as one input sample for the next level.
186
+ - Higher levels never read raw Y values; they combine child min/max pairs.
187
+ - Ring wrap invalidates buckets whose covered logical index range was overwritten.
188
+ - Query receives a visible logical index range from x-search and maps that range to bucket indices using the selected level width.
189
+
190
+ **Query**: `samples_per_pixel = visible_samples / plotWidthPx`, pick `level = max(0, ceil(log2(samples_per_pixel)) - 1)`, return min/max pairs.
191
+
192
+ **Render decision**:
193
+ - few points → raw line strip
194
+ - many points → vertical min/max segments
195
+ - very many → envelope mesh
196
+
197
+ ---
198
+
199
+ ## Backend strategy
200
+
201
+ ```
202
+ V1: WebGL2 + regl ← CURRENT
203
+ V2: Backend abstraction ← In place (GpuBackend interface)
204
+ V3: WebGPU backend ← Future
205
+ ```
206
+
207
+ regl is the V1 backend, not the architecture. The `GpuBackend` interface decouples core from GPU.
208
+
209
+ regl rules for V1:
210
+ - Persistent buffers (no re-create per frame)
211
+ - Precompiled regl commands
212
+ - `subdata` on small ranges
213
+ - Batched draw calls
214
+ - Simple shaders, camera in uniforms
215
+ - WebGL2 required (no fallback)
216
+
217
+ ---
218
+
219
+ ## Overflow semantics
220
+
221
+ **Status: undecided**
222
+
223
+ `RingBuffer` wraps silently at capacity. For streaming this is usually correct, but it should be explicit. Options:
224
+ - Ring-buffer with wrap notification
225
+ - Fixed capacity with error on overflow
226
+ - Auto-growing buffer (breaks streaming contract)
227
+
228
+ Deferred until we have a concrete use case.
229
+
230
+ ---
231
+
232
+ ## What we're NOT doing (V1)
233
+
234
+ - SVG / Canvas2D fallback
235
+ - Spline interpolation
236
+ - Complex fill (gradient, area below)
237
+ - Markers / point symbols
238
+ - Antialias perfection
239
+ - Recalculating axes in render loop
240
+ - Per-series draw call without batching
241
+
242
+ ---
243
+
244
+ ## Competitive advantage
245
+
246
+ Not WebGL. The core differentiator:
247
+
248
+ > Incremental min/max pyramid + zero-allocation render loop + Camera2D viewport model
249
+
250
+ Never render 10M points. Render `plotWidthPx * 2` (2k–8k vertices).
251
+
252
+ ---
253
+
254
+ ## Future / difficult
255
+
256
+ - Multi-chart sync
257
+ - Multiple Y axes
258
+ - Spectrogram / heatmap
259
+ - Large scatter ( > 1M points )
260
+ - OHLC / candlestick
261
+ - FFT / waterfall
262
+ - Out-of-core data ( > RAM)
263
+ - WebGPU backend
@@ -0,0 +1,9 @@
1
+ import { RingBuffer } from './RingBuffer.js';
2
+ export declare class DataCursor {
3
+ private _index;
4
+ private _buffer;
5
+ bind(buffer: RingBuffer): void;
6
+ get index(): number;
7
+ seekTimestamp(x: number): number;
8
+ }
9
+ //# sourceMappingURL=DataCursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DataCursor.d.ts","sourceRoot":"","sources":["../../src/core/DataCursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAA2B;IAE1C,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAK9B,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;CAYjC"}
@@ -0,0 +1,15 @@
1
+ import { LODView, Viewport } from './types.js';
2
+ import { RingBuffer } from './RingBuffer.js';
3
+ export declare class MinMaxPyramid {
4
+ readonly bucketSize: number;
5
+ private levels;
6
+ private levelLengths;
7
+ private levelSampleWidths;
8
+ constructor(bucketSize?: number);
9
+ build(source: RingBuffer): void;
10
+ query(_viewport: Viewport, pixelWidth: number, xRange: {
11
+ start: number;
12
+ length: number;
13
+ }): LODView;
14
+ }
15
+ //# sourceMappingURL=MinMaxPyramid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MinMaxPyramid.d.ts","sourceRoot":"","sources":["../../src/core/MinMaxPyramid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,qBAAa,aAAa;IAKZ,QAAQ,CAAC,UAAU,EAAE,MAAM;IAJvC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,iBAAiB,CAAc;gBAElB,UAAU,GAAE,MAAU;IAS3C,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAgD/B,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO;CAsCnG"}
@@ -0,0 +1,24 @@
1
+ import { TimeRange } from './types.js';
2
+ export declare class RingBuffer {
3
+ readonly capacity: number;
4
+ private _length;
5
+ private _head;
6
+ private readonly xData;
7
+ private readonly yData;
8
+ constructor(capacity: number);
9
+ get length(): number;
10
+ get range(): TimeRange | null;
11
+ push(x: number, y: number): void;
12
+ get(index: number): {
13
+ x: number;
14
+ y: number;
15
+ } | null;
16
+ getX(index: number): number;
17
+ getY(index: number): number;
18
+ lowerBoundX(x: number): number;
19
+ upperBoundX(x: number): number;
20
+ clear(): void;
21
+ private logicalToPhysical;
22
+ private assertValidIndex;
23
+ }
24
+ //# sourceMappingURL=RingBuffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RingBuffer.d.ts","sourceRoot":"","sources":["../../src/core/RingBuffer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,qBAAa,UAAU;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,KAAK,CAAa;IAE1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAEzB,QAAQ,EAAE,MAAM;IAU5B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,KAAK,IAAI,SAAS,GAAG,IAAI,CAG5B;IAED,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAOhC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAKnD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAK3B,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAK3B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAW9B,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAW9B,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,gBAAgB;CAKzB"}
@@ -0,0 +1,22 @@
1
+ import { SeriesConfig, SeriesStyle, LODView, Viewport } from './types.js';
2
+ export declare class SeriesStore {
3
+ readonly config: SeriesConfig;
4
+ readonly style: SeriesStyle;
5
+ private readonly buffer;
6
+ private readonly pyramid;
7
+ private _dirty;
8
+ private _visible;
9
+ constructor(config: SeriesConfig, style: SeriesStyle);
10
+ get dirty(): boolean;
11
+ get length(): number;
12
+ get visible(): boolean;
13
+ setVisible(visible: boolean): void;
14
+ append(x: ArrayLike<number>, y: ArrayLike<number>): void;
15
+ clear(): void;
16
+ rebuildPyramid(): void;
17
+ query(viewport: Viewport, pixelWidth: number): LODView;
18
+ visibleSampleCount(viewport: Viewport): number;
19
+ copyRawVisible(viewport: Viewport, target: Float32Array, maxPoints: number): number;
20
+ copyMinMaxVisible(viewport: Viewport, target: Float32Array, maxSegments: number): number;
21
+ }
22
+ //# sourceMappingURL=SeriesStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SeriesStore.d.ts","sourceRoot":"","sources":["../../src/core/SeriesStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI/E,qBAAa,WAAW;IACtB,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IAExC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,QAAQ,CAAiB;gBAErB,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW;IAOpD,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIlC,MAAM,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI;IAQxD,KAAK,IAAI,IAAI;IAMb,cAAc,IAAI,IAAI;IAMtB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAetD,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM;IAM9C,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM;IAmBnF,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;CAqCzF"}
@@ -0,0 +1,6 @@
1
+ export type { Viewport, LODBucket, LODView, TimeRange, SeriesStyle, SeriesMode, SeriesConfig } from './types.js';
2
+ export { RingBuffer } from './RingBuffer.js';
3
+ export { MinMaxPyramid } from './MinMaxPyramid.js';
4
+ export { SeriesStore } from './SeriesStore.js';
5
+ export { DataCursor } from './DataCursor.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,33 @@
1
+ export interface Viewport {
2
+ readonly xMin: number;
3
+ readonly xMax: number;
4
+ readonly yMin: number;
5
+ readonly yMax: number;
6
+ }
7
+ export interface LODBucket {
8
+ readonly xStart: number;
9
+ readonly xEnd: number;
10
+ readonly minY: number;
11
+ readonly maxY: number;
12
+ }
13
+ export interface LODView {
14
+ readonly buckets: Float32Array;
15
+ readonly bucketCount: number;
16
+ readonly level: number;
17
+ readonly samplesPerPixel: number;
18
+ }
19
+ export interface TimeRange {
20
+ readonly start: number;
21
+ readonly end: number;
22
+ }
23
+ export interface SeriesStyle {
24
+ readonly color: readonly [number, number, number, number];
25
+ readonly lineWidth: number;
26
+ }
27
+ export type SeriesMode = "line" | "envelope" | "scatter";
28
+ export interface SeriesConfig {
29
+ readonly mode: SeriesMode;
30
+ readonly capacity: number;
31
+ readonly downsample: "minmax";
32
+ }
33
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC;CAC/B"}
@@ -0,0 +1,10 @@
1
+ export { Chart } from './ui/Chart.js';
2
+ export type { ChartFrameStats, ChartOptions } from './ui/Chart.js';
3
+ export { SeriesStore } from './core/SeriesStore.js';
4
+ export { RingBuffer } from './core/RingBuffer.js';
5
+ export { MinMaxPyramid } from './core/MinMaxPyramid.js';
6
+ export { Camera2D } from './interaction/Camera2D.js';
7
+ export { AxisController } from './interaction/AxisController.js';
8
+ export type { Viewport, LODBucket, LODView, TimeRange, SeriesStyle, SeriesMode, SeriesConfig } from './core/types.js';
9
+ export type { PanIntent, ZoomAxis, ZoomIntent, ViewportPolicy } from './interaction/types.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACtH,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}