blazeplot 0.3.0 → 0.3.2

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 (2) hide show
  1. package/README.md +441 -187
  2. package/package.json +9 -5
package/README.md CHANGED
@@ -21,21 +21,23 @@ bun install blazeplot
21
21
 
22
22
  ## Quick start
23
23
 
24
+ A chart only needs a host element. For regular arrays, wrap your data in a `StaticDataset`; `capacity` is only needed when you want a streaming ring buffer.
25
+
24
26
  ```html
25
27
  <div id="chart" style="width:100%;height:400px"></div>
26
28
 
27
29
  <script type="module">
28
- import { Chart } from "blazeplot";
30
+ import { Chart, StaticDataset } from "blazeplot";
29
31
 
30
- const chart = new Chart(document.getElementById("chart"));
31
- const wave = chart.addLine({ capacity: 10_000, downsample: "minmax" });
32
+ const el = document.getElementById("chart");
33
+ if (!el) throw new Error("Missing #chart element");
32
34
 
33
- wave.append(
34
- Float64Array.from({ length: 1000 }, (_, i) => i),
35
- Float32Array.from({ length: 1000 }, (_, i) => Math.sin(i * 0.02)),
36
- );
35
+ const x = Array.from({ length: 1000 }, (_, i) => i);
36
+ const y = x.map((value) => Math.sin(value * 0.02));
37
37
 
38
- chart.setViewport({ xMin: 0, xMax: 1000, yMin: -1.5, yMax: 1.5 });
38
+ const chart = new Chart(el);
39
+ chart.addLine({ dataset: new StaticDataset(x, y), name: "sine" });
40
+ chart.setViewport({ xMin: x[0], xMax: x[x.length - 1], yMin: -1.5, yMax: 1.5 });
39
41
  chart.start();
40
42
  </script>
41
43
  ```
@@ -55,186 +57,423 @@ bun install blazeplot
55
57
  | **Benchmark overlay** | Built-in fps, frame time, vertex count, draw calls. |
56
58
  | **ResizeObserver** | Automatic DPR-aware canvas sizing. |
57
59
 
58
- ## API
59
-
60
- ### `Chart`
61
-
62
- | Signature | Description |
63
- |---|---|
64
- | `new Chart(container, options?)` | Create a chart inside an HTML container element. The chart owns the plot canvas and axis layout. |
65
- | `chart.addSeries(config, style?)` | Add a data series. Returns `SeriesStore`. |
66
- | `chart.addLine(config, style?)` / `addArea` / `addScatter` / `addBar` / `addOhlc` / `addCandlestick` | Typed helpers that set the series mode for you. `config.yAxis: "right"` maps a series to the secondary Y axis. |
67
- | `chart.removeSeries(series)` | Remove a previously added series. |
68
- | `chart.setViewport({ xMin, xMax, yMin, yMax })` | Set the visible data range on both Y axes and the shared X range. |
69
- | `chart.setYViewport("left" | "right", { yMin, yMax })` | Set one Y-axis viewport while preserving the shared X range. |
70
- | `chart.getViewport(yAxis?)` | Return the current visible data range for `"left"` or `"right"` Y axis. |
71
- | `chart.pan(intent)` / `chart.zoom(intent)` | Plugin-facing camera interaction helpers. |
72
- | `chart.clientToData(clientX, clientY, yAxis?)` / `chart.dataToPlot(x, y, yAxis?)` | Convert between client/plot coordinates and data coordinates for the selected Y axis. |
73
- | `chart.resize(dpr?)` | Resize the internal plot canvas to match its CSS size × DPR. |
74
- | `chart.start()` | Start the render loop (rAF). |
75
- | `chart.stop()` | Stop the render loop. |
76
- | `chart.canvas` | Read-only access to the internal plot canvas. |
77
- | `chart.xAxisElement` / `chart.yAxisElement` | Plugin-facing access to outside axis gutter elements. |
78
- | `chart.theme` | Resolved theme values used by the chart and built-in plugins. |
79
- | `chart.getFrameStats(target?)` | Copy per-frame benchmark counters into a reusable object. |
80
- | `chart.getSeriesState()` | Return public series metadata/state for plugins or custom UI. |
81
- | `chart.setSeriesVisible(series, visible)` | Toggle visibility and notify series-state subscribers. |
82
- | `chart.pick(clientX, clientY, options?)` | Raw-data hit test. Supports `mode: "nearest-x" | "nearest-point"`, `group: "x" | "none"`, and `maxDistancePx`; returned items include actual sample X/Y and plot/client coordinates for highlights. |
83
- | `chart.subscribe("hover", cb)` / `chart.subscribe("serieschange", cb)` | Subscribe to hover or series state changes. Returns an unsubscribe function. |
84
- | `await chart.screenshot(options?)` | Export the full chart as an image `Blob`, including the WebGL plot and built-in DOM text overlays. |
85
- | `chart.dispose()` | Dispose GPU resources, observers, input handlers, and owned DOM layout. |
86
-
87
- ### `ChartOptions`
88
-
89
- | Property | Default | Description |
90
- |---|---|---|
91
- | `viewportPolicy?` | — | Optional `beforeRender` viewport hook. Pass the same policy to `interactionsPlugin({ viewportPolicy })` for pan/zoom hooks. |
92
- | `hover?` | `{ mode: "nearest-x", group: "x" }` | Default hover picking behavior. `mode` can be `"nearest-x"` or `"nearest-point"`; `group: "x"` reports one item per visible series at the selected X, while `group: "none"` reports only the selected point. |
93
- | `plugins?` | `[]` | Optional `ChartPlugin` instances, e.g. `legendPlugin()` and `tooltipPlugin()`. |
94
- | `theme?` | built-in dark theme | Override chart, axis, palette, legend, and tooltip colors/fonts. |
95
- | `grid?` | `true` | Show grid lines. |
96
- | `gridStyle?` | `{ color: theme.gridColor }` | Grid line color and width; overrides the theme grid color. |
97
- | `axes?` | `true` | Show axis tick labels. `true`/`false`, or per-axis `{ x?: boolean \| AxisConfig, y?: boolean \| AxisConfig, y2?: boolean \| AxisConfig }`. `y2` is the right-side secondary Y axis and is hidden by default. |
98
-
99
- ### `ChartTheme`
100
-
101
- ```css
102
- :root {
103
- --plot-bg: #050816;
104
- --plot-grid: rgb(148 163 184 / 0.22);
105
- --plot-axis: #cbd5e1;
106
- --series-a: #38bdf8;
107
- --series-b: oklch(70% 0.19 22);
108
- }
109
- ```
110
-
111
- ```ts
112
- new Chart(container, {
113
- theme: {
114
- backgroundColor: "var(--plot-bg)",
115
- gridColor: "var(--plot-grid)",
116
- axisColor: "var(--plot-axis)",
117
- seriesColors: ["var(--series-a)", "var(--series-b)"],
118
- tooltipBackgroundColor: "rgb(4 8 16 / 0.85)",
119
- legendBackgroundColor: "rgb(4 8 16 / 0.85)",
120
- },
121
- });
122
- ```
123
-
124
- WebGL-facing colors (`backgroundColor`, `gridColor`, `seriesColors`) accept either normalized RGBA tuples (`[r,g,b,a]`) or CSS colors, including CSS variables inherited by the chart container. BlazePlot resolves CSS colors internally to WebGL-compatible RGBA floats while preserving CSS strings for DOM styling where appropriate. Per-series styles and plugin options still override theme defaults.
125
-
126
- ### `AxisConfig`
127
-
128
- | Property | Default | Description |
129
- |---|---|---|
130
- | `visible?` | `true` | Show this axis. |
131
- | `position?` | `"inside"` | `"inside"` draws labels over the plot; `"outside"` reserves a real DOM gutter and shrinks the plot canvas. |
132
-
133
- ```ts
134
- // X labels outside (bottom gutter), left Y inside, right Y outside.
135
- const chart = new Chart(canvas, {
136
- axes: { x: { position: "outside" }, y: true, y2: { position: "outside" } }
137
- });
138
-
139
- chart.addLine({ capacity: 10_000, yAxis: "left" });
140
- chart.addLine({ capacity: 10_000, yAxis: "right" });
141
- chart.setYViewport("right", { yMin: 0, yMax: 100 });
142
- ```
143
-
144
- ### `ChartFrameStats`
60
+ <!-- README_DOCS_START -->
61
+ ## API reference
145
62
 
