From f5bcea5e07eb23912202b593133ff54608429b8b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 10 Nov 2026 19:48:57 +0000 Subject: [PATCH] fix: adopt already-valid candle arrays in setData A finite, strictly increasing, unique, OHLC-consistent series is stored as-is. The chart still copies at its boundary so callers and the default config stay untouched. Dirty rows still take the normalize path. Co-authored-by: Joe Blau --- packages/chart/src/core/chart.ts | 6 +++--- packages/chart/src/core/data.ts | 11 ++++++++--- packages/chart/test/data.test.ts | 6 +++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/chart/src/core/chart.ts b/packages/chart/src/core/chart.ts index 785b63e..4f1f9c1 100644 --- a/packages/chart/src/core/chart.ts +++ b/packages/chart/src/core/chart.ts @@ -390,7 +390,7 @@ export class Chart { } else { throw new Error('chart-ts: inject a container canvas or a ChartDocument'); } - this.store.setData(this.config.data); + this.store.setData(this.config.data.slice()); this.timeSlots.sync(this.store.raw(), this.config.timeScale, 'data'); this.syncInlineBook(); this.syncHeatmap(); @@ -452,7 +452,7 @@ export class Chart { setData(candles: readonly Candle[]): void { this.rangeAnimation?.clear(); this.candleAnimation?.clear(); - this.store.setData(candles); + this.store.setData(candles.slice()); this.timeSlots.sync(this.store.raw(), this.config.timeScale, 'data'); this.events.dataLoaded('set', this.store.length); this.indicatorCache.clear(); @@ -577,7 +577,7 @@ export class Chart { if (partial.data !== undefined) { this.rangeAnimation?.clear(); this.candleAnimation?.clear(); - this.store.setData(this.config.data); + this.store.setData(this.config.data.slice()); this.events.dataLoaded('set', this.store.length); this.indicatorCache.clear(); } diff --git a/packages/chart/src/core/data.ts b/packages/chart/src/core/data.ts index 61acd35..a898191 100644 --- a/packages/chart/src/core/data.ts +++ b/packages/chart/src/core/data.ts @@ -150,11 +150,16 @@ export class DataStore { /** * Replaces the whole dataset. Non-finite rows are dropped, duplicate times * keep the last row, `high`/`low` expand to cover `open` and `close`, and a - * negative or non-finite volume becomes 0. Already-clean input keeps the - * same candle objects. + * negative or non-finite volume becomes 0. An array that is already finite, + * strictly increasing, unique and OHLC-consistent is stored as-is: no copy. + * Callers must not mutate that array. */ setData(candles: readonly Candle[]): void { - this.candles = firstDirty(candles) === candles.length ? candles.slice() : sanitizeCandles(candles); + if (firstDirty(candles) === candles.length) { + this.candles = candles as Candle[]; + return; + } + this.candles = sanitizeCandles(candles); } /** diff --git a/packages/chart/test/data.test.ts b/packages/chart/test/data.test.ts index ed6ea2f..fccfffd 100644 --- a/packages/chart/test/data.test.ts +++ b/packages/chart/test/data.test.ts @@ -16,13 +16,17 @@ describe('DataStore', () => { assert.equal(s.last(), undefined); assert.deepEqual(s.all(), []); }); - it('setData sorts defensively and copies', () => { + it('setData sorts defensively and copies only when a row is dirty', () => { const s = new DataStore(); const input = [candle(3), candle(1), candle(2)]; s.setData(input); assert.deepEqual(s.all().map((c) => c.time), [1, 2, 3]); input.push(candle(9)); assert.equal(s.length, 3); + const clean = [candle(1), candle(2), candle(3)]; + s.setData(clean); + assert.equal(s.raw(), clean); + assert.equal(s.at(0), clean[0]); }); it('append inserts in order and out of order', () => { const s = new DataStore();