goro-charts 1.0.0 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,119 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0] — 2026-07-06
9
+
10
+ ### Behavior fixes
11
+
12
+ - **Data ownership contract (`copy`/`borrowed`).**
13
+ `setData` now **copies** the arrays by default (`'copy'`), making the chart
14
+ immune to external mutation. The `'borrowed'` (zero-copy) mode is available
15
+ as an opt-in but requires the caller to treat the arrays as immutable.
16
+ _(minor, behavior fix — the default semantics changed from borrowed to copy)_
17
+ - **Numeric input validation.**
18
+ Length mismatches, non-monotonic X, non-finite X (`Infinity`, `-Infinity`,
19
+ `NaN`), and non-finite Y (`±Infinity`) are now rejected with a descriptive
20
+ error naming the series and position — the same contract applies both in
21
+ snapshot mode (`setData`) and in streaming mode (`append`/`appendBatch`).
22
+ `NaN` in Y is the only accepted exception (see below). Inputs previously
23
+ accepted silently (or only with a `console.warn`) now throw.
24
+ _(minor, behavior fix)_
25
+ - **Non-monotonic append now throws.**
26
+ The previous `console.warn` was promoted to a thrown error to fail fast.
27
+ `appendBatch` validates the entire batch before pushing any sample — partial
28
+ batches never corrupt the ring state. _(minor, behavior fix)_
29
+ - **NaN in Y accepted and documented.**
30
+ `NaN` in Y is accepted, excluded from the extent computation, and reserved
31
+ for gap rendering in v1.6.0. Arrays where every Y is `NaN` produce a safe
32
+ degenerate range. _(minor, behavior fix)_
33
+
34
+ ### Added
35
+
36
+ - `DataOwnership = 'copy' | 'borrowed'` type and the optional `ownership`
37
+ parameter on `setData(index, x, y, ownership?)`.
38
+ - `npm run check:readme` script — extracts every `ts` block from the README
39
+ and typechecks it against the real exported types. Wired into CI.
40
+ - New `Check README examples` CI step (before Typecheck).
41
+ - Reserved (commented-out TODO) slot for `docs/assets/streaming.gif` in the
42
+ README's first fold — the `![…]` stays commented until the asset exists, so
43
+ a broken image is never published.
44
+
45
+ ### Changed
46
+
47
+ - `setData(index, x, y)` now takes an optional fourth argument `ownership`
48
+ (default `'copy'`).
49
+ - README signature-reference blocks (e.g. `new LineChart(canvas, opts?:
50
+ ChartOpts)`) are marked with `// signature` and skipped by `check:readme`,
51
+ keeping the readable form without breaking the semantic checking of the
52
+ runnable examples.
53
+
54
+ ## [1.2.0] — 2026-07-06
55
+
56
+ ### Behavior fixes
57
+
58
+ - **Crosshair sync by X value, not by pixel.**
59
+ Charts with different sizes, margins, and domains now sync correctly: the
60
+ data value is converted from pixel at the origin and back to pixel at the
61
+ target via pxToX/xToPx. A value outside the domain hides the crosshair on
62
+ the target. _(minor, behavior fix)_
63
+ - **Stacking separates positives and negatives.**
64
+ Positives accumulate on an ascending track, negatives on a descending track,
65
+ without cancelling each other out. In development, series in the same `stack`
66
+ with divergent axes or lengths emit a descriptive warning.
67
+ _(minor, behavior fix)_
68
+ - **`renderedPointCount` renamed to `windowPointCount`.**
69
+ The old metric lied: it returned the total points in the window, not those
70
+ actually drawn (the renderer decimates to ~2·plotW columns in the dense
71
+ regime). There are now two honest metrics: `windowPointCount` (data volume)
72
+ and `drawnPointCount` (post-decimation estimate). Anyone using
73
+ `renderedPointCount` should migrate to `windowPointCount`.
74
+ _(minor, behavior fix)_
75
+
76
+ ### Added
77
+
78
+ - `unsync(other)` — removes bidirectional crosshair synchronization.
79
+ - `drawnPointCount` — estimate of segments actually drawn after decimation
80
+ (useful to verify that decimation is active).
81
+ - Stacking: alignment validation between series in the same group (axis and
82
+ length) in development.
83
+
84
+ ### Changed
85
+
86
+ - `injectCursor` (private) now receives an X value instead of a pixel —
87
+ aligned with value-based sync. `notifySyncCrosshair` sends a value instead
88
+ of a screen coordinate.
89
+ - `destroy` now removes the chart from all synced peers before clearing the
90
+ stores.
91
+ - `accumulateStackGroup` returns separate `{ posCum, negCum }` instead of a
92
+ single net accumulation.
93
+
94
+ ## [1.1.0] — 2026-07-06
95
+
96
+ ### Behavior fixes
97
+
98
+ - **`yMin`/`yMax` sentinel: `0` is now a legitimate bound.**
99
+ Previously `yMin: 0` and `yMax: 0` were treated as "auto" (discarded). They
100
+ now fall back to an automatic domain only when `undefined`. An anchored
101
+ `yMin: 0` is the most common case and finally works. _(minor, behavior fix)_
102
+ - **Keyboard: navigation by data point, not by pixel.**
103
+ The arrow keys move the crosshair point by point (logical) across the first
104
+ non-empty series. `Shift+arrow` advances 10 points. It previously navigated
105
+ by pixel, without anchoring to real data. _(minor, behavior fix)_
106
+ - **`prefers-reduced-motion` no longer stops streaming.**
107
+ It previously turned off `autoDraw`, interrupting the coalesced repaint of
108
+ live data. It now only sets a flag to suppress visual animations (when
109
+ present) without affecting the chart's continuous updates.
110
+ _(minor, behavior fix)_
111
+
112
+ ### Changed
113
+
114
+ - `ResolvedOpts.yMin` / `ResolvedOpts.yMax` are now `number | undefined`.
115
+ The "auto" sentinel changed from `0` to `undefined`. Code that relied on the
116
+ old behavior (e.g. `yMin !== 0` checks) should use `yMin !== undefined`.
117
+ - `prefers-reduced-motion` adds a runtime `change` listener to re-evaluate the
118
+ preference without recreating the chart. The listener is removed in
119
+ `destroy()`.
120
+
8
121
  ## [1.0.0]
