hyperprop-charting-library 0.1.138 → 0.1.140

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/docs/API.md CHANGED
@@ -568,6 +568,9 @@ an up measurement, red for down.
568
568
  - `cancelDrawing(): boolean` — abort an in-progress multi-click drawing (between the first click and the final point, e.g. a half-drawn trendline/ray/fib). Returns `true` if a draft was cancelled. Wire it to Escape.
569
569
  - Hold **Shift** while drawing or dragging an endpoint of a `trendline`/`ray`/`arrow` to constrain it to the nearest 0/45/90° angle (e.g. a perfectly horizontal line).
570
570
  - `setMagnetMode(mode): void` / `getMagnetMode()` — magnet/snap for all drawing tools. `"none"` off, `"weak"` snaps to a candle's OHLC only when the cursor is near a value, `"strong"` always snaps to the nearest OHLC of the bar under the cursor. Holding Cmd/Ctrl while drawing/dragging forces strong snapping regardless of the mode.
571
+ - `setDatafeed(datafeed: ChartDatafeed | null): Promise<void>` — attach a pull-based data source with lazy history; see "Datafeed" below
572
+ - `onLongPress(handler: ((event: ChartLongPressEvent) => void) | null): void` — touch press-and-hold inspection
573
+ - `setCompareSeries(series: CompareSeriesOptions[]): void` / `addCompareSeries(series)` / `removeCompareSeries(id)` / `getCompareSeries()` — overlay other instruments on the main pane; see "Compare overlays" below
571
574
  - `setPriceScale(options: { mode?: "linear" | "log" | "percent"; inverted?: boolean }): void` / `getPriceScale()` — price-scale modes for the main pane (TradingView-style). `"log"` maps prices through log10 (wide ranges get 1/2/5-per-decade axis marks). `"percent"` keeps the linear mapping but relabels the axis (ticks, last-price/PDC/H/L/bid/ask tags and the crosshair) as % change from the first visible bar's close, updating as you pan. `inverted: true` flips the axis top-to-bottom. All viewport state stays in plain price space, so switching modes never moves your zoom, and drawings/orders/indicators follow automatically. Indicator panes keep their own scales. Included in `saveState()` as `priceScale`.
572
575
  - `setActiveDrawingTool(tool: DrawingToolType | null): void` (`DrawingToolType` = `"horizontal-line" | "vertical-line" | "trendline" | "ray" | "fib-retracement" | "fib-extension" | "long-position" | "short-position" | "price-range" | "rectangle" | "text" | "note" | "parallel-channel" | "ellipse" | "arrow" | "brush" | "callout" | "measure"`)
573
576
  - `getActiveDrawingTool(): DrawingToolType | null`
@@ -610,6 +613,109 @@ link.click();
610
613
 
611
614
  ---
612
615
 
