openalgo-charts 2.5.7 → 2.5.8

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/README.md CHANGED
@@ -12,7 +12,7 @@ with no runtime dependencies.
12
12
  [![npm version](https://img.shields.io/npm/v/openalgo-charts.svg?color=cb3837&label=npm)](https://www.npmjs.com/package/openalgo-charts)
13
13
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
14
14
  [![npm downloads](https://img.shields.io/npm/dm/openalgo-charts.svg?color=0ea5e9&label=npm%20downloads)](https://www.npmjs.com/package/openalgo-charts)
15
- [![tests](https://img.shields.io/badge/engine%20tests-9247%20passing-brightgreen.svg)](#develop)
15
+ [![tests](https://img.shields.io/badge/engine%20tests-9374%20passing-brightgreen.svg)](#develop)
16
16
  [![dependencies](https://img.shields.io/badge/runtime%20deps-0-brightgreen.svg)](#principles)
17
17
 
18
18
  [**Documentation**](https://marketcalls.github.io/openalgo-charts/)  ·  [**Live examples**](https://marketcalls.github.io/openalgo-charts/examples)  ·  [**Getting started**](./docs/getting-started.md)  ·  [**Migrating to 2.0**](./docs/migrating-to-2.md)  ·  [**Architecture**](./ARCHITECTURE.md)
@@ -25,7 +25,7 @@ with no runtime dependencies.
25
25
  npm install openalgo-charts
26
26
  ```
27
27
 
28
- Current version: **2.5.7**, an internal release: the chart's code is split by responsibility with no change to the API or to what it paints. 2.5.6 added one undo timeline for the whole chart, study policies, one draw order for studies and drawings, extended hours and adjusted prices as their own series, study inputs that follow other settings, and the space past the last bar laid out in the venue's hours.
28
+ Current version: **2.5.8**, a rendering performance release: a live tick no longer rebuilds the time index or rewrites every study plot, sixteen common built-in studies update without a pass over the whole history, a tick or a study recompute repaints only the panes it changes, a zoomed-out chart draws one stick per pixel column (the level of detail, now on by default), primitives can bound their hit tests, and a render bench holds frame budgets per bar count in CI. 2.5.7 split the chart's code by responsibility with no change to the API or to what it paints.
29
29
  See the [changelog](./CHANGELOG.md) for release notes.
30
30
 
31
31
  ## Quick start
@@ -56,7 +56,7 @@ for live data, or [the widget example](#the-whole-terminal-in-one-call) for char
56
56
 
57
57
  ## Architecture
58
58
 
59
- <a href="docs/architecture-diagram.svg"><img src="docs/architecture-diagram.svg" alt="OpenAlgo Charts 2.5.7 architecture: host responsibilities, the base data-to-rendering pipeline with alerts and shared replay, and eight optional tiers including workspace storage, trading tools and the widget" width="920" /></a>
59
+ <a href="docs/architecture-diagram.svg"><img src="docs/architecture-diagram.svg" alt="OpenAlgo Charts 2.5.8 architecture: host responsibilities, the base data-to-rendering pipeline with alerts and shared replay, and eight optional tiers including workspace storage, trading tools and the widget" width="920" /></a>
60
60
 
61
61
  How data reaches the chart, what your app owns, and which features you can import.
62
62
  [Open the full-size diagram](docs/architecture-diagram.svg).
@@ -107,7 +107,7 @@ You can also load the library from a CDN in a plain HTML page:
107
107
  ```html
108
108
  <div id="chart" style="width:100vw;height:100vh"></div>
109
109
  <script type="module">
110
- import { createChart, generateBars } from 'https://unpkg.com/openalgo-charts@2.5.7/dist/openalgo-charts.mjs';
110
+ import { createChart, generateBars } from 'https://unpkg.com/openalgo-charts@2.5.8/dist/openalgo-charts.mjs';
111
111
  const chart = createChart(document.getElementById('chart'), { timezone: 'Asia/Kolkata' });
112
112
  chart.addSeries('candlestick').setData(generateBars(1700000000, 200, 3600));
113
113
  chart.fitContent();
@@ -165,8 +165,8 @@ Unused optional tiers stay out of the base chart download.
165
165
 
166
166
  | Import | Contents | Brotli |
167
167
  |---|---|---|
168
- | `openalgo-charts` | Engine, 13 chart types, panes and scales, custom indicator registry, primitives, alerts, replay, comparisons, chart linking, state, feeds, bar cache, trading overlays, CSV and SVG export | 126.91 kB |
169
- | `openalgo-charts/indicators` | 105 built-in indicators, calculation helpers and helpers for studies that use external data | 36.44 kB |
168
+ | `openalgo-charts` | Engine, 13 chart types, panes and scales, custom indicator registry, primitives, alerts, replay, comparisons, chart linking, state, feeds, bar cache, trading overlays, CSV and SVG export | 130.37 kB |
169
+ | `openalgo-charts/indicators` | 105 built-in indicators, calculation helpers and helpers for studies that use external data | 40.38 kB |
170
170
  | `openalgo-charts/draw` | 87 drawing tools + a headless drawing controller, clipboard, settings schema, level palette, freehand geometry and SVG icons | 48.42 kB |
171
171
  | `openalgo-charts/transform` | Heikin Ashi, Renko, Range bars, Line Break, Point &amp; Figure, Kagi, and symbol arithmetic (`AAPL/MSFT`) | 4.50 kB |
172
172
  | `openalgo-charts/profile` | Volume Profile, Market Profile (TPO) with compact pixel letters, Footprint, order flow | 14.96 kB |
@@ -175,7 +175,7 @@ Unused optional tiers stay out of the base chart download.
175
175
  | `openalgo-charts/widget` | `createWidget`: toolbar, Data, Objects, Watchlist and News dock, account summary, symbol search, dialogs, mobile controls, shortcuts and optional layout persistence | 92.72 kB |
176
176
  | `openalgo-charts/workspace` | Validated workspace and indicator-template documents, named watchlists, named catalogs with revision checks, asynchronous storage and an IndexedDB adapter; no DOM | 10.47 kB |
177
177
 
178
- Everything together is **357.50 kB Brotli**; a widget terminal with built-in indicators (base + draw + indicators + widget) is 304.49 kB. Figures are measured from the 2.5.7 release build. The trade tier is 16.69 kB on its own; base + trade costs 143.60 kB. Sizes use decimal kB.
178
+ Everything together is **364.91 kB Brotli**; a widget terminal with built-in indicators (base + draw + indicators + widget) is 311.90 kB. Figures are measured from the 2.5.8 release build. The trade tier is 16.69 kB on its own; base + trade costs 147.06 kB. Sizes use decimal kB.
179
179
 
180
180
  ## What's built
181
181
 
@@ -501,17 +501,17 @@ Enforced in CI by [`size-limit`](./.size-limit.json). Nothing is excluded, becau
501
501
 
502
502
  | Bundle | Limit | Actual |
503
503
  |---|---|---|
504
- | Base engine | 126.91 kB | 126.91 kB |
505
- | Base + trade | 143.60 kB | 143.60 kB |
506
- | Indicators tier | 36.44 kB | 36.44 kB |
504
+ | Base engine | 130.38 kB | 130.37 kB |
505
+ | Base + trade | 147.07 kB | 147.06 kB |
506
+ | Indicators tier | 40.39 kB | 40.38 kB |
507
507
  | Draw tier | 48.42 kB | 48.42 kB |
508
508
  | Transform tier | 6 kB | 4.50 kB |
509
509
  | Profile tier | 15 kB | 14.96 kB |
510
510
  | WebGL2 tier | 7 kB | 6.39 kB |
511
511
  | Widget tier | 92.72 kB | 92.72 kB |
512
- | Widget terminal (base + draw + indicators + widget) | 304.49 kB | 304.49 kB |
512
+ | Widget terminal (base + draw + indicators + widget) | 311.90 kB | 311.90 kB |
513
513
  | Workspace tier | 10.47 kB | 10.47 kB |
514
- | **Everything** | 357.50 kB | 357.50 kB |
514
+ | **Everything** | 364.91 kB | 364.91 kB |
515
515
 
516
516
  ## Documentation
517
517
 
@@ -556,18 +556,20 @@ See [Contributing](./CONTRIBUTING.md) for setup, targeted checks, documentation
556
556
  ```bash
557
557
  npm install # install dev toolchain
558
558
  npm run typecheck # strict TypeScript check
559
- npm test # engine unit tests (Vitest): 9247 across 409 files
559
+ npm test # engine unit tests (Vitest): 9374 across 418 files
560
560
  npm run test:demo # reference-host tests: 653 across 62 files
561
561
  npm run test:endurance # node endurance-harness tests: 7 cases
562
562
  npm run build # Rollup -> dist/ (minified ESM per tier + types)
563
563
  npm run size # size-limit (Brotli) against the budget
564
564
  npm run e2e # Playwright Chromium smoke tests
565
+ npm run bench:render # render bench: pan, zoom-out and tick frame budgets per bar count
565
566
  npm run verify # lint + types + unit + endurance harness + build + demo + dts + size + shake
566
567
  ```
567
568
 
568
569
  ## Principles
569
570
 
570
- - **Canvas rendering, two canvases per pane**: a base canvas for the chart, its axes and the price lines (order and position lines included), and an overlay canvas for the crosshair, the drawings and the other top-layer primitives, so a crosshair move repaints only the overlay. No SVG and no DOM element per bar; SVG appears only in `exportSVG` output and in the icon markup of the draw and widget tiers. The optional WebGL tier draws on a shared offscreen surface and copies into the base canvas. Frame times are recorded, not promised: see [browser endurance](./docs/browser-endurance.md).
571
+ - **Canvas rendering, two canvases per pane**: a base canvas for the chart, its axes and the price lines (order and position lines included), and an overlay canvas for the crosshair, the drawings and the other top-layer primitives, so a crosshair move repaints only the overlay. No SVG and no DOM element per bar; SVG appears only in `exportSVG` output and in the icon markup of the draw and widget tiers. The optional WebGL tier draws on a shared offscreen surface and copies into the base canvas. Frame times are budgeted per bar count by the render bench ([performance notes](./docs/performance-notes.md)) and recorded over long sessions by [browser endurance](./docs/browser-endurance.md).
572
+ - **Repaint what changed, on one loop**: a live tick repaints the price pane and the panes of the studies computed from it, and a study recompute repaints only the panes its output lands on; a write that moves the shared time scale, such as an appended bar, repaints every pane. The bottom pane repaints with them while the corner clock is on, and every pane with a price series while the bar countdown is on, since both read the time as their pane paints. Kinetic scroll and the eased wheel zoom are stepped inside the chart's own animation frame, so each frame paints the step it made.
571
573
  - **Gapless time axis by default**: weekends, holidays, and session breaks collapse.
572
574
  - **Registries, not switches**: chart types, indicators, and drawing tools are all descriptors. Adding one is a registration, never a core change.
573
575
  - **Zero runtime dependencies**: nothing is excluded from the size budget.
@@ -575,14 +577,14 @@ npm run verify # lint + types + unit + endurance harness + build + demo + dt
575
577
 
576
578
  ## Status &amp; limitations
577
579
 
578
- Version **2.5.7**. All engine build phases are implemented. Upgrading a 1.9.x host: [Migrating to 2.0](./docs/migrating-to-2.md).
580
+ Version **2.5.8**. All engine build phases are implemented. Upgrading a 1.9.x host: [Migrating to 2.0](./docs/migrating-to-2.md).
579
581
 
580
582
  Known gaps, stated plainly:
581
583
 
582
584
  - **Footprint and order flow need trade-by-trade data classified bid/ask.** OpenAlgo does not store this by default, so it is live-session-only unless you add a tick recorder: `FootprintAggregator` is the live path. See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §6A.
583
585
  - **Only `Footprint` is theme-aware among the profile primitives.** `VolumeProfile`, `MarketProfile` and `HorizontalProfile` never read `rc.theme`; their defaults are dark-tuned, so a light theme needs explicit colours. `HorizontalProfile` also hardcodes its POC / value-area line colours and has no `setOptions`.
584
586
  - The OpenAlgo **WS/trade adapter wire schemas** ship with injectable transports and offline tests, but the exact field names should be verified against your running OpenAlgo build.
585
- - **Large histories slow the live path.** With 150 bars in view, two charts and five studies each, the recorded frame-interval p95 is 17 ms at 2,000 bars per chart and 717 ms at 50,000. The view is the same in both, so the extra time is work over the whole history, such as recomputing every study on each tick. The workload, machine and commands are in [browser endurance](./docs/browser-endurance.md); bound retained history for sustained sessions.
587
+ - **Large histories slow the live path.** With 150 bars in view, two charts and five studies each, the frame-interval p95 recorded on 2.5.5 was 17 ms at 2,000 bars per chart and 717 ms at 50,000. The view is the same in both, so the extra time is work over the whole history, such as recomputing every study on each tick. The workload, machine and commands are in [browser endurance](./docs/browser-endurance.md); that workload has not been rerun since. 2.5.8 cut the tick: a study writes only the plot points that moved and sixteen common built-ins step from the last bar, and on the render bench a ten-study tick at 50,000 bars fell from a p95 of 1166.3 ms to 49.2 ms on `canvas2d` ([performance notes](./docs/performance-notes.md)). It still grows with the loaded history rather than the view, so bound retained history for sustained sessions.
586
588
  - **The WebGL2 backend draws the standard chart types.** Kagi, point-and-figure and custom chart types, drawings, text and every primitive stay on the 2D context; `renderer: 'auto'` moves the series pass of the standard types to the GPU and is not a second renderer for everything. Its frame time against Canvas2D has not been measured.
587
589
 
588
590
  See [`ARCHITECTURE.md`](./ARCHITECTURE.md) §13a for the full deferred list.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Library version string. Matches package.json (including locally prepared releases). */
2
- declare const VERSION = "2.5.7";
2
+ declare const VERSION = "2.5.8";
3
3
  /** Returns the current library version. */
4
4
  declare function version(): string;
5
5
 
@@ -7,9 +7,10 @@ declare function version(): string;
7
7
  * Invalidation model (ARCHITECTURE.md §3.2).
8
8
  *
9
9
  * A single global level is too coarse for multi-pane indicators and trade
10
- * overlays, so the mask carries a **global level + a per-pane map + a queue of
11
- * time-scale operations**. Multiple invalidations within one frame coalesce via
12
- * {@link InvalidateMask.merge}.
10
+ * overlays, so the mask carries a **global level + a per-pane map**. Multiple
11
+ * invalidations within one frame coalesce via {@link InvalidateMask.merge}. It
12
+ * also keeps a queue of time-scale operations that nothing in the chart reads
13
+ * (see {@link TimeScaleOp}).
13
14
  */
14
15
  /** How much of a pane (or the whole chart) must be repainted this frame. */
15
16
  declare const InvalidationLevel: {
@@ -29,9 +30,11 @@ interface PaneInvalidation {
29
30
  autoScale: boolean;
30
31
  }
31
32
  /**
32
- * Discrete time-scale operations an invalidation can carry. The mask queues
33
- * and merges them, but nothing in the chart reads the queue yet: pan, zoom,
34
- * fit and reset change the shared time scale directly (ARCHITECTURE.md §3.2).
33
+ * Discrete time-scale operations a mask can queue, kept for compatibility.
34
+ * The mask queues and merges them, and the chart never reads the queue: pan,
35
+ * zoom, fit and reset change the shared time scale directly, and the kinetic
36
+ * and wheel-zoom glides are stepped at the top of the render loop's frame
37
+ * (ARCHITECTURE.md §3.2).
35
38
  */
36
39
  type TimeScaleOp = {
37
40
  type: 'fitContent';
@@ -56,6 +59,7 @@ declare class InvalidateMask {
56
59
  invalidatePane(paneIndex: number, invalidation: PaneInvalidation): void;
57
60
  paneInvalidation(paneIndex: number): PaneInvalidation | undefined;
58
61
  panes(): ReadonlyMap<number, PaneInvalidation>;
62
+ /** Queue an operation. The chart never reads the queue; see {@link TimeScaleOp}. */
59
63
  addTimeScaleOp(op: TimeScaleOp): void;
60
64
  timeScaleOps(): readonly TimeScaleOp[];
61
65
  isEmpty(): boolean;
@@ -739,10 +743,26 @@ declare class DataLayer {
739
743
  createSeries(): SeriesId;
740
744
  removeSeries(id: SeriesId): void;
741
745
  /**
742
- * Bulk-load (full replace) one series' data, then re-merge the time axis.
743
- * Input is sorted and de-duplicated by time by a private `sortedUniqueByTime`.
746
+ * Bulk-load (full replace) one series' data. Input is sorted and
747
+ * de-duplicated by time by a private `sortedUniqueByTime`.
748
+ *
749
+ * The shared index is rebuilt only when the set of times changes. A rebuild
750
+ * re-merges every series on the chart, and most whole writes do not change
751
+ * the set: a study's plots carry the source's own times, which the source
752
+ * put in the index before any study recomputed, and a colour overlay on the
753
+ * source keeps its times. Ten studies on a long history used to pay for the
754
+ * index two dozen times per tick while it came out unchanged.
744
755
  */
745
756
  setSeriesData(id: SeriesId, bars: readonly Bar[]): void;
757
+ /**
758
+ * Bring the index up to date without a rebuild for a series whose bars went
759
+ * from `previous` to `next`, when the set of times cannot have changed but
760
+ * for times past the right edge: every time of `previous` is still in
761
+ * `next`, and every time of `next` is in the index already or later than all
762
+ * of it. False when a time went away (another series may or may not still
763
+ * hold it) or a new one lands inside the axis, and the caller rebuilds.
764
+ */
765
+ private _extendIndex;
746
766
  /**
747
767
  * Upsert bars into a series by time (used for history paging / backfill /
748
768
  * out-of-order corrections — ARCHITECTURE.md §4.2). Existing times are
@@ -789,8 +809,8 @@ declare class DataLayer {
789
809
  timeToIndexFloat(time: number): number;
790
810
  /**
791
811
  * The future plan for the current last bars and calendar. A rebuild that
792
- * leaves the recent bars as they were (an indicator re-merging its plots on
793
- * every tick) keeps the plan and the times it already generated.
812
+ * leaves the recent bars as they were (a page of older history, a gap filled
813
+ * well to the left) keeps the plan and the times it already generated.
794
814
  */
795
815
  private _plan;
796
816
  /**
@@ -1049,7 +1069,11 @@ interface PrimitiveHit {
1049
1069
  /** Distinguishes hover regions which share one external click ID. Omission uses externalId. */
1050
1070
  hoverKey?: string;
1051
1071
  zOrder: ZOrder;
1052
- /** Pixel distance from the cursor (smaller wins ties before z-order). */
1072
+ /**
1073
+ * Pixel distance from the cursor (smaller wins ties before z-order). Never
1074
+ * negative: zero is a hit on the shape itself, and the pane stops asking
1075
+ * once the front band has one.
1076
+ */
1053
1077
  distance: number;
1054
1078
  cursor?: string;
1055
1079
  /** Coordinate scale for a bound primitive's drag prices. Unbound hits omit it. */
@@ -1114,6 +1138,30 @@ interface IPrimitive {
1114
1138
  afterAutoscale?(): void;
1115
1139
  /** Optional: topmost hit under (x,y) in media px (relative to the pane plot). */
1116
1140
  hitTest?(x: number, y: number, rc: PrimitiveRenderContext): PrimitiveHit | null;
1141
+ /**
1142
+ * Optional: the box, in the coordinates `hitTest` receives, outside which it
1143
+ * answers null, or null when nothing of this primitive can be hit at all.
1144
+ * Edges are inclusive, and an unbounded side may be infinite.
1145
+ *
1146
+ * A pointer move then asks `hitTest` only of primitives whose box it is in,
1147
+ * so a pane carrying hundreds of them pays for the few near the pointer.
1148
+ * The pane asks for the box once and reuses it while nothing it can follow
1149
+ * changes: the time scale and the bars' times, the pane's price scales, its
1150
+ * size and axes, the pixel ratio, the hover and drag state, and the
1151
+ * primitive's own state, which it announces through `requestUpdate`. A box
1152
+ * that follows anything else, such as bar prices, has to request an update
1153
+ * when that changes, or leave this out. Work the box out from the same
1154
+ * geometry `hitTest` answers from, `rc` or what the last `draw` recorded: a
1155
+ * box asked for between a change and the frame that paints it is asked for
1156
+ * again once that frame is painted. Absent, every pointer move asks
1157
+ * `hitTest`.
1158
+ */
1159
+ hitBounds?(rc: PrimitiveRenderContext): {
1160
+ left: number;
1161
+ top: number;
1162
+ right: number;
1163
+ bottom: number;
1164
+ } | null;
1117
1165
  attached?(host: PrimitiveHost): void;
1118
1166
  detached?(): void;
1119
1167
  }
@@ -2167,6 +2215,11 @@ interface IndicatorHost {
2167
2215
  legendRowsOn(paneIndex: number): number;
2168
2216
  /** The instrument's own series, for a descriptor anchoring marks to price. */
2169
2217
  primarySeries?(): SeriesApi | null;
2218
+ /**
2219
+ * A series for one plot. The study writes it with `setData` for a whole pass
2220
+ * and, from 2.5.8, with `update` for the points a live tick moved (older
2221
+ * points included), so a host's own series must implement both as documented.
2222
+ */
2170
2223
  addIndicatorSeries(type: string, paneIndex: number, style: Record<string, unknown> | undefined, priceScaleId: string | undefined,
2171
2224
  /** Axis/crosshair formatting for the scale this plot maps to. */
2172
2225
  priceFormat?: PriceFormat): SeriesApi;
@@ -3932,6 +3985,10 @@ interface IRenderBackend {
3932
3985
  * Paint one series. Same contract as `RendererEntry.draw` minus the context:
3933
3986
  * `priceToY` and `items[].x` are media px, and the renderer snaps to device
3934
3987
  * pixels with `dpr` itself.
3988
+ *
3989
+ * `items` and the objects in it belong to the pane, which rewrites them in
3990
+ * place the next time it draws this series, so a backend that keeps any of
3991
+ * it past that (a batch held across frames, say) copies what it keeps.
3935
3992
  */
3936
3993
  drawSeries(entry: RendererEntry, items: readonly DrawItem[], priceToY: (price: number) => number, barSpacing: number, dpr: number, style: SeriesStyle, rc: SeriesRenderContext): void;
3937
3994
  /** Finish the frame: flush whatever the backend batched. */
@@ -4058,9 +4115,12 @@ interface PaneRenderContext {
4058
4115
  * neither paint nor hit-test until it opens again.
4059
4116
  */
4060
4117
  collapsed?: boolean;
4061
- /** Enable OHLC-preserving conflation when bars fall below ~0.5px (§4.4). */
4118
+ /**
4119
+ * Draw one OHLC-preserving stick per device-pixel column once bars are
4120
+ * narrower than a column (§4.4); false draws every bar at every zoom.
4121
+ */
4062
4122
  conflate: boolean;
4063
- /** Conflation aggressiveness (1 = perf only; higher = more smoothing). */
4123
+ /** Columns this many sticks wide (1 = one stick, the default; higher = coarser). */
4064
4124
  conflationFactor: number;
4065
4125
  /** Active palette — drives chrome, series defaults, and trade colors. */
4066
4126
  theme: ChartTheme;
@@ -4154,6 +4214,12 @@ declare class Pane {
4154
4214
  private _destroyed;
4155
4215
  private _width;
4156
4216
  private _height;
4217
+ /** Each series' draw items, reused from frame to frame (src/render/draw-items.ts). */
4218
+ private readonly _drawItems;
4219
+ /** Hit boxes of the primitives that declare one, kept while nothing they follow changes (src/render/hit-boxes.ts). */
4220
+ private readonly _hitBoxes;
4221
+ /** Bumped by every change on this pane that can move a primitive's context: series, scales, axes, primitives. */
4222
+ private _hitEpoch;
4157
4223
  /**
4158
4224
  * `backend` defaults to the 2D one so a pane built on its own (tests, a host
4159
4225
  * composing panes by hand) paints the way it always has; the chart passes
@@ -4335,8 +4401,26 @@ declare class Pane {
4335
4401
  * band order, as `bestHit` ranks them. A hit painted by a primitive placed
4336
4402
  * in the series band names it (`paintedBy`), so the chart can rank it
4337
4403
  * against a series painted over it too.
4404
+ *
4405
+ * Two things keep a pointer move from asking every primitive. One that
4406
+ * declares a hit box is skipped when the point is outside it; the boxes are
4407
+ * kept while nothing they depend on changes (`_checkHitBoxes`). And the
4408
+ * walk stops at an exact hit in the front band, which nothing after it can
4409
+ * outrank: a later primitive would need a smaller distance than zero or a
4410
+ * band above the front one, and the order the walk visits primitives in
4411
+ * only ever breaks exact ties, which the first hit wins.
4338
4412
  */
4339
4413
  hitTestPrimitives(x: number, y: number, ctx: PaneRenderContext, except?: IPrimitive | null): PrimitiveHit | null;
4414
+ /** After an on-screen paint: what the pane shows now is what the key describes (src/render/hit-boxes.ts). */
4415
+ private _paintedHitBoxes;
4416
+ /**
4417
+ * Read the key the kept hit boxes are checked against: what a primitive's
4418
+ * render context can map with (the time scale and the bars' times, every
4419
+ * price scale here, the pane's size and axes, the pixel ratio, the readout
4420
+ * scale, hover and drag, the theme, the session calendar) and this pane's
4421
+ * own count of changes.
4422
+ */
4423
+ private _checkHitBoxes;
4340
4424
  /**
4341
4425
  * Draw the rule over this pane's top edge in `color` at device pixel ratio
4342
4426
  * `dpr`, or none with a null colour.
@@ -5132,9 +5216,25 @@ interface ChartOptions {
5132
5216
  * tool is being placed a double-click finishes the shape whatever this says.
5133
5217
  */
5134
5218
  doubleClick?: DoubleClickAction;
5135
- /** Enable OHLC-preserving conflation when zoomed out (§4.4). Default false. */
5219
+ /**
5220
+ * Level of detail for a zoomed-out chart (§4.4). Default true: once bars are
5221
+ * narrower than the stick a candle is drawn with (about one CSS px; exactly
5222
+ * one at a whole pixel ratio), the bars sharing a device-pixel column are
5223
+ * drawn as one OHLC-preserving stick (open of the first, close of the last,
5224
+ * the column's high and low), and line and histogram series keep each
5225
+ * column's first, lowest, highest and last values. A frame then costs the
5226
+ * plot's width, not the number of bars in view. Above that spacing nothing
5227
+ * changes. Data, autoscale, indicators and the crosshair still see every
5228
+ * bar. `false` draws every bar at every zoom.
5229
+ *
5230
+ * The default time scale never zooms out past one bar per CSS px
5231
+ * (`timeScale.minBarSpacing: 1`), so it matters once a host lowers that floor.
5232
+ */
5136
5233
  conflate?: boolean;
5137
- /** Conflation aggressiveness (default 1). */
5234
+ /**
5235
+ * Column width in sticks (default 1). Higher merges into wider columns, and
5236
+ * from spacings that many times wider, for a coarser, cheaper picture.
5237
+ */
5138
5238
  conflationFactor?: number;
5139
5239
  /**
5140
5240
  * Which backend paints the series. `'canvas2d'` (the default) is the 2D
@@ -5918,6 +6018,8 @@ declare class Chart {
5918
6018
  private readonly _priceAxisWidth;
5919
6019
  private readonly _timeAxisHeight;
5920
6020
  private _pending;
6021
+ /** While a frame steps the glides and the studies: what they invalidate is painted by that frame. */
6022
+ private _framing;
5921
6023
  private _scaleMutationDepth;
5922
6024
  /** Size and device-pixel-ratio observation; see chart-pixels.ts. */
5923
6025
  private readonly _pixels;
@@ -6915,6 +7017,7 @@ declare class Chart {
6915
7017
  */
6916
7018
  private _paintNow;
6917
7019
  private _onFrame;
7020
+ private _paintFrame;
6918
7021
  private readonly _onPointerEnter;
6919
7022
  private readonly _onContextMenu;
6920
7023
  private readonly _onPointerDown;
@@ -9250,12 +9353,23 @@ declare function supertrendSeries(bars: readonly Bar[], period?: number, multipl
9250
9353
  };
9251
9354
 
9252
9355
  /**
9253
- * Optional OHLC-preserving conflation / downsampling (ARCHITECTURE.md §4.4).
9254
- * When zoomed far out, many bars map to sub-pixel widths; drawing them all is
9255
- * wasted work. Conflation merges groups of bars into one, preserving candle
9256
- * shape — open = first, close = last, high = max, low = min, volume = sum
9257
- * (never a lossy average). Off by default; enabling it changes nothing until
9258
- * bars fall below the pixel threshold.
9356
+ * OHLC-preserving conflation (ARCHITECTURE.md §4.4): what a zoomed-out series
9357
+ * pass draws instead of one mark per bar.
9358
+ *
9359
+ * Once bars are narrower than the stick a candle is drawn with, several of them
9360
+ * land in the same device-pixel column, and drawing each one paints the same
9361
+ * pixels over and over: 200,000 bars on a 1,000 px plot put two hundred marks
9362
+ * in every column where one would do. The level of detail at the end of this
9363
+ * file merges what shares a column into one OHLC-preserving stick (open =
9364
+ * first, close = last, high = max, low = min, volume = sum; never a lossy
9365
+ * average), so a frame costs the plot's width rather than the history's
9366
+ * length. It is on by default (`conflate`) and inert above its threshold: a
9367
+ * chart zoomed in far enough that every bar has a column of its own paints
9368
+ * exactly what it always did.
9369
+ *
9370
+ * The group helpers (`conflationGroupSize`, `conflateBars`, `conflateItems`,
9371
+ * `mergeBars`) merge fixed-size groups, for a host that downsamples bars
9372
+ * itself; the pane merges by column.
9259
9373
  */
9260
9374
 
9261
9375
  /**