@perspective-dev/viewer-charts 4.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/LICENSE.md +193 -0
- package/dist/cdn/perspective-viewer-charts.js +3 -0
- package/dist/cdn/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/axis/axis-primitives.d.ts +24 -0
- package/dist/esm/axis/bar-axis.d.ts +51 -0
- package/dist/esm/axis/canvas.d.ts +24 -0
- package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
- package/dist/esm/axis/categorical-axis.d.ts +27 -0
- package/dist/esm/axis/facet-chrome.d.ts +13 -0
- package/dist/esm/axis/label-geometry.d.ts +41 -0
- package/dist/esm/axis/legend.d.ts +44 -0
- package/dist/esm/axis/numeric-axis.d.ts +20 -0
- package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
- package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
- package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
- package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
- package/dist/esm/charts/canvas-types.d.ts +15 -0
- package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
- package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
- package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
- package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
- package/dist/esm/charts/chart-base.d.ts +402 -0
- package/dist/esm/charts/chart.d.ts +338 -0
- package/dist/esm/charts/common/band-layout.d.ts +32 -0
- package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
- package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
- package/dist/esm/charts/common/leaf-color.d.ts +33 -0
- package/dist/esm/charts/common/node-store.d.ts +81 -0
- package/dist/esm/charts/common/tree-chart.d.ts +48 -0
- package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
- package/dist/esm/charts/common/tree-data.d.ts +54 -0
- package/dist/esm/charts/common/visible-extent.d.ts +51 -0
- package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
- package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
- package/dist/esm/charts/map/map.d.ts +67 -0
- package/dist/esm/charts/registry.d.ts +14 -0
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
- package/dist/esm/charts/series/series-build.d.ts +228 -0
- package/dist/esm/charts/series/series-interact.d.ts +35 -0
- package/dist/esm/charts/series/series-render.d.ts +41 -0
- package/dist/esm/charts/series/series-type.d.ts +49 -0
- package/dist/esm/charts/series/series.d.ts +317 -0
- package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
- package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
- package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
- package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
- package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
- package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
- package/dist/esm/charts/treemap/treemap.d.ts +74 -0
- package/dist/esm/config.d.ts +27 -0
- package/dist/esm/data/lazy-row.d.ts +32 -0
- package/dist/esm/data/split-groups.d.ts +20 -0
- package/dist/esm/data/view-reader.d.ts +35 -0
- package/dist/esm/event-detail.d.ts +28 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/interaction/hit-test.d.ts +30 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
- package/dist/esm/interaction/host-sink-message.d.ts +46 -0
- package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
- package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
- package/dist/esm/interaction/spatial-grid.d.ts +15 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
- package/dist/esm/interaction/zoom-controller.d.ts +106 -0
- package/dist/esm/interaction/zoom-router.d.ts +48 -0
- package/dist/esm/layout/facet-grid.d.ts +126 -0
- package/dist/esm/layout/plot-layout.d.ts +104 -0
- package/dist/esm/layout/ticks.d.ts +17 -0
- package/dist/esm/map/mercator.d.ts +102 -0
- package/dist/esm/map/tile-cache.d.ts +38 -0
- package/dist/esm/map/tile-layer.d.ts +66 -0
- package/dist/esm/map/tile-loader.d.ts +52 -0
- package/dist/esm/map/tile-source.d.ts +66 -0
- package/dist/esm/perspective-viewer-charts.js +3 -0
- package/dist/esm/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/plugin/charts.d.ts +40 -0
- package/dist/esm/plugin/plugin.d.ts +95 -0
- package/dist/esm/render/scheduler.d.ts +41 -0
- package/dist/esm/theme/gradient.d.ts +48 -0
- package/dist/esm/theme/palette.d.ts +13 -0
- package/dist/esm/theme/theme-snapshot.d.ts +7 -0
- package/dist/esm/theme/theme.d.ts +53 -0
- package/dist/esm/transport/protocol.d.ts +430 -0
- package/dist/esm/transport/renderer-transport.d.ts +201 -0
- package/dist/esm/utils/css.d.ts +1 -0
- package/dist/esm/utils/font-snapshot.d.ts +50 -0
- package/dist/esm/webgl/buffer-pool.d.ts +62 -0
- package/dist/esm/webgl/context-manager.d.ts +184 -0
- package/dist/esm/webgl/gradient-texture.d.ts +17 -0
- package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
- package/dist/esm/webgl/plot-frame.d.ts +39 -0
- package/dist/esm/webgl/program-cache.d.ts +13 -0
- package/dist/esm/webgl/shader-manifest.d.ts +53 -0
- package/dist/esm/webgl/shader-registry.d.ts +22 -0
- package/dist/esm/worker/boot.d.ts +0 -0
- package/dist/esm/worker/dispatch.d.ts +9 -0
- package/dist/esm/worker/font-loader.d.ts +2 -0
- package/dist/esm/worker/renderer.worker.d.ts +115 -0
- package/dist/esm/worker/session-host.d.ts +26 -0
- package/package.json +47 -0
- package/src/css/perspective-viewer-charts.css +95 -0
- package/src/ts/axis/axis-primitives.ts +125 -0
- package/src/ts/axis/bar-axis.ts +345 -0
- package/src/ts/axis/canvas.ts +64 -0
- package/src/ts/axis/categorical-axis-core.ts +125 -0
- package/src/ts/axis/categorical-axis.ts +716 -0
- package/src/ts/axis/facet-chrome.ts +42 -0
- package/src/ts/axis/label-geometry.ts +188 -0
- package/src/ts/axis/legend.ts +218 -0
- package/src/ts/axis/numeric-axis.ts +353 -0
- package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
- package/src/ts/charts/candlestick/candlestick.ts +367 -0
- package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
- package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
- package/src/ts/charts/canvas-types.ts +30 -0
- package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
- package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
- package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
- package/src/ts/charts/cartesian/cartesian.ts +469 -0
- package/src/ts/charts/cartesian/glyph.ts +81 -0
- package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
- package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
- package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
- package/src/ts/charts/cartesian/label-interner.ts +56 -0
- package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
- package/src/ts/charts/chart-base.ts +840 -0
- package/src/ts/charts/chart.ts +427 -0
- package/src/ts/charts/common/band-layout.ts +63 -0
- package/src/ts/charts/common/categorical-y-chart.ts +81 -0
- package/src/ts/charts/common/category-axis-resolver.ts +314 -0
- package/src/ts/charts/common/chrome-cache.ts +79 -0
- package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
- package/src/ts/charts/common/leaf-color.ts +92 -0
- package/src/ts/charts/common/node-store.ts +235 -0
- package/src/ts/charts/common/tree-chart.ts +76 -0
- package/src/ts/charts/common/tree-chrome.ts +123 -0
- package/src/ts/charts/common/tree-data.ts +623 -0
- package/src/ts/charts/common/visible-extent.ts +112 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
- package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
- package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
- package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
- package/src/ts/charts/heatmap/heatmap.ts +368 -0
- package/src/ts/charts/map/map.ts +201 -0
- package/src/ts/charts/registry.ts +65 -0
- package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
- package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
- package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
- package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
- package/src/ts/charts/series/series-build.ts +848 -0
- package/src/ts/charts/series/series-interact.ts +604 -0
- package/src/ts/charts/series/series-render.ts +1109 -0
- package/src/ts/charts/series/series-type.ts +99 -0
- package/src/ts/charts/series/series.ts +794 -0
- package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
- package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
- package/src/ts/charts/sunburst/sunburst.ts +248 -0
- package/src/ts/charts/treemap/treemap-interact.ts +445 -0
- package/src/ts/charts/treemap/treemap-layout.ts +328 -0
- package/src/ts/charts/treemap/treemap-render.ts +886 -0
- package/src/ts/charts/treemap/treemap.ts +247 -0
- package/src/ts/config.ts +41 -0
- package/src/ts/data/lazy-row.ts +140 -0
- package/src/ts/data/split-groups.ts +97 -0
- package/src/ts/data/view-reader.ts +107 -0
- package/src/ts/event-detail.ts +44 -0
- package/src/ts/index.ts +53 -0
- package/src/ts/interaction/hit-test.ts +106 -0
- package/src/ts/interaction/host-sink-dom.ts +85 -0
- package/src/ts/interaction/host-sink-message.ts +75 -0
- package/src/ts/interaction/lazy-tooltip.ts +102 -0
- package/src/ts/interaction/raw-event-forwarder.ts +175 -0
- package/src/ts/interaction/spatial-grid.ts +100 -0
- package/src/ts/interaction/tooltip-controller.ts +407 -0
- package/src/ts/interaction/zoom-controller.ts +468 -0
- package/src/ts/interaction/zoom-router.ts +230 -0
- package/src/ts/layout/facet-grid.ts +346 -0
- package/src/ts/layout/plot-layout.ts +277 -0
- package/src/ts/layout/ticks.ts +168 -0
- package/src/ts/map/mercator.ts +204 -0
- package/src/ts/map/tile-cache.ts +96 -0
- package/src/ts/map/tile-layer.ts +382 -0
- package/src/ts/map/tile-loader.ts +143 -0
- package/src/ts/map/tile-source.ts +156 -0
- package/src/ts/plugin/charts.ts +286 -0
- package/src/ts/plugin/plugin.ts +668 -0
- package/src/ts/render/scheduler.ts +339 -0
- package/src/ts/shaders/area.frag.glsl +20 -0
- package/src/ts/shaders/area.vert.glsl +19 -0
- package/src/ts/shaders/bar.frag.glsl +25 -0
- package/src/ts/shaders/bar.vert.glsl +60 -0
- package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
- package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
- package/src/ts/shaders/density-extreme.frag.glsl +30 -0
- package/src/ts/shaders/density-mrt.frag.glsl +44 -0
- package/src/ts/shaders/density-mrt.vert.glsl +48 -0
- package/src/ts/shaders/density-resolve.frag.glsl +89 -0
- package/src/ts/shaders/density-resolve.vert.glsl +23 -0
- package/src/ts/shaders/density-splat.frag.glsl +34 -0
- package/src/ts/shaders/density-splat.vert.glsl +52 -0
- package/src/ts/shaders/gridline.frag.glsl +18 -0
- package/src/ts/shaders/gridline.vert.glsl +18 -0
- package/src/ts/shaders/heatmap.frag.glsl +23 -0
- package/src/ts/shaders/heatmap.vert.glsl +42 -0
- package/src/ts/shaders/line-uniform.frag.glsl +26 -0
- package/src/ts/shaders/line-uniform.vert.glsl +54 -0
- package/src/ts/shaders/line.frag.glsl +28 -0
- package/src/ts/shaders/line.vert.glsl +87 -0
- package/src/ts/shaders/scatter.frag.glsl +39 -0
- package/src/ts/shaders/scatter.vert.glsl +67 -0
- package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
- package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
- package/src/ts/shaders/tile.frag.glsl +27 -0
- package/src/ts/shaders/tile.vert.glsl +35 -0
- package/src/ts/shaders/treemap.frag.glsl +19 -0
- package/src/ts/shaders/treemap.vert.glsl +25 -0
- package/src/ts/shaders/y-scatter.frag.glsl +30 -0
- package/src/ts/shaders/y-scatter.vert.glsl +31 -0
- package/src/ts/theme/gradient.ts +312 -0
- package/src/ts/theme/palette.ts +64 -0
- package/src/ts/theme/theme-snapshot.ts +66 -0
- package/src/ts/theme/theme.ts +166 -0
- package/src/ts/transport/protocol.ts +497 -0
- package/src/ts/transport/renderer-transport.ts +788 -0
- package/src/ts/utils/css.ts +36 -0
- package/src/ts/utils/font-snapshot.ts +159 -0
- package/src/ts/webgl/buffer-pool.ts +163 -0
- package/src/ts/webgl/context-manager.ts +414 -0
- package/src/ts/webgl/gradient-texture.ts +84 -0
- package/src/ts/webgl/instanced-attrs.ts +139 -0
- package/src/ts/webgl/plot-frame.ts +91 -0
- package/src/ts/webgl/program-cache.ts +46 -0
- package/src/ts/webgl/shader-manifest.ts +148 -0
- package/src/ts/webgl/shader-registry.ts +97 -0
- package/src/ts/worker/boot.ts +22 -0
- package/src/ts/worker/dispatch.ts +99 -0
- package/src/ts/worker/font-loader.ts +89 -0
- package/src/ts/worker/renderer.worker.ts +734 -0
- package/src/ts/worker/session-host.ts +118 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import type { CandlestickChart } from "./candlestick";
|
|
14
|
+
import { renderCandlestickChromeOverlay } from "./candlestick-render";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pixels of horizontal slack around the wick centerline so narrow
|
|
18
|
+
* bodies with tall wicks stay clickable.
|
|
19
|
+
*/
|
|
20
|
+
const WICK_TOLERANCE_PX = 3;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Find the leftmost candle index whose `xCenter` is `>= target`. Bars
|
|
24
|
+
* are appended in (split, cat) order; within a split `xCenter` is
|
|
25
|
+
* monotonically increasing, but across splits it interleaves at the
|
|
26
|
+
* same catIdx. The hit-test still only needs the first candidate at or
|
|
27
|
+
* after `target` — subsequent split records share the same catIdx and
|
|
28
|
+
* are visited until xCenter exceeds `target + halfWidth`, so a plain
|
|
29
|
+
* binary search on `xCenter` ordered as written suffices when
|
|
30
|
+
* splits=1; for multi-split we fall back to a linear scan from the
|
|
31
|
+
* lower bound found.
|
|
32
|
+
*/
|
|
33
|
+
function lowerBoundXCenter(
|
|
34
|
+
xC: Float64Array,
|
|
35
|
+
count: number,
|
|
36
|
+
target: number,
|
|
37
|
+
): number {
|
|
38
|
+
let lo = 0;
|
|
39
|
+
let hi = count;
|
|
40
|
+
while (lo < hi) {
|
|
41
|
+
const mid = (lo + hi) >>> 1;
|
|
42
|
+
if (xC[mid] < target) {
|
|
43
|
+
lo = mid + 1;
|
|
44
|
+
} else {
|
|
45
|
+
hi = mid;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return lo;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function handleCandlestickHover(
|
|
53
|
+
chart: CandlestickChart,
|
|
54
|
+
mx: number,
|
|
55
|
+
my: number,
|
|
56
|
+
): void {
|
|
57
|
+
if (chart._pinnedIdx !== -1) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const layout = chart._lastLayout;
|
|
62
|
+
if (!layout) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const candles = chart._candles;
|
|
67
|
+
if (candles.count === 0) {
|
|
68
|
+
if (chart._hoveredIdx !== -1) {
|
|
69
|
+
chart._hoveredIdx = -1;
|
|
70
|
+
renderCandlestickChromeOverlay(chart);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Convert mouse → data once; from then on hit-tests are in data
|
|
77
|
+
// space, eliminating ~5 `dataToPixel` calls per candidate that the
|
|
78
|
+
// legacy implementation performed.
|
|
79
|
+
const plot = layout.plotRect;
|
|
80
|
+
const padXMin = layout.paddedXMin;
|
|
81
|
+
const padXMax = layout.paddedXMax;
|
|
82
|
+
const padYMin = layout.paddedYMin;
|
|
83
|
+
const padYMax = layout.paddedYMax;
|
|
84
|
+
if (
|
|
85
|
+
mx < plot.x ||
|
|
86
|
+
mx > plot.x + plot.width ||
|
|
87
|
+
my < plot.y ||
|
|
88
|
+
my > plot.y + plot.height
|
|
89
|
+
) {
|
|
90
|
+
if (chart._hoveredIdx !== -1) {
|
|
91
|
+
chart._hoveredIdx = -1;
|
|
92
|
+
renderCandlestickChromeOverlay(chart);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const dataX = padXMin + ((mx - plot.x) / plot.width) * (padXMax - padXMin);
|
|
99
|
+
const dataY = padYMax - ((my - plot.y) / plot.height) * (padYMax - padYMin);
|
|
100
|
+
const pxPerDataX = plot.width / (padXMax - padXMin);
|
|
101
|
+
const wickToleranceData = WICK_TOLERANCE_PX / pxPerDataX;
|
|
102
|
+
|
|
103
|
+
const xC = candles.xCenter;
|
|
104
|
+
const hw = candles.halfWidth;
|
|
105
|
+
const open = candles.open;
|
|
106
|
+
const close = candles.close;
|
|
107
|
+
const high = candles.high;
|
|
108
|
+
const low = candles.low;
|
|
109
|
+
|
|
110
|
+
// Estimate a generous halfWidth bound so the binary-search visible
|
|
111
|
+
// slice covers any candle whose body could overlap `dataX`. The
|
|
112
|
+
// halfWidth is uniform per build; conservatively read from the
|
|
113
|
+
// first record (or fall back to a small constant).
|
|
114
|
+
const maxHalfWidth = candles.count > 0 ? hw[0] : 0;
|
|
115
|
+
const tol = Math.max(maxHalfWidth, wickToleranceData);
|
|
116
|
+
|
|
117
|
+
// Binary-search to a small slice [lo, hi) covering candidates whose
|
|
118
|
+
// xCenter falls within ±tol of dataX. Candles outside this window
|
|
119
|
+
// can't possibly be hit; the linear scan that follows is bounded by
|
|
120
|
+
// (split count × overlap), not the full candle count.
|
|
121
|
+
const lo = lowerBoundXCenter(xC, candles.count, dataX - tol);
|
|
122
|
+
const hi = lowerBoundXCenter(xC, candles.count, dataX + tol + 1e-12);
|
|
123
|
+
|
|
124
|
+
// Walk the slice in reverse so the most-recently-added (frontmost)
|
|
125
|
+
// candle wins ties — matches legacy behavior.
|
|
126
|
+
let hit = -1;
|
|
127
|
+
for (let i = hi - 1; i >= lo; i--) {
|
|
128
|
+
const xc = xC[i];
|
|
129
|
+
const halfW = hw[i];
|
|
130
|
+
const xWithinBody = dataX >= xc - halfW && dataX <= xc + halfW;
|
|
131
|
+
const xWithinWick = Math.abs(dataX - xc) <= wickToleranceData;
|
|
132
|
+
if (!xWithinBody && !xWithinWick) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const o = open[i];
|
|
137
|
+
const c = close[i];
|
|
138
|
+
const bodyLow = o < c ? o : c;
|
|
139
|
+
const bodyHigh = o < c ? c : o;
|
|
140
|
+
const insideBody = xWithinBody && dataY >= bodyLow && dataY <= bodyHigh;
|
|
141
|
+
const insideWick = dataY >= low[i] && dataY <= high[i];
|
|
142
|
+
if (insideBody || insideWick) {
|
|
143
|
+
hit = i;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (hit !== chart._hoveredIdx) {
|
|
149
|
+
chart._hoveredIdx = hit;
|
|
150
|
+
renderCandlestickChromeOverlay(chart);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function showCandlestickPinnedTooltip(
|
|
155
|
+
chart: CandlestickChart,
|
|
156
|
+
idx: number,
|
|
157
|
+
): void {
|
|
158
|
+
chart._tooltip.dismiss();
|
|
159
|
+
chart._pinnedIdx = idx;
|
|
160
|
+
|
|
161
|
+
const candles = chart._candles;
|
|
162
|
+
if (idx < 0 || idx >= candles.count || !chart._lastLayout) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const lines = buildCandlestickTooltipLines(chart, idx);
|
|
167
|
+
if (lines.length === 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const xCenter = candles.xCenter[idx];
|
|
172
|
+
const yMid = (candles.high[idx] + candles.low[idx]) / 2;
|
|
173
|
+
const pos = chart._lastLayout.dataToPixel(xCenter, yMid);
|
|
174
|
+
|
|
175
|
+
// CSS bounds come from the chart's own layout, which is populated
|
|
176
|
+
// by the render path regardless of where the chart runs.
|
|
177
|
+
const cssWidth = chart._lastLayout.cssWidth;
|
|
178
|
+
const cssHeight = chart._lastLayout.cssHeight;
|
|
179
|
+
|
|
180
|
+
chart._tooltip.pin(lines, pos, { cssWidth, cssHeight });
|
|
181
|
+
|
|
182
|
+
// Pinning hides the inline hover tooltip but does not change the
|
|
183
|
+
// WebGL pass — only the chrome overlay needs to redraw.
|
|
184
|
+
chart._hoveredIdx = -1;
|
|
185
|
+
renderCandlestickChromeOverlay(chart);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function dismissCandlestickPinnedTooltip(chart: CandlestickChart): void {
|
|
189
|
+
chart._tooltip.dismiss();
|
|
190
|
+
chart._pinnedIdx = -1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Build tooltip lines for candle at index `idx` in the columnar
|
|
195
|
+
* storage. Indexed access avoids materializing a `CandleRecord` POJO
|
|
196
|
+
* on the hot tooltip path.
|
|
197
|
+
*/
|
|
198
|
+
export function buildCandlestickTooltipLines(
|
|
199
|
+
chart: CandlestickChart,
|
|
200
|
+
idx: number,
|
|
201
|
+
): string[] {
|
|
202
|
+
const lines: string[] = [];
|
|
203
|
+
const candles = chart._candles;
|
|
204
|
+
if (idx < 0 || idx >= candles.count) {
|
|
205
|
+
return lines;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const catIdx = candles.catIdx[idx];
|
|
209
|
+
const splitIdx = candles.splitIdx[idx];
|
|
210
|
+
const open = candles.open[idx];
|
|
211
|
+
const close = candles.close[idx];
|
|
212
|
+
const high = candles.high[idx];
|
|
213
|
+
const low = candles.low[idx];
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
chart._categoryAxisMode === "numeric" &&
|
|
217
|
+
chart._numericCategoryDomain &&
|
|
218
|
+
chart._categoryPositions
|
|
219
|
+
) {
|
|
220
|
+
const v = chart._categoryPositions[catIdx];
|
|
221
|
+
const xColumn = chart._groupBy[0];
|
|
222
|
+
lines.push(chart.getColumnFormatter(xColumn, "value")(v));
|
|
223
|
+
} else if (chart._rowPaths.length > 0) {
|
|
224
|
+
const parts: string[] = [];
|
|
225
|
+
for (const rp of chart._rowPaths) {
|
|
226
|
+
const s = rp.labels[catIdx] ?? "";
|
|
227
|
+
if (s) {
|
|
228
|
+
parts.push(s);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (parts.length > 0) {
|
|
233
|
+
lines.push(parts.join(" › "));
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
lines.push(`Row ${catIdx + chart._rowOffset}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (splitIdx >= 0 && chart._splitPrefixes.length > 1) {
|
|
240
|
+
const prefix = chart._splitPrefixes[splitIdx];
|
|
241
|
+
if (prefix) {
|
|
242
|
+
lines.push(prefix);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const openFmt = chart.getColumnFormatter(chart._columnSlots[0], "value");
|
|
247
|
+
const closeFmt = chart.getColumnFormatter(chart._columnSlots[1], "value");
|
|
248
|
+
const highFmt = chart.getColumnFormatter(chart._columnSlots[2], "value");
|
|
249
|
+
const lowFmt = chart.getColumnFormatter(chart._columnSlots[3], "value");
|
|
250
|
+
lines.push(`Open: ${openFmt(open)}`);
|
|
251
|
+
lines.push(`Close: ${closeFmt(close)}`);
|
|
252
|
+
lines.push(`High: ${highFmt(high)}`);
|
|
253
|
+
lines.push(`Low: ${lowFmt(low)}`);
|
|
254
|
+
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import type { WebGLContextManager } from "../../webgl/context-manager";
|
|
14
|
+
import type { CandlestickChart, CandlestickAutoFitCache } from "./candlestick";
|
|
15
|
+
import { PlotLayout } from "../../layout/plot-layout";
|
|
16
|
+
import { sampleGradient } from "../../theme/gradient";
|
|
17
|
+
import { renderInPlotFrame } from "../../webgl/plot-frame";
|
|
18
|
+
import { renderCanvasTooltip } from "../../interaction/tooltip-controller";
|
|
19
|
+
import { computeNiceTicks } from "../../layout/ticks";
|
|
20
|
+
import { type AxisDomain } from "../../axis/numeric-axis";
|
|
21
|
+
import {
|
|
22
|
+
renderBarAxesChrome,
|
|
23
|
+
renderBarGridlines,
|
|
24
|
+
type BarCategoryAxis,
|
|
25
|
+
} from "../../axis/bar-axis";
|
|
26
|
+
import {
|
|
27
|
+
measureCategoricalAxisHeight,
|
|
28
|
+
type CategoricalDomain,
|
|
29
|
+
} from "../../axis/categorical-axis";
|
|
30
|
+
import { buildCandlestickTooltipLines } from "./candlestick-interact";
|
|
31
|
+
import {
|
|
32
|
+
computeVisibleExtent,
|
|
33
|
+
type VisibleExtent,
|
|
34
|
+
} from "../common/visible-extent";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve up/down body colors from `theme.gradientStops`. Cached on the
|
|
38
|
+
* chart via `_upDownColorKey` (reference identity of the stops array)
|
|
39
|
+
* — only `restyle()` (which clears the theme cache via
|
|
40
|
+
* `invalidateTheme`) or a data load with a fresh theme triggers
|
|
41
|
+
* resampling. Legacy code re-sampled every frame.
|
|
42
|
+
*/
|
|
43
|
+
export function ensureUpDownColors(chart: CandlestickChart): void {
|
|
44
|
+
const theme = chart._resolveTheme();
|
|
45
|
+
const stops = theme.gradientStops;
|
|
46
|
+
if (chart._upDownColorKey === stops) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const upSample = sampleGradient(stops, 1.0);
|
|
51
|
+
const downSample = sampleGradient(stops, 0.0);
|
|
52
|
+
chart._upColor = [upSample[0], upSample[1], upSample[2]];
|
|
53
|
+
chart._downColor = [downSample[0], downSample[1], downSample[2]];
|
|
54
|
+
chart._upDownColorKey = stops;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Drop persistent body / wick / OHLC vertex buffers. Subsequent draws
|
|
59
|
+
* no-op until the next {@link rebuildGlyphBuffers} call.
|
|
60
|
+
*/
|
|
61
|
+
export function invalidateGlyphBuffers(chart: CandlestickChart): void {
|
|
62
|
+
chart._glyphs.bodyWick.invalidateBuffers(chart);
|
|
63
|
+
chart._glyphs.ohlc.invalidateBuffers(chart);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Rebuild the persistent body / wick / OHLC vertex buffers. Reads
|
|
68
|
+
* `_candles` (columnar) plus the cached `_upColor` / `_downColor` to
|
|
69
|
+
* populate the GPU buffers exactly once per data load. Subsequent pan/
|
|
70
|
+
* zoom redraws bind + dispatch with no uploads.
|
|
71
|
+
*/
|
|
72
|
+
export function rebuildGlyphBuffers(
|
|
73
|
+
chart: CandlestickChart,
|
|
74
|
+
glManager: WebGLContextManager,
|
|
75
|
+
): void {
|
|
76
|
+
chart._glyphs.bodyWick.rebuildBuffers(chart, glManager);
|
|
77
|
+
chart._glyphs.ohlc.rebuildBuffers(chart, glManager);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function renderCandlestickFrame(
|
|
81
|
+
chart: CandlestickChart,
|
|
82
|
+
glManager: WebGLContextManager,
|
|
83
|
+
): void {
|
|
84
|
+
const gl = glManager.gl;
|
|
85
|
+
const dpr = glManager.dpr;
|
|
86
|
+
const cssWidth = gl.canvas.width / dpr;
|
|
87
|
+
const cssHeight = gl.canvas.height / dpr;
|
|
88
|
+
if (cssWidth <= 0 || cssHeight <= 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (chart._numCategories === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const theme = chart._resolveTheme();
|
|
97
|
+
|
|
98
|
+
// Up/down colors sampled at the extremes of the theme gradient.
|
|
99
|
+
// Cached on the chart — `ensureUpDownColors` is a no-op when the
|
|
100
|
+
// gradient-stops reference matches the previous call. `restyle()`
|
|
101
|
+
// clears the cache via `invalidateTheme`, and the data-load path
|
|
102
|
+
// refreshes it before rebuilding glyph buffers.
|
|
103
|
+
ensureUpDownColors(chart);
|
|
104
|
+
|
|
105
|
+
const numericCat = chart._categoryAxisMode === "numeric";
|
|
106
|
+
const xDomainMin = numericCat ? chart._numericCategoryDomain!.min : -0.5;
|
|
107
|
+
const xDomainMax = numericCat
|
|
108
|
+
? chart._numericCategoryDomain!.max
|
|
109
|
+
: chart._numCategories - 0.5;
|
|
110
|
+
if (chart._zoomController) {
|
|
111
|
+
chart._zoomController.setBaseDomain(
|
|
112
|
+
xDomainMin,
|
|
113
|
+
xDomainMax,
|
|
114
|
+
chart._yDomain.min,
|
|
115
|
+
chart._yDomain.max,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const vis = chart._zoomController
|
|
120
|
+
? chart._zoomController.getVisibleDomain()
|
|
121
|
+
: {
|
|
122
|
+
xMin: xDomainMin,
|
|
123
|
+
xMax: xDomainMax,
|
|
124
|
+
yMin: chart._yDomain.min,
|
|
125
|
+
yMax: chart._yDomain.max,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Auto-fit the price axis to the visible X window. Skipped at
|
|
129
|
+
// default zoom (the refit equals `_yDomain` there and would only
|
|
130
|
+
// churn baselines).
|
|
131
|
+
if (
|
|
132
|
+
chart._autoFitValue &&
|
|
133
|
+
chart._zoomController &&
|
|
134
|
+
!chart._zoomController.isDefault()
|
|
135
|
+
) {
|
|
136
|
+
const fit = computeVisibleCandleExtent(chart, vis.xMin, vis.xMax);
|
|
137
|
+
if (fit.hasFit) {
|
|
138
|
+
vis.yMin = fit.min;
|
|
139
|
+
vis.yMax = fit.max;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const hasXLabel = chart._groupBy.length > 0;
|
|
144
|
+
|
|
145
|
+
const provisionalDomain: CategoricalDomain = {
|
|
146
|
+
levels: chart._rowPaths,
|
|
147
|
+
numRows: chart._numCategories,
|
|
148
|
+
levelLabels: chart._groupBy.slice(),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
let layout: PlotLayout;
|
|
152
|
+
if (numericCat) {
|
|
153
|
+
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
154
|
+
hasXLabel,
|
|
155
|
+
hasYLabel: true,
|
|
156
|
+
hasLegend: false,
|
|
157
|
+
bottomExtra: 24,
|
|
158
|
+
});
|
|
159
|
+
} else {
|
|
160
|
+
const estLeft = 55 + 16;
|
|
161
|
+
const estRight = 16;
|
|
162
|
+
const estPlotWidth = Math.max(1, cssWidth - estLeft - estRight);
|
|
163
|
+
const bottomExtra = measureCategoricalAxisHeight(
|
|
164
|
+
provisionalDomain,
|
|
165
|
+
estPlotWidth,
|
|
166
|
+
);
|
|
167
|
+
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
168
|
+
hasXLabel,
|
|
169
|
+
hasYLabel: true,
|
|
170
|
+
hasLegend: false,
|
|
171
|
+
bottomExtra,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
chart._lastLayout = layout;
|
|
176
|
+
if (chart._zoomController) {
|
|
177
|
+
chart._zoomController.updateLayout(layout);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const projection = layout.buildProjectionMatrix(
|
|
181
|
+
vis.xMin,
|
|
182
|
+
vis.xMax,
|
|
183
|
+
vis.yMin,
|
|
184
|
+
vis.yMax,
|
|
185
|
+
"y",
|
|
186
|
+
undefined,
|
|
187
|
+
undefined,
|
|
188
|
+
chart._categoryOrigin,
|
|
189
|
+
0,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const yTicks = computeNiceTicks(vis.yMin, vis.yMax, 6);
|
|
193
|
+
const yLabel = chart._columnSlots[0] || "";
|
|
194
|
+
|
|
195
|
+
const xDomain: CategoricalDomain = provisionalDomain;
|
|
196
|
+
const yDomain: AxisDomain = {
|
|
197
|
+
min: vis.yMin,
|
|
198
|
+
max: vis.yMax,
|
|
199
|
+
label: yLabel,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (chart._gridlineCanvas) {
|
|
203
|
+
renderBarGridlines(
|
|
204
|
+
chart._gridlineCanvas,
|
|
205
|
+
layout,
|
|
206
|
+
yTicks,
|
|
207
|
+
theme,
|
|
208
|
+
glManager.dpr,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
renderInPlotFrame(gl, layout, glManager.dpr, () => {
|
|
213
|
+
if (chart._defaultChartType === "ohlc") {
|
|
214
|
+
chart._glyphs.ohlc.draw(chart, gl, glManager, projection);
|
|
215
|
+
} else {
|
|
216
|
+
chart._glyphs.bodyWick.draw(chart, gl, glManager, projection);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
chart._lastXDomain = xDomain;
|
|
221
|
+
chart._lastYDomain = yDomain;
|
|
222
|
+
chart._lastYTicks = yTicks;
|
|
223
|
+
chart._lastCatTicks = numericCat
|
|
224
|
+
? computeNiceTicks(vis.xMin, vis.xMax, 6)
|
|
225
|
+
: null;
|
|
226
|
+
renderCandlestickChromeOverlay(chart);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function renderCandlestickChromeOverlay(chart: CandlestickChart): void {
|
|
230
|
+
if (
|
|
231
|
+
!chart._chromeCanvas ||
|
|
232
|
+
!chart._lastLayout ||
|
|
233
|
+
!chart._lastYDomain ||
|
|
234
|
+
!chart._lastYTicks
|
|
235
|
+
) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const theme = chart._resolveTheme();
|
|
240
|
+
let catAxis: BarCategoryAxis;
|
|
241
|
+
if (
|
|
242
|
+
chart._categoryAxisMode === "numeric" &&
|
|
243
|
+
chart._numericCategoryDomain &&
|
|
244
|
+
chart._lastCatTicks
|
|
245
|
+
) {
|
|
246
|
+
catAxis = {
|
|
247
|
+
mode: "numeric",
|
|
248
|
+
domain: {
|
|
249
|
+
min: chart._numericCategoryDomain.min,
|
|
250
|
+
max: chart._numericCategoryDomain.max,
|
|
251
|
+
isDate: chart._numericCategoryDomain.isDate,
|
|
252
|
+
label: chart._numericCategoryDomain.label,
|
|
253
|
+
},
|
|
254
|
+
ticks: chart._lastCatTicks,
|
|
255
|
+
};
|
|
256
|
+
} else if (chart._lastXDomain) {
|
|
257
|
+
catAxis = { mode: "category", domain: chart._lastXDomain };
|
|
258
|
+
} else {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// OHLC value axis: all four price columns share the value axis;
|
|
263
|
+
// pick the first available (Open is always present, the rest can
|
|
264
|
+
// be null per `candlestick-build.ts`).
|
|
265
|
+
const valueColumn =
|
|
266
|
+
chart._columnSlots[0] ??
|
|
267
|
+
chart._columnSlots[1] ??
|
|
268
|
+
chart._columnSlots[2] ??
|
|
269
|
+
chart._columnSlots[3];
|
|
270
|
+
const xColumn = chart._groupBy[0];
|
|
271
|
+
renderBarAxesChrome(
|
|
272
|
+
chart._chromeCanvas,
|
|
273
|
+
catAxis,
|
|
274
|
+
chart._lastYDomain,
|
|
275
|
+
chart._lastYTicks,
|
|
276
|
+
chart._lastLayout,
|
|
277
|
+
theme,
|
|
278
|
+
chart._glManager?.dpr ?? 1,
|
|
279
|
+
undefined,
|
|
280
|
+
undefined,
|
|
281
|
+
false,
|
|
282
|
+
{
|
|
283
|
+
value: chart.getColumnFormatter(valueColumn, "tick"),
|
|
284
|
+
category: chart.getColumnFormatter(xColumn, "tick"),
|
|
285
|
+
},
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
if (chart._hoveredIdx >= 0 && chart._hoveredIdx < chart._candles.count) {
|
|
289
|
+
renderCandlestickTooltip(chart);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderCandlestickTooltip(chart: CandlestickChart): void {
|
|
294
|
+
if (!chart._chromeCanvas || !chart._lastLayout) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const i = chart._hoveredIdx;
|
|
299
|
+
const candles = chart._candles;
|
|
300
|
+
if (i < 0 || i >= candles.count) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const layout = chart._lastLayout;
|
|
305
|
+
const xCenter = candles.xCenter[i];
|
|
306
|
+
const yMid = (candles.high[i] + candles.low[i]) / 2;
|
|
307
|
+
const pos = layout.dataToPixel(xCenter, yMid);
|
|
308
|
+
const lines = buildCandlestickTooltipLines(chart, i);
|
|
309
|
+
const theme = chart._resolveTheme();
|
|
310
|
+
renderCanvasTooltip(
|
|
311
|
+
chart._chromeCanvas,
|
|
312
|
+
pos,
|
|
313
|
+
lines,
|
|
314
|
+
layout,
|
|
315
|
+
theme,
|
|
316
|
+
chart._glManager?.dpr ?? 1,
|
|
317
|
+
{
|
|
318
|
+
crosshair: false,
|
|
319
|
+
highlightRadius: 0,
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Price extent over candles whose `xCenter` falls inside
|
|
326
|
+
* `[visXMin, visXMax]`. Uses `low`/`high` (not `open`/`close`) so the
|
|
327
|
+
* wick stays inside the plot at any zoom. Cached on
|
|
328
|
+
* `chart._autoFitCache`; hover-only redraws hit the cache.
|
|
329
|
+
*
|
|
330
|
+
* Cache lifetime: reset on data upload ([candlestick.ts]
|
|
331
|
+
* `uploadAndRender`).
|
|
332
|
+
*/
|
|
333
|
+
function computeVisibleCandleExtent(
|
|
334
|
+
chart: CandlestickChart,
|
|
335
|
+
visXMin: number,
|
|
336
|
+
visXMax: number,
|
|
337
|
+
): VisibleExtent {
|
|
338
|
+
const cache = chart._autoFitCache;
|
|
339
|
+
if (cache && cache.xMin === visXMin && cache.xMax === visXMax) {
|
|
340
|
+
return cache;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const next = cache ?? newCandlestickAutoFitCache();
|
|
344
|
+
next.xMin = visXMin;
|
|
345
|
+
next.xMax = visXMax;
|
|
346
|
+
|
|
347
|
+
// Walk the columnar storage directly; the legacy form built a
|
|
348
|
+
// closure adapter per call, defeating monomorphism in
|
|
349
|
+
// `computeVisibleExtent`.
|
|
350
|
+
const candles = chart._candles;
|
|
351
|
+
let lo = Infinity;
|
|
352
|
+
let hi = -Infinity;
|
|
353
|
+
let hasFit = false;
|
|
354
|
+
const xC = candles.xCenter;
|
|
355
|
+
const lows = candles.low;
|
|
356
|
+
const highs = candles.high;
|
|
357
|
+
for (let j = 0; j < candles.count; j++) {
|
|
358
|
+
const cx = xC[j];
|
|
359
|
+
if (cx < visXMin || cx > visXMax) {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (lows[j] < lo) {
|
|
364
|
+
lo = lows[j];
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (highs[j] > hi) {
|
|
368
|
+
hi = highs[j];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
hasFit = true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
next.min = hasFit ? lo : 0;
|
|
375
|
+
next.max = hasFit ? hi : 1;
|
|
376
|
+
next.hasFit = hasFit;
|
|
377
|
+
chart._autoFitCache = next;
|
|
378
|
+
|
|
379
|
+
// Reference suppression — `computeVisibleExtent` retained for the
|
|
380
|
+
// shared common helper but no longer used in this fast path.
|
|
381
|
+
void computeVisibleExtent;
|
|
382
|
+
return next;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function newCandlestickAutoFitCache(): CandlestickAutoFitCache {
|
|
386
|
+
return { xMin: 0, xMax: 0, min: 0, max: 1, hasFit: false };
|
|
387
|
+
}
|