616
+ ## Datafeed (optional, pull-based)
617
+
618
+ By default the host pushes bars in with `setData`/`upsertBar`. Attach a
619
+ datafeed instead and the chart pulls what it needs — including older history
620
+ as the user scrolls left, which every host otherwise hand-rolls as infinite
621
+ scroll. The shape follows TradingView's datafeed closely enough to port an
622
+ existing adapter.
623
+
624
+ ```ts
625
+ await chart.setDatafeed({
626
+ onReady: async () => { /* optional, awaited once before the first getBars */ },
627
+ getBars: async ({ fromMs, toMs, countBack, firstRequest }) => {
628
+ // countBack is authoritative; fromMs is a hint derived from bar spacing.
629
+ return fetchBars({ symbol, to: toMs, limit: countBack });
630
+ },
631
+ subscribeBars: (onBar) => {
632
+ const socket = openStream(symbol, onBar);
633
+ return () => socket.close(); // teardown, called on replace/destroy
634
+ },
635
+ onError: (error, request) => report(error, request),
636
+ });
637
+ ```
638
+
639
+ - The chart loads `initialVisibleBars × 3` bars, then calls `subscribeBars` and
640
+ upserts every streamed bar.
641
+ - **Lazy history**: when the viewport comes within
642
+ `datafeedOptions.prefetchThresholdBars` (default 150) of the oldest loaded
643
+ bar, the chart requests the next `chunkBars` (default 1500) and merges them,
644
+ re-anchoring on time so the view doesn't jump. A `cooldownMs` (default 1200)
645
+ keeps a fast scroll from stampeding the backend.
646
+ - **Return an empty array to mean "no more history"** — the chart stops asking
647
+ until a new datafeed is attached.
648
+ - `setData`/`upsertBar` keep working while a datafeed is attached, so hosts can
649
+ migrate one surface at a time. `setDatafeed(null)` detaches and unsubscribes.
650
+
651
+ ## Touch
652
+
653
+ Touch input gets its own ergonomics, configured with `touch` in `ChartOptions`:
654
+
655
+ - **Bigger targets.** Every drawing, handle and pane-divider tolerance is
656
+ multiplied by `hitToleranceScale` (default 2.2) while the last input was
657
+ touch or pen, so a line you can grab within 7px with a mouse is grabbable
658
+ within ~15px with a finger. Mouse precision is untouched, and the mode
659
+ flips back the moment a mouse moves.
660
+ - **Press-and-hold to inspect.** Holding still on the plot for
661
+ `longPressMs` (default 400) puts the crosshair on that bar and draws an OHLC
662
+ tooltip (time, O/H/L/C, change with %, volume) offset clear of the fingertip.
663
+ The finger then *scrubs* — it moves the reading instead of panning the chart
664
+ — until it lifts, which clears both. Turn the built-in tooltip off with
665
+ `longPressTooltip: false`; subscribe with `onLongPress(handler)` to drive
666
+ your own UI (the event carries the bar, price, index and client coords, and
667
+ `scrubbing` distinguishes the initial press from the drag that follows).
668
+
669
+ ## Compare overlays (multi-series)
670
+
671
+ Overlay other instruments on the main pane. The host owns the data (it already
672
+ has a datafeed); the chart aligns each series to the main bars by timestamp and
673
+ draws it.
674
+
675
+ ```ts
676
+ chart.setCompareSeries([
677
+ { id: "NQ", label: "NQ1!", data: nqBars, color: "#22d3ee" },
678
+ { id: "BTC", label: "BTCUSD", data: btcBars, color: "#a855f7", style: "area" },
679
+ ]);
680
+ ```
681
+
682
+ - **Alignment** is by timestamp, computed once per data change (a merge walk,
683
+ not a search per bar), so different session hours and holidays are fine — the
684
+ last known value carries forward across gaps, and bars before a compared
685
+ instrument starts are simply not drawn.
686
+ - **`scale`** defaults to `"percent"`: both instruments are normalised to their
687
+ first visible bar, so a $180 index and a $60,000 coin can be compared by
688
+ shape. It re-anchors as you pan, exactly like TradingView. Use `"price"` to
689
+ plot raw values on the main scale (only sensible for related instruments).
690
+ - **Autoscale** includes compare series unless `includeInAutoScale: false`.
691
+ - Each series gets a colored tag in the price gutter showing its own % move
692
+ (or value in price mode); tags stack instead of overlapping, and drop the
693
+ label when the gutter is too narrow.
694
+ - Compare data is **not** in `saveState()` — it can be megabytes and the host
695
+ re-fetches it anyway. Persist the symbol list yourself and re-supply data on
696
+ load.
697
+
698
+ ## Deep zoom-out (viewport downsampling)
699
+
700
+ Once bars are narrower than `downsampling.thresholdPx` (default 1.5px), several
701
+ bars share a pixel column, and drawing each one separately is wasted work. The
702
+ chart aggregates each column into one min/max bucket, so path operations scale
703
+ with the chart's width rather than the number of bars. Extremes are preserved
704
+ exactly — a one-bar spike still reaches full height — because the bucket keeps
705
+ the true high and low rather than sampling. Candle and bar styles converge to a
706
+ single colored high-low column (the body is sub-pixel at that density anyway);
707
+ line/area/baseline keep a min/max envelope so the shape of the move survives.
708
+
709
+ Measured on the playground benchmark (50,000 bars, 120Hz display):
710
+
711
+ | Scenario | Per-bar (before) | Downsampled |
712
+ | --- | --- | --- |
713
+ | zoom 100 ↔ 20k bars | 21.3ms avg, 49.8ms p95, 47fps, 53 slow frames | 8.3ms avg, 10.3ms p95, 120fps, 0 slow frames |
714
+ | deep zoom-out, 50k bars | 52.7ms avg, 58.8ms p95, 19fps, 240 slow frames | 8.3ms avg, 10.2ms p95, 120fps, 0 slow frames |
715
+
716
+ Turn it off with `downsampling: { enabled: false }`, or tune when it kicks in
717
+ with `thresholdPx`.
718
+
613
719
  ## Context menus, selection toolbars and keyboard
614
720
 
615
721
  Three pieces of chrome that every host previously rebuilt by hand. The chart
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.138",
3
+ "version": "0.1.140",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",