146
- | Field | Description |
147
- |---|---|
148
- | `fps` | Instantaneous render-loop FPS. |
149
- | `frameMs` | Milliseconds spent in `render()`. |
150
- | `pointsRendered` | Number of vertices drawn this frame. |
151
- | `drawCalls` | Number of GPU draw calls this frame. |
152
- | `uploadBytes` | Bytes uploaded to GPU this frame. |
153
- | `renderMode` | `"none"` / `"raw"` / `"minmax"` / `"points"` / `"bars"` / `"area"` / `"mixed"`. |
154
-
155
- ### Plugins
156
-
157
- ```js
158
- import { Chart } from "blazeplot";
159
- import { interactionsPlugin } from "blazeplot/plugins/interactions";
160
- import { legendPlugin } from "blazeplot/plugins/legend";
161
- import { tooltipPlugin } from "blazeplot/plugins/tooltip";
162
- import { annotationsPlugin } from "blazeplot/plugins/annotations";
163
-
164
- const chart = new Chart(container, {
165
- hover: { mode: "nearest-x", group: "x" },
166
- plugins: [
167
- interactionsPlugin({ axis: "xy" }),
168
- annotationsPlugin({
169
- annotations: [
170
- { type: "y-line", y: 0, label: "baseline" },
171
- { type: "x-range", xMin: 10, xMax: 20, label: "event" },
172
- { type: "point", x: 42, y: 1.2, shape: "diamond", label: "marker" },
173
- ],
174
- }),
175
- legendPlugin(),
176
- tooltipPlugin({ mode: "nearest-point", group: "none", maxDistancePx: 24 }),
177
- ],
178
- });
179
- ```
63
+ This section is generated by `bun run docs:readme` from TypeScript declaration files and JSDoc comments. Do not edit it by hand.
180
64
 