9
122
 
10
123
  Initial stable release.
package/README.md CHANGED
@@ -7,6 +7,10 @@
7
7
 
8
8
  **[Live demo →](https://stefanelloisaac.github.io/goro-charts/)**
9
9
 
10
+ <!-- TODO: gravar e adicionar docs/assets/streaming.gif, depois descomentar:
11
+ ![Live streaming demo](docs/assets/streaming.gif)
12
+ -->
13
+
10
14
  Minimal high-performance chart engine. **Canvas 2D only. Zero runtime dependencies. Framework-agnostic.** Inspired by [uPlot](https://github.com/leeoniya/uPlot) — small, fast, and covers only what you need.
11
15
 
12
16
  - **LineChart** — batched polyline with per-pixel-column min/max decimation
@@ -59,6 +63,7 @@ chart.appendBatch(1, [1, 2, 3], [39, 40, 39]);
59
63
  Every chart holds one or more series. Each series owns its visual identity:
60
64
 
61
65
  ```ts
66
+ // signature
62
67
  import type { SeriesConfig } from 'goro-charts';
63
68
 
64
69
  interface SeriesConfig {
@@ -94,6 +99,7 @@ When `series` is omitted from `ChartOpts` a single default series is created aut
94
99
  ### `LineChart`
95
100
 
96
101
  ```ts
102
+ // signature
97
103
  new LineChart(canvas, opts?: ChartOpts)
98
104
  ```
99
105
 
@@ -102,6 +108,7 @@ Each series is drawn as a single batched polyline. When the dataset is dense (mo
102
108
  ### `AreaChart`
103
109
 
104
110
  ```ts
111
+ // signature
105
112
  new AreaChart(canvas, opts?: ChartOpts)
106
113
  ```
107
114
 
@@ -123,6 +130,7 @@ const chart = new AreaChart(canvas, {
123
130
  ### `ScatterChart`
124
131
 
125
132
  ```ts
133
+ // signature
126
134
  new ScatterChart(canvas, opts?: ChartOpts)
127
135
  ```
128
136
 
@@ -131,9 +139,10 @@ Each series is drawn as filled circles, one per sampled point. When the dataset
131
139
  ```ts
132
140
  const chart = new ScatterChart(canvas, {
133
141
  series: [
134
- { name: 'Packets', color: '#f07167', pointRadius: 3.5 },
142
+ { name: 'Packets', color: '#f07167' },
135
143
  { name: 'Errors', color: '#ffb454', dash: [6, 3] },
136
144
  ],
145
+ pointRadius: 3.5,
137
146
  maxPoints: 5000,
138
147
  autoDraw: true,
139
148
  });
@@ -145,18 +154,33 @@ const chart = new ScatterChart(canvas, {
145
154
 
146
155
  Every data method takes a **series index** as the first argument. `setMaxPoints()`, `clear()`, and `draw()` operate on all series.
147
156
 
148
- | Method | Signature | Description |
149
- | -------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
150
- | `setData` | `(seriesIndex, x: Float64Array, y: Float64Array)` | Snapshot: replace a series. O(n) extent. |
151
- | `append` | `(seriesIndex, x: number, y: number)` | Ring: append one point. O(1) amortized. |
152
- | `appendBatch` | `(seriesIndex, xs: ArrayLike<number>, ys: ArrayLike<number>)` | Ring: append a batch. O(k). |
153
- | `setMaxPoints` | `(maxPoints: number)` | Resize the streaming window for all series. |
154
- | `clear` | `()` | Empty all series and reset the grid domain. |
155
- | `draw` | `()` | Manual paint. No-op when clean and no crosshair. |
156
- | `suspendDraw` | `()` | Pause rAF-coalesced drawing. Nestable — pair with `resumeDraw()`. |
157
- | `resumeDraw` | `()` | Resume after matching `suspendDraw()`. Draws immediately if dirty. |
158
- | `toImage` | `()` | Export the canvas as a PNG data URL. |
159
- | `destroy` | `()` | Detach observers, release buffers. |
157
+ | Method | Signature | Description |
158
+ | -------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
+ | `setData` | `(seriesIndex, x: Float64Array, y: Float64Array, ownership?: 'copy' \| 'borrowed')` | Snapshot: replace a series. O(n) extent. Ownership: `'copy'` (default, safe) copies arrays; `'borrowed'` keeps caller's reference (must be treated as immutable). |
160
+ | `append` | `(seriesIndex, x: number, y: number)` | Ring: append one point. O(1) amortized. |
161
+ | `appendBatch` | `(seriesIndex, xs: ArrayLike<number>, ys: ArrayLike<number>)` | Ring: append a batch. O(k). |
162
+ | `setMaxPoints` | `(maxPoints: number)` | Resize the streaming window for all series. |
163
+ | `clear` | `()` | Empty all series and reset the grid domain. |
164
+ | `draw` | `()` | Manual paint. No-op when clean and no crosshair. |
165
+ | `suspendDraw` | `()` | Pause rAF-coalesced drawing. Nestable — pair with `resumeDraw()`. |
166
+ | `resumeDraw` | `()` | Resume after matching `suspendDraw()`. Draws immediately if dirty. |
167
+ | `toImage` | `()` | Export the canvas as a PNG data URL. |
168
+ | `destroy` | `()` | Detach observers, release buffers. |
169
+
170
+ ### Ownership (`copy` vs `borrowed`)
171
+
172
+ `setData` accepts an optional fourth argument to control data ownership:
173
+
174
+ - **`'copy'`** (default) — the chart copies your arrays into fresh buffers. You may reuse or mutate the originals freely after the call. This is the safe default.
175
+ - **`'borrowed'`** — the chart keeps your arrays by reference to avoid allocation. The caller **must** treat the arrays as immutable for as long as the chart holds them; mutating them externally leads to undefined behaviour.
176
+
177
+ ```ts
178
+ // Safe default — caller can mutate the originals later
179
+ chart.setData(0, x, y);
180
+
181
+ // Zero-copy — caller must not mutate x or y after this call
182
+ chart.setData(0, x, y, 'borrowed');
183
+ ```
160
184
 
161
185
  ### Read-only properties
162
186
 
@@ -335,13 +359,13 @@ flat regardless of window size.
335
359
  Pre-built `DARK` and `LIGHT` colour presets ready to spread over constructor options.
336
360
 
337
361
  ```ts
338
- import { LineChart, DARK, LIGHT } from 'goro-charts'
362
+ import { LineChart, DARK, LIGHT } from 'goro-charts';
339
363
 
340
364
  // Dark (default) — explicit
341
- new LineChart(canvas, { ...DARK, series: [...] })
365
+ new LineChart(canvas, { ...DARK, series: [/* ... */] });
342
366
 
343
367
  // Light theme
344
- new LineChart(canvas, { ...LIGHT, series: [...] })
368
+ new LineChart(canvas, { ...LIGHT, series: [/* ... */] });
345
369
  ```
346
370
 
347
371
  ---
@@ -402,9 +426,7 @@ tooltips, update framework state, or pipe values into external widgets.
402
426
 
403
427
  ```ts
404
428
  chart.onHover = (hits) => {
405
- myTooltipEl.innerHTML = hits
406
- .map((h) => `${h.label}: ${h.yVal.toFixed(1)}`)
407
- .join('<br>');
429
+ myTooltipEl.innerHTML = hits.map((h) => `${h.label}: ${h.yVal.toFixed(1)}`).join('<br>');
408
430
  };
409
431
  ```
410
432
 
@@ -454,6 +476,7 @@ outside the canvas.
454
476
  ## Troubleshooting
455
477
 
456
478
  ### Canvas is blank / black
479
+
457
480
  - Ensure the canvas element has non-zero CSS width and height.
458
481
  - Check that `chart.draw()` is being called after `setData()` (or enable
459
482
  `autoDraw` for streaming mode).
@@ -462,23 +485,27 @@ outside the canvas.
462
485
  visible.
463
486
 
464
487
  ### "Canvas 2D context not available"
488
+
465
489
  - This error means `canvas.getContext('2d')` returned `null`. Common causes:
466
490
  - The canvas element does not exist in the DOM at construction time.
467
491
  - The canvas was already claimed by a WebGL context.
468
492
  - You are running in a server-side environment (Node.js without `node-canvas`).
469
493
 
470
494
  ### "append() requires the chart to be created with { maxPoints }"
495
+
471
496
  - Streaming methods (`append`, `appendBatch`) require ring mode, which is
472
497
  activated by passing `maxPoints` (a positive number) to the constructor.
473
498
  - Use `setData()` for snapshot mode (full replacement).
474
499
 
475
500
  ### Crosshair does not appear
501
+
476
502
  - The crosshair only shows when the mouse is inside the plot area (the inner
477
503
  rectangle after padding is applied).
478
504
  - Check that `onHover` or mouse events are not being consumed by an overlay
479
505
  element above the canvas.
480
506
 
481
507
  ### Performance is slow with many points
508
+
482
509
  - Verify that decimation is working: the line/area renderers auto-switch to
483
510
  per-pixel-column decimation when `count > 2 × plotWidth`.
484
511
  - For scatter charts, reduce `maxDots` (default 2000) or set it to a lower
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { SeriesStore } from '../data/series-store.js';
22
22
  import { Surface } from '../render/surface.js';
23
- import type { ChartOpts, ResolvedOpts, SeriesConfig, SeriesView, PlotRect } from '../types.js';
23
+ import type { ChartOpts, ResolvedOpts, SeriesConfig, SeriesView, PlotRect, DataOwnership } from '../types.js';
24
24
  import type { SeriesHit } from '../render/crosshair.js';
25
25
  /** Abstract base for all chart types. */
26
26
  export declare abstract class ChartBase {
@@ -39,10 +39,17 @@ export declare abstract class ChartBase {
39
39
  */
40
40
  private stackGroupsAll;
41
41
  private stackGroupsByAxis;
42
+ /**
43
+ * Tracks stack-misalignment warnings already emitted, so a misaligned group
44
+ * warns once instead of on every frame (accumulateStackGroup runs per draw).
45
+ */
46
+ private stackWarned;
42
47
  private dirty;
43
48
  private cursorX;
44
49
  private cursorY;
45
50
  private showCrosshair;
51
+ /** Logical index of the keyboard-cursor point in the reference series. */
52
+ private cursorLogical;
46
53
  private autoDraw;
47
54
  private rafScheduled;
48
55
  private suspendCount;
@@ -50,6 +57,13 @@ export declare abstract class ChartBase {
50
57
  private resizeObserver;
51
58
  protected destroyed: boolean;
52
59
  private liveRegion;
60
+ /**
61
+ * Reference to the `prefers-reduced-motion` MQL plus its change listener.
62
+ * Check `reducedMotionMql.matches` to test the preference; the listener
63
+ * triggers a re-draw when the preference changes at runtime.
64
+ */
65
+ private reducedMotionMql;
66
+ private readonly handleReducedMotionChange;
53
67
  private readonly handleResize;
54
68
  private readonly handleMouseMove;
55
69
  private readonly handleMouseLeave;
@@ -62,9 +76,15 @@ export declare abstract class ChartBase {
62
76
  private validateOpts;
63
77
  /**
64
78
  * Detect system accessibility preferences and adjust visual styles.
65
- * - prefers-reduced-motion → disable rAF coalescing (synchronous draws)
79
+ * - prefers-reduced-motion → tracked via `reducedMotionMql.matches`
80
+ * (streaming/rAF continue normally; read the flag to suppress future
81
+ * transitions/animations)
66
82
  * - prefers-contrast: more → increase colour opacity
67
83
  * - forced-colors: active → use system CSS system colours
84
+ *
85
+ * Also registers a `change` listener on the reduced-motion MQL so the
86
+ * preference is re-evaluated at runtime. The listener is removed in
87
+ * {@link destroy}.
68
88
  */
69
89
  private applySystemTheme;
70
90
  /**
@@ -77,10 +97,21 @@ export declare abstract class ChartBase {
77
97
  * Called once per static redraw.
78
98
  */
79
99
  private updateAriaLabel;
100
+ /**
101
+ * Return the first non-empty series index, or -1 when all stores are empty.
102
+ * Used as the reference for keyboard navigation — the cursor advances by
103
+ * logical point indices of this series and the crosshair position is derived
104
+ * from its X value at the current logical index.
105
+ */
106
+ private referenceSeriesIndex;
80
107
  /**
81
108
  * Handle keyboard navigation for the crosshair.
82
109
  * - ArrowLeft / ArrowRight: move crosshair by 1 point (Shift: 10 points)
83
110
  * - Escape: hide crosshair
111
+ *
112
+ * Navigation is anchored to the logical index of the first non-empty series
113
+ * (see {@link referenceSeriesIndex}). The pixel position is derived from the
114
+ * point's X value via the grid domain so the cursor tracks data, not pixels.
84
115
  */
85
116
  private onKeyDown;
86
117
  /** Reusable crosshair-sync helpers used by both mouse and keyboard handlers. */
@@ -91,27 +122,28 @@ export declare abstract class ChartBase {
91
122
  /**
92
123
  * Snapshot mode: replace the series at `index` (O(n) extent).
93
124
  * @param index - Series index (0-based)
94
- * @param x - X values, must be monotonically increasing
95
- * @param y - Y values, must have the same length as x
96
- * @throws {Error} if `index` is out of range, or x/y length mismatch
125
+ * @param x - X values, must be finite and monotonically increasing
126
+ * @param y - Y values, may contain NaN (reserved for gaps v1.6.0); non-finite other than NaN is rejected
127
+ * @param ownership - `'copy'` (default): store copies the arrays; `'borrowed'`: store keeps caller's arrays by reference (must be treated as immutable)
128
+ * @throws {Error} if `index` is out of range, length mismatch, empty, non-finite X, or non-monotonic X
97
129
  */
98
- setData(index: number, x: Float64Array<ArrayBufferLike>, y: Float64Array<ArrayBufferLike>): void;
130
+ setData(index: number, x: Float64Array<ArrayBufferLike>, y: Float64Array<ArrayBufferLike>, ownership?: DataOwnership): void;
99
131
  /**
100
132
  * Ring mode: append one sample to series `index`.
101
133
  * @param index - Series index (0-based)
102
- * @param x - X value (must be monotonically increasing)
103
- * @param y - Y value
134
+ * @param x - X value (must be finite and monotonically increasing)
135
+ * @param y - Y value (NaN is allowed — reserved for gaps v1.6.0)
104
136
  * @throws {Error} if ring mode is not active (maxPoints not set)
105
- * @throws {Error} if `index` is out of range
137
+ * @throws {Error} if `index` is out of range, x is not finite, x is non-monotonic, or y is non-finite (NaN allowed)
106
138
  */
107
139
  append(index: number, x: number, y: number): void;
108
140
  /**
109
141
  * Ring mode: append a batch of samples to series `index`.
110
142
  * @param index - Series index (0-based)
111
- * @param xs - X values (must be monotonically increasing)
112
- * @param ys - Y values, must have the same length as xs
143
+ * @param xs - X values (must be finite and monotonically increasing)
144
+ * @param ys - Y values (NaN is allowed reserved for gaps v1.6.0)
113
145
  * @throws {Error} if ring mode is not active
114
- * @throws {Error} if `index` is out of range or xs/ys length mismatch
146
+ * @throws {Error} if `index` is out of range, length mismatch, non-finite X, non-monotonic X, or non-finite Y (NaN allowed)
115
147
  */
116
148
  appendBatch(index: number, xs: ArrayLike<number>, ys: ArrayLike<number>): void;
117
149
  /**
@@ -125,10 +157,20 @@ export declare abstract class ChartBase {
125
157
  /** Number of series configured. */
126
158
  get seriesCount(): number;
127
159
  /**
128
- * Total points rendered across all series in the last draw.
129
- * Useful for debug/performance monitoring (e.g. to verify decimation is active).
160
+ * Total points currently in the window across all series (before decimation).
161
+ * Useful as a high-level data-volume indicator.
130
162
  */
131
- get renderedPointCount(): number;
163
+ get windowPointCount(): number;
164
+ /**
165
+ * Estimated number of line segments actually drawn in the last render.
166
+ * When a series has more than 2×plotW points, the renderer decimates to
167
+ * ~2·plotW per-pixel-column segments (the `dense` regime). This property
168
+ * mirrors that rule so you can verify decimation is active.
169
+ *
170
+ * Returns an upper bound (the real hardware draw count may be slightly
171
+ * lower due to degenerate columns) — sufficient for debug/monitoring.
172
+ */
173
+ get drawnPointCount(): number;
132
174
  /**
133
175
  * Number of points currently in the window for series `index`.
134
176
  * @param index - Series index (0-based)
@@ -169,6 +211,12 @@ export declare abstract class ChartBase {
169
211
  * @param other - Another chart instance to sync with
170
212
  */
171
213
  sync(other: ChartBase): void;
214
+ /**
215
+ * Remove bidirectional crosshair sync previously established with `other`.
216
+ * No-op if the two charts were not synced.
217
+ * @param other - Another chart instance to unsync from
218
+ */
219
+ unsync(other: ChartBase): void;
172
220
  /**
173
221
  * External callback for hover events. Called on `mousemove` with the
174
222
  * interpolated data for every visible series. Use to build custom DOM
@@ -200,8 +248,23 @@ export declare abstract class ChartBase {
200
248
  */
201
249
  private computeAllStackGroups;
202
250
  /**
203
- * Compute accumulated Y values across a stack group.
204
- * Returns the cumulative Y array (length = n), or null if all stores are empty.
251
+ * Validate that all series within each stack group share the same axis.
252
+ * Runs once in the constructor axis assignment is immutable after that.
253
+ * Data-length alignment is validated separately at draw time in
254
+ * {@link accumulateStackGroup}, since counts change as data arrives.
255
+ * Warns in development — mixed-axis stacking produces incorrect output.
256
+ */
257
+ private validateStackGroups;
258
+ /**
259
+ * Compute accumulated Y values across a stack group, separating positive
260
+ * and negative contributions.
261
+ *
262
+ * Positive values accumulate upward from 0 (`posCum`); negative values
263
+ * accumulate downward from 0 (`negCum`). This prevents positive and negative
264
+ * series from cancelling each other in the same accumulation track.
265
+ *
266
+ * Returns `{ posCum, negCum }` where each is `null` when no value of that
267
+ * sign exists, or the cumulative `Float64Array` (length = n).
205
268
  */
206
269
  private accumulateStackGroup;
207
270
  /**
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * The store is purely about data: it never touches the canvas or scheduling.
11
11
  */
12
- import type { SeriesView } from '../types.js';
12
+ import type { SeriesView, DataOwnership } from '../types.js';
13
13
  /** Owns the series data and computes/caches its extents. */
14
14
  export declare class SeriesStore implements SeriesView {
15
15
  xArr: Float64Array<ArrayBufferLike>;
@@ -32,19 +32,36 @@ export declare class SeriesStore implements SeriesView {
32
32
  /**
33
33
  * Snapshot mode: replace the whole series. Extents computed once in O(n).
34
34
  * Disables any active ring mode.
35
+ *
36
+ * By default (`ownership: 'copy'`) the store copies the caller's arrays into
37
+ * fresh buffers — the caller may mutate the originals freely after the call.
38
+ * Pass `'borrowed'` to keep the caller's arrays by reference (must be treated
39
+ * as immutable for as long as the chart holds them).
40
+ *
35
41
  * @throws {Error} if x and y have different lengths
36
42
  * @throws {Error} if x is empty
43
+ * @throws {Error} if any x value is not finite, or x is not monotonically increasing
44
+ * @throws {Error} if any y value is not finite (NaN in Y is allowed — reserved for gaps v1.6.0)
37
45
  */
38
- setData(x: Float64Array<ArrayBufferLike>, y: Float64Array<ArrayBufferLike>): void;
46
+ setData(x: Float64Array<ArrayBufferLike>, y: Float64Array<ArrayBufferLike>, ownership?: DataOwnership): void;
39
47
  /**
40
48
  * Ring mode: append one sample (x must be monotonically increasing).
41
49
  * @throws {Error} if ring mode is not active (maxPoints not set)
50
+ * @throws {Error} if x is not finite
51
+ * @throws {Error} if x is < last x (not monotonically increasing)
52
+ * @throws {Error} if y is not finite (NaN in Y is allowed — reserved for gaps v1.6.0)
42
53
  */
43
54
  append(x: number, y: number): void;
44
55
  /**
45
56
  * Ring mode: append a batch of parallel samples. O(k).
57
+ *
58
+ * The entire batch is validated before any sample is pushed, so a partial
59
+ * batch never corrupts the ring.
60
+ *
46
61
  * @throws {Error} if ring mode is not active
47
62
  * @throws {Error} if xs and ys have different lengths
63
+ * @throws {Error} if any x is not finite, or xs are not monotonically increasing
64
+ * @throws {Error} if any y is not finite (NaN in Y is allowed — reserved for gaps v1.6.0)
48
65
  */
49
66
  appendBatch(xs: ArrayLike<number>, ys: ArrayLike<number>): void;
50
67
  /** Resize the streaming window, keeping the most recent samples. */
@@ -55,6 +72,12 @@ export declare class SeriesStore implements SeriesView {
55
72
  get lastValue(): number;
56
73
  physOf(logical: number): number;
57
74
  bracketLogical(target: number): number;
75
+ /**
76
+ * Validate snapshot data before accepting it into the store.
77
+ * Checks length, non-empty, finite + monotonic X, and finite Y.
78
+ * @throws {Error} with position-aware message on any violation.
79
+ */
80
+ private validateSnapshot;
58
81
  private requireRing;
59
82
  /** Point the store's logical window at the ring's physical storage. */
60
83
  private bindRing;
@@ -6,8 +6,8 @@
6
6
  * is a sentinel meaning "snapshot mode" (no ring); the constructor activates
7
7
  * ring mode only when the user passes a positive value.
8
8
  *
9
- * `yMin: 0 / yMax: 0` are sentinels meaning "auto" — the grid domain
10
- * expands from data unless the user sets non-zero values.
9
+ * `yMin: undefined / yMax: undefined` — the grid domain expands from data
10
+ * automatically. Set a number to pin the bound; `0` is a legitimate bound.
11
11
  */
12
12
  import type { ResolvedOpts } from './types.js';
13
13
  export declare const CHART_DEFAULTS: ResolvedOpts;