181
- Built-in plugins are optional. `interactionsPlugin()` provides plain-drag box zoom, Shift+drag plot pan, wheel zoom, double-click reset, and `axis: "x" | "y" | "xy"`. When outside axes are visible, scrolling an axis zooms that axis and dragging an axis pans that axis; the plugin also applies a subtle axis hover color/filter configurable with `axisHover`, `axisHoverColor`, and `axisHoverFilter`. `annotationsPlugin()` supports common plot annotations: vertical/horizontal lines, X/Y bands, boxes, point markers, and labels. Legend/tooltip consume public APIs (`getSeriesState`, `setSeriesVisible`, `pick`, and `subscribe`) so custom UI can use the same contract. The default tooltip updates while the cursor is still on live charts and highlights the raw sample(s) it is reporting. For shared time-series tooltips, use `group: "x"`; for scatter/true point hover, use `group: "none"`.
65
+ - [Quick start](#quick-start)
66
+ - [Features](#features)
67
+ - [Architecture](#architecture)
68
+ - [Development](#development)
69
+ - [Roadmap](ROADMAP.md)
70
+ - [Changelog for v0.3.2](changelogs/v0.3.2.md)
182
71
 
183
- ### `SeriesStore`
72
+ ### Package entry points
184
73
 
185
- | Signature | Description |
74
+ | Import | Contents |
186
75
  |---|---|
187
- | `series.append(xs, ys)` | Append typed arrays of X and Y values (streaming). |
188
- | `series.clear()` | Clear all data and reset. |
189
- | `series.setVisible(v)` | Toggle visibility. |
190
- | `series.visible` | Current visibility state. |
191
- | `series.length` | Number of samples buffered. |
192
-
193
- ### `SeriesConfig`
194
-
195
- | Property | Description |
196
- |---|---|
197
- | `mode` | `"line"` / `"area"` / `"scatter"` / `"bar"` / `"ohlc"` / `"candlestick"` / `"envelope"` (envelope roadmap-only). |
198
- | `capacity` | Ring buffer capacity (samples). |
199
- | `id?` / `name?` | Optional metadata exposed to plugins, legend, and tooltip rows. |
200
- | `yAxis?` | `"left"` or `"right"`; selects the primary or secondary Y viewport/axis for this series. |
201
- | `downsample` | `"minmax"` or `"none"`. Min/max LOD applies to line/bar rendering. Scatter with LOD uses a 2D viewport-aware point sampler; scatter with `"none"` renders exact 2D-culled chunks. Area renders raw sampled strips. |
202
-
203
- ### `SeriesStyle`
204
-
205
- | Property | Default | Description |
206
- |---|---|---|
207
- | `color` | `[0.3, 0.6, 1.0, 1.0]` | RGBA float color. |
208
- | `lineWidth` | `1` | Line width in pixels. |
209
- | `pointSize` | `4` | Scatter point size in CSS pixels. |
210
- | `barWidth` | `0.8` | Bar width in data-space X units. |
211
- | `baseline` | `0` | Area/bar baseline in data-space Y units. |
212
- | `fillColor` | line color with 25% alpha | Area fill RGBA color. |
213
- | `tickWidth` | `barWidth` or `0.8` | OHLC open/close tick width in data-space X units. |
214
- | `upColor` / `downColor` | series color / translucent fill | Candlestick body colors. |
215
- | `wickColor` | series color | Candlestick wick color. |
216
-
217
- ### High-performance dataset capabilities
218
-
219
- `Dataset` only requires `getX/getY` and X binary search. For maximum performance with huge, procedural, remote, or memory-mapped data, implement `AcceleratedDataset`.
220
-
221
- `AcceleratedDataset` is the convenience contract for all fast paths: range min/max, exact range copying, stable viewport sampling, and renderer-ready min/max buckets. BlazePlot still exports the smaller capability interfaces (`RangeMinMaxDataset`, `RangeSampleCopyDataset`, `VisibleSampleCopyDataset`, `MinMaxSegmentCopyDataset`) for advanced partial acceleration, but most high-performance custom datasets should target `AcceleratedDataset`.
222
-
223
- ### `ViewportPolicy`
224
-
225
- `beforeRender` is consumed by `Chart`; `beforePan` and `beforeZoom` are consumed by `interactionsPlugin({ viewportPolicy })`.
226
-
227
- ```ts
228
- interface ViewportPolicy {
229
- beforePan?(camera: Camera2D, intent: PanIntent): PanIntent | null;
230
- beforeZoom?(camera: Camera2D, intent: ZoomIntent): ZoomIntent | null;
231
- beforeRender?(camera: Camera2D): void;
232
- }
233
- ```
234
-
235
- ### Lower-level primitives
236
-
237
- `Camera2D`, `RingBuffer`, `MinMaxPyramid`, `AxisController` are exported for advanced use cases.
76
+ | `blazeplot` | Core chart, data, interaction, rendering types, and low-level primitives. |
77
+ | `blazeplot/react` | React wrapper component and hooks. |
78
+ | `blazeplot/linked` | Linked chart layout helpers. |
79
+ | `blazeplot/plugins/legend` | Built-in legend plugin. |
80
+ | `blazeplot/plugins/tooltip` | Built-in tooltip plugin. |
81
+ | `blazeplot/plugins/interactions` | Built-in pan, zoom, axis interaction, and reset plugin. |
82
+ | `blazeplot/plugins/annotations` | Built-in annotation overlay plugin. |
83
+ | `blazeplot/plugins/selection` | Built-in brush/range selection plugin. |
84
+ | `blazeplot/plugins/crosshair` | Built-in crosshair and ruler plugin. |
85
+ | `blazeplot/plugins/navigator` | Built-in overview/navigator plugin. |
86
+
87
+ ### Selected generated declarations
88
+
89
+ These member tables are generated from TypeScript declarations.
90
+
91
+ <details>
92
+ <summary>class Chart</summary>
93
+
94
+ | Member |
95
+ |---|
96
+ | `constructor(target: HTMLElement, options?: ChartOptions)` |
97
+ | `get canvas(): HTMLCanvasElement` |
98
+ | `get rootElement(): HTMLElement` |
99
+ | `get plotElement(): HTMLElement` |
100
+ | `get xAxisElement(): HTMLElement` |
101
+ | `get yAxisElement(): HTMLElement` |
102
+ | `get y2AxisElement(): HTMLElement` |
103
+ | `get theme(): ResolvedChartTheme` |
104
+ | `getWebGLContext(): WebGL2RenderingContext \\| null` |
105
+ | `getCamera(yAxis?: SeriesYAxis): Camera2D` |
106
+ | `dataToPlot(x: number, y: number, yAxis?: SeriesYAxis): [ number, number ]` |
107
+ | `clientToData(clientX: number, clientY: number, yAxis?: SeriesYAxis): [ number, number ] \\| null` |
108
+ | `getViewport(yAxis?: SeriesYAxis): Viewport` |
109
+ | `pan(intent: PanIntent): void` |
110
+ | `zoom(intent: ZoomIntent): void` |
111
+ | `addSeries(config: SeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
112
+ | `addLine(config: TypedSeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
113
+ | `addArea(config: TypedSeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
114
+ | `addScatter(config: TypedSeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
115
+ | `addBar(config: TypedSeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
116
+ | `addOhlc(config: TypedSeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
117
+ | `addCandlestick(config: TypedSeriesConfig, style?: Partial<SeriesStyle>): SeriesStore` |
118
+ | `removeSeries(series: SeriesStore): boolean` |
119
+ | `setSeriesVisible(series: SeriesStore, visible: boolean): boolean` |
120
+ | `getSeriesState(): ChartSeriesState[]` |
121
+ | `setViewport(v: { xMin?: number; xMax?: number; yMin?: number; yMax?: number; }): void` |
122
+ | `setYViewport(yAxis: SeriesYAxis, v: { yMin?: number; yMax?: number; }): void` |
123
+ | `resize(dpr?: number): boolean` |
124
+ | `getFrameStats(target?: ChartFrameStats): ChartFrameStats` |
125
+ | `getHoverState(): ChartHoverState \\| null` |
126
+ | `setLayoutReservation(id: string, reservation: ChartLayoutReservation \\| null): void` |
127
+ | `subscribe(event: "hover", callback: (state: ChartHoverState \\| null) => void): () => void` |
128
+ | `subscribe(event: "serieschange", callback: () => void): () => void` |
129
+ | `subscribe(event: "themechange", callback: () => void): () => void` |
130
+ | `subscribe(event: "render", callback: (chart: Chart) => void): () => void` |
131
+ | `subscribe(event: "viewportchange", callback: (event: ChartViewportChangeEvent) => void): () => void` |
132
+ | `subscribe(event: "select", callback: (event: ChartSelectEvent) => void): () => void` |
133
+ | `subscribe(event: "seriesclick", callback: (event: ChartSeriesClickEvent) => void): () => void` |
134
+ | `subscribe(event: ChartPointerEventType, callback: (event: ChartPointerEventState) => void): () => void` |
135
+ | `emitSelect(selection: unknown): void` |
136
+ | `setTheme(theme?: ChartTheme): void` |
137
+ | `setGridVisible(visible: boolean): void` |
138
+ | `getGridVisible(): boolean` |
139
+ | `setAxes(axes: ChartOptions["axes"]): void` |
140
+ | `pick(clientX: number, clientY: number, options?: ChartPickOptions): ChartHoverState \\| null` |
141
+ | `screenshot(options?: ChartScreenshotOptions): Promise<Blob>` |
142
+ | `start(): void` |
143
+ | `stop(): void` |
144
+ | `dispose(): void` |
145
+
146
+ </details>
147
+
148
+ <details>
149
+ <summary>interface ChartOptions</summary>
150
+
151
+ | Member |
152
+ |---|
153
+ | `viewportPolicy?: ViewportPolicy` |
154
+ | `grid?: boolean` |
155
+ | `gridStyle?: Partial<SeriesStyle>` |
156
+ | `axes?: boolean \\| { x?: boolean \\| AxisConfig; y?: boolean \\| AxisConfig; y2?: boolean \\| AxisConfig; }` |
157
+ | `title?: string \\| ChartTitleConfig` |
158
+ | `subtitle?: string \\| ChartTitleConfig` |
159
+ | `hover?: ChartPickOptions` |
160
+ | `plugins?: readonly ChartPlugin[]` |
161
+ | `theme?: ChartTheme` |
162
+
163
+ </details>
164
+
165
+ <details>
166
+ <summary>interface ChartTheme</summary>
167
+
168
+ | Member |
169
+ |---|
170
+ | `backgroundColor?: ThemeColor` |
171
+ | `gridColor?: ThemeColor` |
172
+ | `axisColor?: string` |
173
+ | `axisFont?: string` |
174
+ | `seriesColors?: readonly ThemeColor[]` |
175
+ | `tooltipBackgroundColor?: string` |
176
+ | `tooltipTextColor?: string` |
177
+ | `tooltipFont?: string` |
178
+ | `legendBackgroundColor?: string` |
179
+ | `legendBorderColor?: string` |
180
+ | `legendTextColor?: string` |
181
+ | `legendMutedTextColor?: string` |
182
+ | `legendFont?: string` |
183
+ | `titleColor?: string` |
184
+ | `titleFont?: string` |
185
+ | `subtitleColor?: string` |
186
+ | `subtitleFont?: string` |
187
+ | `axisTitleColor?: string` |
188
+ | `axisTitleFont?: string` |
189
+
190
+ </details>
191
+
192
+ <details>
193
+ <summary>interface AxisConfig</summary>
194
+
195
+ | Member |
196
+ |---|
197
+ | `visible?: boolean` |
198
+ | `position?: AxisPosition` |
199
+ | `scale?: AxisScale` |
200
+ | `tickFormat?: AxisTickFormat` |
201
+ | `timezone?: AxisTimeZone` |
202
+ | `title?: string \\| AxisTitleConfig` |
203
+
204
+ </details>
205
+
206
+ <details>
207
+ <summary>class SeriesStore</summary>
208
+
209
+ | Member |
210
+ |---|
211
+ | `config: SeriesConfig` |
212
+ | `style: SeriesStyle` |
213
+ | `constructor(dataset: Dataset, config: SeriesConfig, style: SeriesStyle)` |
214
+ | `get hasLOD(): boolean` |
215
+ | `get dirty(): boolean` |
216
+ | `get length(): number` |
217
+ | `get visible(): boolean` |
218
+ | `setVisible(visible: boolean): void` |
219
+ | `append(x: ArrayLike<number>, y: ArrayLike<number>): void` |
220
+ | `appendY(y: ArrayLike<number>): void` |
221
+ | `markDirty(): void` |
222
+ | `clear(): void` |
223
+ | `rebuildPyramid(): void` |
224
+ | `query(viewport: Viewport, pixelWidth: number): LODView` |
225
+ | `visibleSampleCount(viewport: Viewport): number` |
226
+ | `sampleAt(index: number): SeriesSample \\| null` |
227
+ | `nearestSampleByX(x: number, viewport?: Viewport): SeriesSample \\| null` |
228
+ | `nearestSampleByPoint(x: number, y: number, viewport: Viewport, plotWidth: number, plotHeight: number, maxDistancePx?: number): SeriesSample \\| null` |
229
+ | `copyRawVisible(viewport: Viewport, target: Float32Array, maxPoints: number, xOrigin?: number): number` |
230
+ | `copyScatterVisible(viewport: Viewport, target: Float32Array, maxPoints: number, pixelWidth: number, pixelHeight: number, pointSize: number, xOrigin?: number): number` |
231
+ | `copyScatterRange(start: number, end: number, viewport: Viewport, target: Float32Array, maxPoints: number, xOrigin?: number, pixelHeight?: number, pointSize?: number): number` |
232
+ | `copyRawVisibleClipped(viewport: Viewport, target: Float32Array, maxPoints: number, xOrigin?: number): number` |
233
+ | `copyRawVisibleClipSpace(viewport: Viewport, target: Float32Array, maxPoints: number): number` |
234
+ | `copyRawRange(start: number, end: number, target: Float32Array, maxPoints: number, xOrigin?: number): number` |
235
+ | `copyAreaVisible(viewport: Viewport, target: Float32Array, maxPoints: number, baseline?: number, xOrigin?: number): number` |
236
+ | `copyAreaRange(start: number, end: number, target: Float32Array, maxPoints: number, baseline?: number, xOrigin?: number): number` |
237
+ | `copyMinMaxVisible(viewport: Viewport, target: Float32Array, maxSegments: number, xOrigin?: number): number` |
238
+ | `copyMinMaxInstanced(viewport: Viewport, target: Float32Array, maxSegments: number, xOrigin?: number): number` |
239
+ | `copyOhlcRange(start: number, end: number, target: Float32Array, maxCandles: number, tickWidth: number, xOrigin?: number): number` |
240
+ | `copyOhlcTuplesRange(start: number, end: number, target: Float32Array, maxCandles: number, xOrigin?: number): number` |
241
+ | `visibleIndexRange(viewport: Viewport \\| undefined, outerPadding?: number): { start: number; end: number; }` |
242
+
243
+ </details>
244
+
245
+ <details>
246
+ <summary>interface SeriesConfig</summary>
247
+
248
+ | Member |
249
+ |---|
250
+ | `mode: SeriesMode` |
251
+ | `capacity?: number` |
252
+ | `downsample?: LODStrategy` |
253
+ | `overflow?: BufferOverflowStrategy` |
254
+ | `dataset?: Dataset` |
255
+ | `yAxis?: SeriesYAxis` |
256
+ | `id?: string` |
257
+ | `name?: string` |
258
+
259
+ </details>
260
+
261
+ <details>
262
+ <summary>interface SeriesStyle</summary>
263
+
264
+ | Member |
265
+ |---|
266
+ | `color: readonly [ number, number, number, number ]` |
267
+ | `lineWidth: number` |
268
+ | `pointSize?: number` |
269
+ | `barWidth?: number` |
270
+ | `baseline?: number` |
271
+ | `fillColor?: readonly [ number, number, number, number ]` |
272
+ | `tickWidth?: number` |
273
+ | `upColor?: readonly [ number, number, number, number ]` |
274
+ | `downColor?: readonly [ number, number, number, number ]` |
275
+ | `wickColor?: readonly [ number, number, number, number ]` |
276
+
277
+ </details>
278
+
279
+ <details>
280
+ <summary>class UniformRingBuffer</summary>
281
+
282
+ | Member |
283
+ |---|
284
+ | `capacity: number` |
285
+ | `xStep: number` |
286
+ | `constructor(capacity: number, options?: UniformRingBufferOptions)` |
287
+ | `get length(): number` |
288
+ | `get range(): TimeRange \\| null` |
289
+ | `push(x: number, y: number): void` |
290
+ | `append(x: ArrayLike<number>, y: ArrayLike<number>): void` |
291
+ | `appendY(y: ArrayLike<number>): void` |
292
+ | `clear(): void` |
293
+ | `getX(index: number): number` |
294
+ | `getY(index: number): number` |
295
+ | `lowerBoundX(x: number): number` |
296
+ | `upperBoundX(x: number): number` |
297
+ | `rangeMinMaxY(start: number, end: number): { minY: number; maxY: number; } \\| null` |
298
+ | `copyVisibleSamples(viewport: Viewport, target: Float32Array, maxPoints: number, layout: SampleLayout, baseline: number, xOrigin: number): number` |
299
+ | `copySamplesRange(start: number, end: number, target: Float32Array, maxPoints: number, layout: SampleLayout, baseline: number, xOrigin: number): number` |
300
+ | `copyMinMaxSegments(viewport: Viewport, target: Float32Array, maxSegments: number, layout: MinMaxLayout, xOrigin: number): number` |
301
+
302
+ </details>
303
+
304
+ <details>
305
+ <summary>class RingBuffer</summary>
306
+
307
+ | Member |
308
+ |---|
309
+ | `capacity: number` |
310
+ | `constructor(capacity: number, options?: RingBufferOptions)` |
311
+ | `get length(): number` |
312
+ | `get range(): TimeRange \\| null` |
313
+ | `push(x: number, y: number): void` |
314
+ | `append(x: ArrayLike<number>, y: ArrayLike<number>): void` |
315
+ | `get(index: number): { x: number; y: number; } \\| null` |
316
+ | `getX(index: number): number` |
317
+ | `getY(index: number): number` |
318
+ | `lowerBoundX(x: number): number` |
319
+ | `upperBoundX(x: number): number` |
320
+ | `rangeMinMaxY(start: number, end: number): { minY: number; maxY: number; } \\| null` |
321
+ | `clear(): void` |
322
+
323
+ </details>
324
+
325
+ <details>
326
+ <summary>class StaticDataset</summary>
327
+
328
+ | Member |
329
+ |---|
330
+ | `constructor(xData: ArrayLike<number>, yData: ArrayLike<number>)` |
331
+ | `get length(): number` |
332
+ | `get range(): TimeRange \\| null` |
333
+ | `getX(index: number): number` |
334
+ | `getY(index: number): number` |
335
+ | `lowerBoundX(x: number): number` |
336
+ | `upperBoundX(x: number): number` |
337
+
338
+ </details>
339
+
340
+ <details>
341
+ <summary>class OhlcRingBuffer</summary>
342
+
343
+ | Member |
344
+ |---|
345
+ | `capacity: number` |
346
+ | `constructor(capacity: number, options?: OhlcRingBufferOptions)` |
347
+ | `get length(): number` |
348
+ | `get range(): TimeRange \\| null` |
349
+ | `push(x: number, open: number, high: number, low: number, close: number): void` |
350
+ | `append(x: ArrayLike<number>, open: ArrayLike<number>, high: ArrayLike<number>, low: ArrayLike<number>, close: ArrayLike<number>): void` |
351
+ | `clear(): void` |
352
+ | `getX(index: number): number` |
353
+ | `getY(index: number): number` |
354
+ | `getOpen(index: number): number` |
355
+ | `getHigh(index: number): number` |
356
+ | `getLow(index: number): number` |
357
+ | `getClose(index: number): number` |
358
+ | `lowerBoundX(x: number): number` |
359
+ | `upperBoundX(x: number): number` |
360
+
361
+ </details>
362
+
363
+ <details>
364
+ <summary>interface Dataset</summary>
365
+
366
+ | Member |
367
+ |---|
368
+ | `length: number` |
369
+ | `range: TimeRange \\| null` |
370
+ | `getX(index: number): number` |
371
+ | `getY(index: number): number` |
372
+ | `lowerBoundX(x: number): number` |
373
+ | `upperBoundX(x: number): number` |
374
+
375
+ </details>
376
+
377
+ <details>
378
+ <summary>interface ViewportPolicy</summary>
379
+
380
+ | Member |
381
+ |---|
382
+ | `beforePan(camera: Camera2D, intent: PanIntent): PanIntent \\| null` |
383
+ | `beforeZoom(camera: Camera2D, intent: ZoomIntent): ZoomIntent \\| null` |
384
+ | `beforeRender(camera: Camera2D): void` |
385
+
386
+ </details>
387
+
388
+ ### All public exports
389
+
390
+ <details>
391
+ <summary>Generated from <code>dist/index.d.ts</code> after the package build</summary>
392
+
393
+ | Export | Kind | Source | JSDoc summary |
394
+ |---|---|---|---|
395
+ | `AcceleratedDataset` | interface | `./core/types` | Convenience contract for maximum-performance custom datasets. Implement this when a dataset can provide fast exact sample copies, stable viewport sampling, range min/max queries, and renderer-ready min/max buckets. |
396
+ | `AppendableDataset` | interface | `./core/types` | — |
397
+ | `AxisConfig` | interface | `./ui/Chart` | — |
398
+ | `AxisController` | class | `./interaction/AxisController` | — |
399
+ | `AxisControllerAxisOptions` | interface | `./interaction/AxisController` | — |
400
+ | `AxisControllerOptions` | interface | `./interaction/AxisController` | — |
401
+ | `AxisPosition` | type | `./ui/ChartLayout` | — |
402
+ | `AxisRenderTarget` | type | `./interaction/AxisController` | — |
403
+ | `AxisScale` | type | `./interaction/AxisController` | — |
404
+ | `AxisTickFormat` | type | `./interaction/AxisController` | — |
405
+ | `AxisTickFormatter` | type | `./interaction/AxisController` | — |
406
+ | `AxisTimeZone` | type | `./interaction/AxisController` | — |
407
+ | `AxisTitleConfig` | interface | `./ui/Chart` | — |
408
+ | `BufferOverflowStrategy` | type | `./core/types` | — |
409
+ | `BuiltInAxisScale` | type | `./interaction/AxisController` | — |
410
+ | `Camera2D` | class | `./interaction/Camera2D` | — |
411
+ | `Chart` | class | `./ui/Chart` | — |
412
+ | `ChartFrameStats` | interface | `./ui/Chart` | — |
413
+ | `ChartHoverState` | interface | `./ui/Chart` | — |
414
+ | `ChartLayoutReservation` | interface | `./ui/Chart` | — |
415
+ | `ChartOptions` | interface | `./ui/Chart` | — |
416
+ | `ChartPickGroup` | type | `./ui/Chart` | — |
417
+ | `ChartPickItem` | interface | `./ui/Chart` | — |
418
+ | `ChartPickMode` | type | `./ui/Chart` | — |
419
+ | `ChartPickOptions` | interface | `./ui/Chart` | — |
420
+ | `ChartPlugin` | interface | `./ui/Chart` | — |
421
+ | `ChartPluginHandle` | interface | `./ui/Chart` | — |
422
+ | `ChartPointerEventState` | interface | `./ui/Chart` | — |
423
+ | `ChartPointerEventType` | type | `./ui/Chart` | — |
424
+ | `ChartScreenshotOptions` | interface | `./ui/Chart` | — |
425
+ | `ChartSelectEvent` | interface | `./ui/Chart` | — |
426
+ | `ChartSeriesClickEvent` | interface | `./ui/Chart` | — |
427
+ | `ChartSeriesState` | interface | `./ui/Chart` | — |
428
+ | `ChartTheme` | interface | `./ui/theme` | — |
429
+ | `ChartTitleConfig` | interface | `./ui/Chart` | — |
430
+ | `ChartViewportChangeEvent` | interface | `./ui/Chart` | — |
431
+ | `CssColor` | type | `./ui/theme` | — |
432
+ | `CustomAxisScale` | interface | `./interaction/AxisController` | — |
433
+ | `Dataset` | interface | `./core/types` | — |
434
+ | `DEFAULT_CHART_THEME` | const | `./ui/theme` | — |
435
+ | `LODBucket` | interface | `./core/types` | — |
436
+ | `LODStrategy` | type | `./core/types` | — |
437
+ | `LODView` | interface | `./core/types` | — |
438
+ | `MinMaxPyramid` | class | `./core/MinMaxPyramid` | — |
439
+ | `MinMaxSegmentCopyDataset` | interface | `./core/types` | Optional high-performance min/max extraction capability for dense rendering. Implementations can use pyramids, segment trees, database aggregates, or analytic/procedural envelopes to emit renderer-ready min/max buckets. |
440
+ | `MinMaxSegmentLayout` | type | `./core/types` | — |
441
+ | `OhlcDataset` | interface | `./core/types` | — |
442
+ | `OhlcRingBuffer` | class | `./core/OhlcDataset` | — |
443
+ | `OhlcRingBufferOptions` | interface | `./core/OhlcDataset` | — |
444
+ | `PanIntent` | interface | `./interaction/types` | — |
445
+ | `RangeMinMaxDataset` | interface | `./core/types` | — |
446
+ | `RangeSampleCopyDataset` | interface | `./core/types` | Optional high-performance extraction capability for datasets that can copy raw samples without going through repeated getX/getY calls. Implement this for very large datasets, implicit-X datasets, or remote/memory-mapped sources. |
447
+ | `ResolvedChartTheme` | interface | `./ui/theme` | — |
448
+ | `RgbaColor` | type | `./ui/theme` | — |
449
+ | `RingBuffer` | class | `./core/RingBuffer` | — |
450
+ | `RingBufferOptions` | interface | `./core/RingBuffer` | — |
451
+ | `RingBufferOverflow` | type | `./core/RingBuffer` | — |
452
+ | `SampleCopyLayout` | type | `./core/types` | — |
453
+ | `SeriesConfig` | interface | `./core/types` | — |
454
+ | `SeriesMode` | type | `./core/types` | — |
455
+ | `SeriesSample` | interface | `./core/types` | — |
456
+ | `SeriesStore` | class | `./core/SeriesStore` | — |
457
+ | `SeriesStyle` | interface | `./core/types` | — |
458
+ | `SeriesYAxis` | type | `./core/types` | — |
459
+ | `StaticDataset` | class | `./core/StaticDataset` | — |
460
+ | `StaticOhlcDataset` | class | `./core/OhlcDataset` | — |
461
+ | `TextOverlayConfig` | interface | `./ui/Chart` | — |
462
+ | `ThemeColor` | type | `./ui/theme` | — |
463
+ | `TimeRange` | interface | `./core/types` | — |
464
+ | `TypedSeriesConfig` | type | `./ui/Chart` | — |
465
+ | `UniformRingBuffer` | class | `./core/UniformRingBuffer` | High-throughput ring buffer for uniformly spaced X values. Store only Y samples and derive X as `xStart + index * xStep`. This is the fastest built-in dataset for live telemetry, signals, and other fixed-rate streams because appends copy a single typed array and min/max extraction uses a block segment tree over the physical ring. |
466
+ | `UniformRingBufferOptions` | interface | `./core/UniformRingBuffer` | — |
467
+ | `Viewport` | interface | `./core/types` | — |
468
+ | `ViewportPolicy` | interface | `./interaction/types` | — |
469
+ | `VisiblePointCopyDataset` | interface | `./core/types` | Optional high-performance extraction capability for point/scatter datasets. Implementations should cull against the full 2D viewport and may sample in screen space so dense point clouds respond to both X and Y zoom. |
470
+ | `VisibleSampleCopyDataset` | interface | `./core/types` | Optional high-performance stable visible sampling capability. Unlike copySamplesRange, this method may stride/downsample, but should choose samples anchored to data coordinates so streamed appends do not make existing sampled points jitter. |
471
+ | `YAppendableDataset` | interface | `./core/types` | — |
472
+ | `ZoomAxis` | type | `./interaction/types` | — |
473
+ | `ZoomIntent` | interface | `./interaction/types` | — |
474
+
475
+ </details>
476
+ <!-- README_DOCS_END -->
238
477
 
239
478
  ## Architecture
240
479
 
@@ -246,12 +485,27 @@ src/
246
485
  ui/ # Orchestrator (Chart)
247
486
  ```
248
487
 
488
+ ## Contributing
489
+
490
+ Keep pull requests small and focused. One feature or fix per PR.
491
+
492
+ Branch flow:
493
+
494
+ - `development` is the integration branch for regular work.
495
+ - Open feature/fix PRs into `development`.
496
+ - Open release PRs from `development` into `main` with the version and changelog already updated.
497
+ - Merging an unpublished version to `main` runs CI, publishes npm, creates the `vX.Y.Z` tag, and creates the GitHub Release.
498
+
249
499
  ## Development
250
500
 
251
501
  ```bash
252
502
  bun install
253
- bun run dev # Vite dev server → preview/
254
- bun run build # Package build (JS + declarations)
255
- bun test # Tests
256
- bun run typecheck # TypeScript strict check
503
+ bun run dev # Vite dev server → preview/
504
+ bun run ci # Typecheck + tests + package build + benchmark smoke test
505
+ bun run build # Package build (JS + declarations)
506
+ bun test # Tests
507
+ bun run typecheck # TypeScript strict check
508
+ bun run bench:ci # Headless browser benchmark smoke test
509
+ bun run version:patch # Prepare package.json + changelog for a patch release PR
510
+ bun run release:benchmarks # Append benchmark results to the current release changelog
257
511
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blazeplot",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "author": {
5
5
  "name": "Federico Cervelli",
6
6
  "url": "https://cervelli.dev"
@@ -68,17 +68,21 @@
68
68
  "scripts": {
69
69
  "dev": "vite",
70
70
  "build": "vite build",
71
+ "bench:ci": "bun run bench --scenario ci-smoke --measure-ms 500 --warmup-ms 100 --width 800 --height 450 --top 12 --setup-timeout-ms 60000",
72
+ "ci": "bun run typecheck && bun test && bun run build && bun run bench:ci",
71
73
  "build:js": "vite build --mode js-only",
72
74
  "pages:build": "vite build --config vite.pages.config.ts",
73
75
  "preview": "vite preview",
74
76
  "bench": "bun scripts/benchmark.ts",
75
77
  "bench:scatter": "bun scripts/scatter-profile.ts",
76
78
  "bench:report": "bun scripts/benchmark-report.ts",
77
- "release:patch": "node scripts/release.js patch",
78
- "release:minor": "node scripts/release.js minor",
79
- "release:major": "node scripts/release.js major",
79
+ "release:benchmarks": "bun scripts/release-benchmarks.ts",
80
+ "docs:readme": "bun run build && node scripts/generate-readme-docs.js",
80
81
  "test": "bun test",
81
- "typecheck": "tsc --noEmit"
82
+ "typecheck": "tsc --noEmit",
83
+ "version:patch": "node scripts/bump-version.js patch",
84
+ "version:minor": "node scripts/bump-version.js minor",
85
+ "version:major": "node scripts/bump-version.js major"
82
86
  },
83
87
  "peerDependencies": {
84
88
  "react": ">=18"