@wick-charts/vue 0.4.2 → 0.5.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/README.md +5 -3
- package/dist/BarSeries.vue.js +44 -0
- package/dist/BarSeries.vue2.js +4 -0
- package/dist/CandlestickSeries.vue.js +45 -0
- package/dist/CandlestickSeries.vue2.js +4 -0
- package/dist/ChartContainer.vue.js +168 -0
- package/dist/ChartContainer.vue2.js +4 -0
- package/dist/LineSeries.vue.js +44 -0
- package/dist/LineSeries.vue2.js +4 -0
- package/dist/PieSeries.vue.js +34 -0
- package/dist/PieSeries.vue2.js +4 -0
- package/dist/composables.js +44 -0
- package/dist/context.js +25 -0
- package/dist/core/dist/animation/animator.js +72 -0
- package/dist/core/dist/animation/config.js +145 -0
- package/dist/core/dist/animation/easing.js +4 -0
- package/dist/core/dist/animation/scalar-spring.js +57 -0
- package/dist/core/dist/animation/spring.js +11 -0
- package/dist/core/dist/animation/time.js +19 -0
- package/dist/core/dist/animation/viewport-engine.js +174 -0
- package/dist/core/dist/animation/visible-range-spring.js +64 -0
- package/dist/core/dist/animation/y-range-hermite.js +72 -0
- package/dist/core/dist/animation/y-range-snap.js +31 -0
- package/dist/core/dist/animation/y-range-spring.js +59 -0
- package/dist/core/dist/axis/dom-labels.js +60 -0
- package/dist/core/dist/canvas-manager.js +109 -0
- package/dist/core/dist/chart/edge-indicators.js +26 -0
- package/dist/core/dist/chart/fit-to-data.js +9 -0
- package/dist/core/dist/chart/last-value.js +31 -0
- package/dist/core/dist/chart/options.js +35 -0
- package/dist/core/dist/chart/pan-zoom-math.js +66 -0
- package/dist/core/dist/chart/streaming-cadence.js +70 -0
- package/dist/core/dist/chart/streaming-target.js +22 -0
- package/dist/core/dist/chart/viewport-padding.js +6 -0
- package/dist/core/dist/chart/y-target.js +24 -0
- package/dist/core/dist/chart.js +1495 -0
- package/dist/core/dist/components/crosshair.js +9 -0
- package/dist/core/dist/components/edge-indicator.js +36 -0
- package/dist/core/dist/components/grid.js +22 -0
- package/dist/core/dist/components/marker.js +41 -0
- package/dist/core/dist/components/reference-line.js +48 -0
- package/dist/core/dist/components/time-region.js +35 -0
- package/dist/core/dist/data/decimation.js +54 -0
- package/dist/core/dist/data/store.js +76 -0
- package/dist/core/dist/data/sync.js +47 -0
- package/dist/core/dist/events.js +19 -0
- package/dist/core/dist/interactions/handler.js +84 -0
- package/dist/core/dist/interactions/pan.js +26 -0
- package/dist/core/dist/interactions/zoom.js +21 -0
- package/dist/core/dist/navigator/controller.js +374 -0
- package/dist/core/dist/navigator/decimate.js +44 -0
- package/dist/core/dist/navigator/interactions.js +28 -0
- package/dist/core/dist/navigator/render.js +68 -0
- package/dist/core/dist/perf/counting-context.js +19 -0
- package/dist/core/dist/perf/perf-hud.js +453 -0
- package/dist/core/dist/perf/perf-monitor.js +132 -0
- package/dist/core/dist/render-scheduler.js +31 -0
- package/dist/core/dist/scales/tick-tracker.js +110 -0
- package/dist/core/dist/scales/x-scale.js +145 -0
- package/dist/core/dist/scales/y-scale.js +156 -0
- package/dist/core/dist/series/bar.js +341 -0
- package/dist/core/dist/series/base-multi-layer.js +438 -0
- package/dist/core/dist/series/candlestick.js +481 -0
- package/dist/core/dist/series/definition.js +11 -0
- package/dist/core/dist/series/line.js +446 -0
- package/dist/core/dist/series/painters/builtins.js +36 -0
- package/dist/core/dist/series/painters/canvas-path.js +79 -0
- package/dist/core/dist/series/painters/resolve.js +32 -0
- package/dist/core/dist/series/pie.js +545 -0
- package/dist/core/dist/series/register-builtins.js +11 -0
- package/dist/core/dist/series/stack-math.js +17 -0
- package/dist/core/dist/snapshots.js +107 -0
- package/dist/core/dist/theme/create.js +176 -0
- package/dist/core/dist/theme/resolve.js +14 -0
- package/dist/core/dist/theme/themes/andromeda.js +33 -0
- package/dist/core/dist/theme/themes/ayu-mirage.js +33 -0
- package/dist/core/dist/theme/themes/catppuccin.js +33 -0
- package/dist/core/dist/theme/themes/dracula.js +35 -0
- package/dist/core/dist/theme/themes/github-light.js +34 -0
- package/dist/core/dist/theme/themes/gruvbox.js +33 -0
- package/dist/core/dist/theme/themes/handwritten.js +101 -0
- package/dist/core/dist/theme/themes/high-contrast.js +33 -0
- package/dist/core/dist/theme/themes/lavender-mist.js +33 -0
- package/dist/core/dist/theme/themes/light-pink.js +34 -0
- package/dist/core/dist/theme/themes/material-palenight.js +33 -0
- package/dist/core/dist/theme/themes/minimal-light.js +34 -0
- package/dist/core/dist/theme/themes/mint-breeze.js +33 -0
- package/dist/core/dist/theme/themes/monokai-pro.js +33 -0
- package/dist/core/dist/theme/themes/night-owl.js +35 -0
- package/dist/core/dist/theme/themes/one-dark-pro.js +33 -0
- package/dist/core/dist/theme/themes/panda.js +33 -0
- package/dist/core/dist/theme/themes/peach-cream.js +33 -0
- package/dist/core/dist/theme/themes/quiet-light.js +37 -0
- package/dist/core/dist/theme/themes/rose-pine-dawn.js +33 -0
- package/dist/core/dist/theme/themes/sand-dune.js +33 -0
- package/dist/core/dist/theme/themes/solarized-light.js +34 -0
- package/dist/core/dist/tooltip-position.js +10 -0
- package/dist/core/dist/utils/color.js +37 -0
- package/dist/core/dist/utils/format.js +29 -0
- package/dist/core/dist/utils/math.js +26 -0
- package/dist/core/dist/utils/poisoned-data-reporter.js +15 -0
- package/dist/core/dist/utils/time.js +41 -0
- package/dist/index.d.ts +827 -63
- package/dist/index.js +142 -8025
- package/dist/ui/Crosshair.vue.js +53 -0
- package/dist/ui/Crosshair.vue2.js +4 -0
- package/dist/ui/InfoBar.vue.js +143 -0
- package/dist/ui/InfoBar.vue2.js +4 -0
- package/dist/ui/Legend.vue.js +212 -0
- package/dist/ui/Legend.vue2.js +4 -0
- package/dist/ui/Marker.vue.js +47 -0
- package/dist/ui/Marker.vue2.js +4 -0
- package/dist/ui/Navigator.vue.js +58 -0
- package/dist/ui/Navigator.vue2.js +4 -0
- package/dist/ui/NumberFlow.vue.js +69 -0
- package/dist/ui/NumberFlow.vue2.js +4 -0
- package/dist/ui/PieLegend.vue.js +132 -0
- package/dist/ui/PieLegend.vue2.js +4 -0
- package/dist/ui/PieTooltip.vue.js +135 -0
- package/dist/ui/PieTooltip.vue2.js +4 -0
- package/dist/ui/ReferenceLine.vue.js +44 -0
- package/dist/ui/ReferenceLine.vue2.js +4 -0
- package/dist/ui/TimeAxis.vue.js +43 -0
- package/dist/ui/TimeAxis.vue2.js +4 -0
- package/dist/ui/TimeRegion.vue.js +41 -0
- package/dist/ui/TimeRegion.vue2.js +4 -0
- package/dist/ui/Title.vue.js +54 -0
- package/dist/ui/Title.vue2.js +4 -0
- package/dist/ui/Tooltip.vue.js +195 -0
- package/dist/ui/Tooltip.vue2.js +4 -0
- package/dist/ui/YAxis.vue.js +42 -0
- package/dist/ui/YAxis.vue2.js +4 -0
- package/dist/ui/YLabel.vue.js +105 -0
- package/dist/ui/YLabel.vue2.js +4 -0
- package/package.json +2 -2
- package/src/BarSeries.vue +12 -13
- package/src/CandlestickSeries.vue +17 -6
- package/src/LineSeries.vue +12 -13
- package/src/PieSeries.vue +2 -1
- package/src/index.ts +23 -0
- package/src/ui/Legend.vue +1 -2
- package/src/ui/Marker.vue +62 -0
- package/src/ui/ReferenceLine.vue +58 -0
- package/src/ui/TimeRegion.vue +54 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { Animator as G } from "../animation/animator.js";
|
|
2
|
+
import { DEFAULT_CANDLESTICK_SMOOTH as U, DEFAULT_CANDLESTICK_ENTRY as X } from "../animation/config.js";
|
|
3
|
+
import { easeOutCubic as j } from "../animation/easing.js";
|
|
4
|
+
import { ScalarSpring as P } from "../animation/scalar-spring.js";
|
|
5
|
+
import { decimateOHLCData as q } from "../data/decimation.js";
|
|
6
|
+
import { TimeSeriesStore as J } from "../data/store.js";
|
|
7
|
+
import { resolveCandlestickBodyColor as E } from "../theme/resolve.js";
|
|
8
|
+
import { hexToRgba as $ } from "../utils/color.js";
|
|
9
|
+
import { clamp as Q, lerp as V } from "../utils/math.js";
|
|
10
|
+
import { isPoisonedNumber as N, reportPoisonedData as Z } from "../utils/poisoned-data-reporter.js";
|
|
11
|
+
import { normalizeOHLCArray as tt, normalizeTime as F } from "../utils/time.js";
|
|
12
|
+
import { fillRoundedRect as H } from "./painters/canvas-path.js";
|
|
13
|
+
import { resolveCandlePainter as et } from "./painters/resolve.js";
|
|
14
|
+
const it = 0.15, ot = 5e-3;
|
|
15
|
+
class st {
|
|
16
|
+
#t;
|
|
17
|
+
#e;
|
|
18
|
+
#o;
|
|
19
|
+
#n;
|
|
20
|
+
#i;
|
|
21
|
+
#s;
|
|
22
|
+
// Sticky once any seen candle carries volume — mirrors the old lerp, which
|
|
23
|
+
// only emitted `volume` when a side had it, so a volume-less candle stays
|
|
24
|
+
// volume-less and the histogram keeps skipping it.
|
|
25
|
+
#r;
|
|
26
|
+
constructor(t) {
|
|
27
|
+
this.#t = new P(t.open), this.#e = new P(t.high), this.#o = new P(t.low), this.#n = new P(t.close), this.#i = new P(t.volume ?? 0), this.#s = t.time, this.#r = t.volume !== void 0;
|
|
28
|
+
}
|
|
29
|
+
retarget(t, e = {}) {
|
|
30
|
+
this.#s = t.time, t.volume !== void 0 && (this.#r = !0), this.#t.retarget(t.open, e), this.#e.retarget(t.high, e), this.#o.retarget(t.low, e), this.#n.retarget(t.close, e), this.#i.retarget(t.volume ?? 0, e);
|
|
31
|
+
}
|
|
32
|
+
tick(t) {
|
|
33
|
+
const e = this.#t.tick(t), s = this.#e.tick(t), i = this.#o.tick(t), r = this.#n.tick(t), o = this.#i.tick(t);
|
|
34
|
+
return e || s || i || r || o;
|
|
35
|
+
}
|
|
36
|
+
get current() {
|
|
37
|
+
const t = {
|
|
38
|
+
time: this.#s,
|
|
39
|
+
open: this.#t.current,
|
|
40
|
+
high: this.#e.current,
|
|
41
|
+
low: this.#o.current,
|
|
42
|
+
close: this.#n.current
|
|
43
|
+
};
|
|
44
|
+
return this.#r && (t.volume = this.#i.current), t;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const O = 2, nt = { tl: !0, tr: !0, br: !0, bl: !0 }, _ = { tl: !0, tr: !0, br: !1, bl: !1 }, rt = {
|
|
48
|
+
up: { body: "#26a69a", wick: "#26a69a" },
|
|
49
|
+
down: { body: "#ef5350", wick: "#ef5350" },
|
|
50
|
+
bodyWidthRatio: 0.6,
|
|
51
|
+
cornerRadius: O,
|
|
52
|
+
entryMs: X,
|
|
53
|
+
smoothMs: U
|
|
54
|
+
};
|
|
55
|
+
function I(a) {
|
|
56
|
+
return {
|
|
57
|
+
...a,
|
|
58
|
+
entryMs: a.entryMs === !1 ? 0 : a.entryMs ?? X,
|
|
59
|
+
smoothMs: a.smoothMs === !1 ? 0 : a.smoothMs ?? U
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
class at {
|
|
63
|
+
kind = "candlestick";
|
|
64
|
+
store;
|
|
65
|
+
options;
|
|
66
|
+
// --- Animation state ----------------------------------------------------
|
|
67
|
+
/** Smoothed OHLC for the live last candle. Reset on `appendPoint` (new
|
|
68
|
+
* candle → identity snap) and chased on `updateLastPoint`. */
|
|
69
|
+
displayedLast = null;
|
|
70
|
+
/** Per-candle entrance registry. Single-layer, so no per-layer split. */
|
|
71
|
+
entries = /* @__PURE__ */ new Map();
|
|
72
|
+
#t = null;
|
|
73
|
+
/** EMA-smoothed volume scale (tallest visible bar). `null` re-seeds (snaps)
|
|
74
|
+
* on the next frame — set on construction and bulk data replacement. */
|
|
75
|
+
#e = null;
|
|
76
|
+
/** False while {@link #displayedMaxVol} is still chasing the raw window max —
|
|
77
|
+
* feeds {@link needsAnimation} so frames keep flowing until it converges. */
|
|
78
|
+
#o = !0;
|
|
79
|
+
/** Whether the previous frame was rendering decimated (bucketed) candles.
|
|
80
|
+
* Crossing this boundary changes `volume` semantics (per-candle ↔ summed
|
|
81
|
+
* bucket), so the volume scale snaps rather than easing — otherwise the EMA
|
|
82
|
+
* clamps every bar to full band height for ~30 frames. */
|
|
83
|
+
#n = !1;
|
|
84
|
+
/** Series-level alpha for visibility cross-fade. Chart calls {@link setAlpha} on hide. */
|
|
85
|
+
#i = new G({
|
|
86
|
+
initial: 1,
|
|
87
|
+
duration: 0,
|
|
88
|
+
lerp: (t, e, s) => t + (e - t) * s
|
|
89
|
+
});
|
|
90
|
+
/** Body gradients keyed by `top|bottom|height` — built once at y=0..h and
|
|
91
|
+
* positioned per candle via `ctx.translate`, instead of a fresh
|
|
92
|
+
* `createLinearGradient` for every body on every frame. */
|
|
93
|
+
#s = /* @__PURE__ */ new Map();
|
|
94
|
+
constructor(t, e) {
|
|
95
|
+
this.store = t, this.options = I({ ...rt, ...e }), this.displayedLast = t.last() ?? null;
|
|
96
|
+
}
|
|
97
|
+
isEntryEnabled() {
|
|
98
|
+
return (this.options.entryAnimation ?? "unfold") !== "none";
|
|
99
|
+
}
|
|
100
|
+
updateOptions(t) {
|
|
101
|
+
this.options = I({ ...this.options, ...t });
|
|
102
|
+
}
|
|
103
|
+
getColor() {
|
|
104
|
+
return E(this.options.up.body);
|
|
105
|
+
}
|
|
106
|
+
// --- SeriesRenderer interface implementation ------------------------------
|
|
107
|
+
setData(t) {
|
|
108
|
+
this.store.setData(tt(t ?? []));
|
|
109
|
+
const e = this.store.last();
|
|
110
|
+
this.displayedLast = e ?? null, this.#t = null, this.entries.clear(), this.#e = null;
|
|
111
|
+
}
|
|
112
|
+
appendPoint(t) {
|
|
113
|
+
const e = t, s = F(e.time), i = { ...e, time: s };
|
|
114
|
+
this.store.append(i), this.displayedLast = i, this.#t = null;
|
|
115
|
+
const r = this.options.entryMs;
|
|
116
|
+
this.isEntryEnabled() && r > 0 && this.entries.set(s, { startTime: performance.now() });
|
|
117
|
+
}
|
|
118
|
+
updateLastPoint(t) {
|
|
119
|
+
const e = t, s = F(e.time), i = { ...e, time: s };
|
|
120
|
+
this.store.updateLast(i);
|
|
121
|
+
const r = this.options.smoothMs;
|
|
122
|
+
if (r <= 0) {
|
|
123
|
+
this.displayedLast = i, this.#t = null;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let o = this.#t;
|
|
127
|
+
if (o === null) {
|
|
128
|
+
const l = this.displayedLast ?? i;
|
|
129
|
+
o = new st(l), this.#t = o;
|
|
130
|
+
}
|
|
131
|
+
o.retarget(i, { settleMs: r });
|
|
132
|
+
}
|
|
133
|
+
keepLast(t) {
|
|
134
|
+
if (t < 0) return;
|
|
135
|
+
const e = this.store.length - t;
|
|
136
|
+
e <= 0 || this.store.trimStart(e);
|
|
137
|
+
}
|
|
138
|
+
getLayerCount() {
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
setLayerVisible(t, e) {
|
|
142
|
+
}
|
|
143
|
+
isLayerVisible(t) {
|
|
144
|
+
return !0;
|
|
145
|
+
}
|
|
146
|
+
getLayerColors() {
|
|
147
|
+
return [E(this.options.up.body)];
|
|
148
|
+
}
|
|
149
|
+
applyTheme(t, e) {
|
|
150
|
+
this.updateOptions({
|
|
151
|
+
up: { ...t.candlestick.up },
|
|
152
|
+
down: { ...t.candlestick.down }
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
onDataChanged(t) {
|
|
156
|
+
return this.store.on("update", t), () => this.store.off("update", t);
|
|
157
|
+
}
|
|
158
|
+
dispose() {
|
|
159
|
+
this.store.removeAllListeners();
|
|
160
|
+
}
|
|
161
|
+
// --- Animation lifecycle ------------------------------------------------
|
|
162
|
+
/**
|
|
163
|
+
* Advance the live-OHLC chase + alpha fade against `now` and prune
|
|
164
|
+
* fully-settled entrance entries. Called from `render()` once per frame.
|
|
165
|
+
*/
|
|
166
|
+
tickAnimations(t) {
|
|
167
|
+
this.#i.tick(t);
|
|
168
|
+
const e = this.#t;
|
|
169
|
+
if (e !== null) {
|
|
170
|
+
const i = e.tick(t);
|
|
171
|
+
this.displayedLast = e.current, i || (this.#t = null);
|
|
172
|
+
}
|
|
173
|
+
const s = this.options.entryMs;
|
|
174
|
+
if (s <= 0) {
|
|
175
|
+
this.entries.clear();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
for (const [i, r] of this.entries)
|
|
179
|
+
t - r.startTime >= s && this.entries.delete(i);
|
|
180
|
+
}
|
|
181
|
+
/** True while OHLC chase, any per-candle entrance, alpha fade, or the volume
|
|
182
|
+
* scale is unsettled. */
|
|
183
|
+
get needsAnimation() {
|
|
184
|
+
return this.#i.animating || this.#t !== null || this.entries.size > 0 || !this.#o;
|
|
185
|
+
}
|
|
186
|
+
/** Abort in-flight per-candle entrance animations. Live-OHLC chase is left intact. */
|
|
187
|
+
cancelEntranceAnimations() {
|
|
188
|
+
this.entries.clear();
|
|
189
|
+
}
|
|
190
|
+
setAlpha(t, e) {
|
|
191
|
+
this.#i.setTarget(t, { duration: e });
|
|
192
|
+
}
|
|
193
|
+
getAlpha() {
|
|
194
|
+
return this.#i.current;
|
|
195
|
+
}
|
|
196
|
+
getLastValue() {
|
|
197
|
+
const t = this.store.last();
|
|
198
|
+
return t ? t.close : null;
|
|
199
|
+
}
|
|
200
|
+
getDataAtTime(t, e) {
|
|
201
|
+
return this.store.findNearest(t, e);
|
|
202
|
+
}
|
|
203
|
+
// --- TimeSeriesRenderer data queries --------------------------------------
|
|
204
|
+
getTimeBounds() {
|
|
205
|
+
const t = this.store.first(), e = this.store.last();
|
|
206
|
+
return !t || !e ? null : { first: t.time, last: e.time };
|
|
207
|
+
}
|
|
208
|
+
getLastDataPoint() {
|
|
209
|
+
return this.store.last() ?? null;
|
|
210
|
+
}
|
|
211
|
+
getSecondLastDataPoint() {
|
|
212
|
+
const t = this.store.getAll();
|
|
213
|
+
return t.length >= 2 ? t[t.length - 2] : null;
|
|
214
|
+
}
|
|
215
|
+
sampleTimes(t) {
|
|
216
|
+
return this.store.getAll().slice(0, t).map((e) => e.time);
|
|
217
|
+
}
|
|
218
|
+
getVisibleDataPoints(t, e) {
|
|
219
|
+
return this.store.getVisibleData(t, e);
|
|
220
|
+
}
|
|
221
|
+
getValueRange(t, e) {
|
|
222
|
+
const s = this.store.getVisibleData(t, e);
|
|
223
|
+
let i = 1 / 0, r = -1 / 0;
|
|
224
|
+
for (const o of s)
|
|
225
|
+
Number.isFinite(o.low) && o.low < i && (i = o.low), Number.isFinite(o.high) && o.high > r && (r = o.high);
|
|
226
|
+
return i === 1 / 0 || r === -1 / 0 ? null : { min: i, max: r };
|
|
227
|
+
}
|
|
228
|
+
render(t) {
|
|
229
|
+
this.tickAnimations(performance.now());
|
|
230
|
+
const { scope: e, timeScale: s, yScale: i, dataInterval: r } = t, { context: o, horizontalPixelRatio: l } = e, M = s.getRange();
|
|
231
|
+
let h = this.store.getVisibleData(M.from, M.to);
|
|
232
|
+
const d = e.mediaSize.width, k = h.length > d * 2;
|
|
233
|
+
if (k && (h = q(h, Math.round(d * 1.5))), h.length === 0) return;
|
|
234
|
+
const y = this.displayedLast;
|
|
235
|
+
if (!k && y !== null) {
|
|
236
|
+
const p = h.length - 1;
|
|
237
|
+
h[p].time === y.time && (h = [...h.slice(0, p), y]);
|
|
238
|
+
}
|
|
239
|
+
let c = null;
|
|
240
|
+
if (this.entries.size > 0) {
|
|
241
|
+
const p = this.options.entryMs, u = performance.now();
|
|
242
|
+
for (const B of h) {
|
|
243
|
+
const D = this.entries.get(B.time);
|
|
244
|
+
if (D === void 0) continue;
|
|
245
|
+
const W = u - D.startTime, C = p <= 0 ? 1 : Math.min(1, Math.max(0, W / p));
|
|
246
|
+
if (C >= 1) continue;
|
|
247
|
+
const K = j(C);
|
|
248
|
+
c === null && (c = /* @__PURE__ */ new Map()), c.set(B.time, K);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const L = s.barWidthBitmap(r), m = Math.round(30 * l), b = h.length <= 2 ? Math.min(m, L) : L, w = Math.max(1, Math.round(l));
|
|
252
|
+
let n = Math.max(1, Math.round(b * this.options.bodyWidthRatio) - 2);
|
|
253
|
+
(n & 1) !== (w & 1) && (n = n > 1 ? n - 1 : 2);
|
|
254
|
+
const T = Math.floor(n / 2), Y = k !== this.#n;
|
|
255
|
+
this.#n = k;
|
|
256
|
+
const x = Math.round(i.getMediaHeight() * e.verticalPixelRatio);
|
|
257
|
+
this.drawVolume({
|
|
258
|
+
ctx: o,
|
|
259
|
+
data: h,
|
|
260
|
+
timeScale: s,
|
|
261
|
+
chartHeight: x,
|
|
262
|
+
barWidth: b,
|
|
263
|
+
wickWidth: w,
|
|
264
|
+
entranceByTime: c,
|
|
265
|
+
snapVolScale: Y,
|
|
266
|
+
radius: (this.options.cornerRadius ?? O) * l
|
|
267
|
+
});
|
|
268
|
+
const f = [], v = [], g = [];
|
|
269
|
+
let A = null;
|
|
270
|
+
for (let p = 0; p < h.length; p++) {
|
|
271
|
+
const u = h[p];
|
|
272
|
+
if (N(u.open) || N(u.close) || N(u.high) || N(u.low)) {
|
|
273
|
+
g.push(p), A === null && (A = u);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
u.close >= u.open ? f.push(u) : v.push(u);
|
|
277
|
+
}
|
|
278
|
+
A !== null && Z(this, "candlestick", g, `time ${A.time}`);
|
|
279
|
+
const S = {
|
|
280
|
+
painter: et(this.options.candlePainter),
|
|
281
|
+
env: {
|
|
282
|
+
ctx: o,
|
|
283
|
+
theme: t.theme,
|
|
284
|
+
horizontalPixelRatio: l,
|
|
285
|
+
verticalPixelRatio: e.verticalPixelRatio
|
|
286
|
+
},
|
|
287
|
+
radius: (this.options.cornerRadius ?? O) * l
|
|
288
|
+
}, R = {
|
|
289
|
+
ctx: o,
|
|
290
|
+
timeScale: s,
|
|
291
|
+
yScale: i,
|
|
292
|
+
halfBody: T,
|
|
293
|
+
bodyWidth: n,
|
|
294
|
+
wickWidth: w,
|
|
295
|
+
entranceByTime: c,
|
|
296
|
+
candlePass: S
|
|
297
|
+
};
|
|
298
|
+
this.drawCandles({
|
|
299
|
+
...R,
|
|
300
|
+
candles: f,
|
|
301
|
+
body: this.options.up.body,
|
|
302
|
+
wickColor: this.options.up.wick
|
|
303
|
+
}), this.drawCandles({
|
|
304
|
+
...R,
|
|
305
|
+
candles: v,
|
|
306
|
+
body: this.options.down.body,
|
|
307
|
+
wickColor: this.options.down.wick
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
drawVolume({
|
|
311
|
+
ctx: t,
|
|
312
|
+
data: e,
|
|
313
|
+
timeScale: s,
|
|
314
|
+
chartHeight: i,
|
|
315
|
+
barWidth: r,
|
|
316
|
+
wickWidth: o,
|
|
317
|
+
entranceByTime: l,
|
|
318
|
+
snapVolScale: M,
|
|
319
|
+
radius: h
|
|
320
|
+
}) {
|
|
321
|
+
let d = 0;
|
|
322
|
+
for (const n of e)
|
|
323
|
+
Number.isFinite(n.volume) && n.volume > d && (d = n.volume);
|
|
324
|
+
if (d === 0) {
|
|
325
|
+
this.#e = null, this.#o = !0;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
this.#e === null || M ? this.#e = d : this.#e += (d - this.#e) * it, this.#o = Math.abs(d - this.#e) <= d * ot;
|
|
329
|
+
const k = this.#e, y = i * 0.2;
|
|
330
|
+
let c = Math.max(1, r - 2);
|
|
331
|
+
(c & 1) !== (o & 1) && (c = c > 1 ? c - 1 : 2);
|
|
332
|
+
const L = Math.floor(c / 2), m = $(E(this.options.up.body), 0.2), b = $(E(this.options.down.body), 0.2), w = this.options.entryAnimation ?? "unfold";
|
|
333
|
+
for (const n of e) {
|
|
334
|
+
if (!Number.isFinite(n.volume) || n.volume <= 0) continue;
|
|
335
|
+
const T = n.volume, Y = s.timeToBitmapX(n.time), x = Math.min(y, Math.max(1, T / k * y)), f = n.close >= n.open;
|
|
336
|
+
t.fillStyle = f ? m : b;
|
|
337
|
+
const v = l?.get(n.time) ?? 1;
|
|
338
|
+
if (v >= 1 || w === "none") {
|
|
339
|
+
H(t, {
|
|
340
|
+
x: Y - L,
|
|
341
|
+
y: i - x,
|
|
342
|
+
width: c,
|
|
343
|
+
height: x,
|
|
344
|
+
radius: h,
|
|
345
|
+
corners: _
|
|
346
|
+
});
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const g = z(v, w, {
|
|
350
|
+
x: Y - L,
|
|
351
|
+
barWidth: c,
|
|
352
|
+
anchorY: i,
|
|
353
|
+
topY: i - x,
|
|
354
|
+
bottomY: i
|
|
355
|
+
});
|
|
356
|
+
t.save(), t.globalAlpha = g.alpha, H(t, {
|
|
357
|
+
x: g.x,
|
|
358
|
+
y: g.topY,
|
|
359
|
+
width: c,
|
|
360
|
+
height: Math.max(1, g.bottomY - g.topY),
|
|
361
|
+
radius: h,
|
|
362
|
+
corners: _
|
|
363
|
+
}), t.restore();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
drawCandles({
|
|
367
|
+
ctx: t,
|
|
368
|
+
candles: e,
|
|
369
|
+
timeScale: s,
|
|
370
|
+
yScale: i,
|
|
371
|
+
halfBody: r,
|
|
372
|
+
bodyWidth: o,
|
|
373
|
+
wickWidth: l,
|
|
374
|
+
body: M,
|
|
375
|
+
wickColor: h,
|
|
376
|
+
entranceByTime: d,
|
|
377
|
+
candlePass: k
|
|
378
|
+
}) {
|
|
379
|
+
if (e.length === 0) return;
|
|
380
|
+
const y = this.options.entryAnimation ?? "unfold", c = o + 2;
|
|
381
|
+
t.fillStyle = h;
|
|
382
|
+
for (const m of e) {
|
|
383
|
+
const b = d?.get(m.time) ?? 1, w = s.timeToBitmapX(m.time), n = i.valueToBitmapY(m.open), T = i.valueToBitmapY(m.high), Y = i.valueToBitmapY(m.low), x = w - Math.floor(l / 2);
|
|
384
|
+
if (b >= 1 || y === "none") {
|
|
385
|
+
t.fillRect(x, T, l, Y - T);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const f = z(b, y, {
|
|
389
|
+
x,
|
|
390
|
+
barWidth: c,
|
|
391
|
+
anchorY: n,
|
|
392
|
+
topY: T,
|
|
393
|
+
bottomY: Y
|
|
394
|
+
});
|
|
395
|
+
t.save(), t.globalAlpha = f.alpha, t.fillRect(f.x, f.topY, l, Math.max(1, f.bottomY - f.topY)), t.restore();
|
|
396
|
+
}
|
|
397
|
+
const L = Array.isArray(M);
|
|
398
|
+
for (const m of e) {
|
|
399
|
+
const b = d?.get(m.time) ?? 1, w = s.timeToBitmapX(m.time), n = i.valueToBitmapY(m.open), T = i.valueToBitmapY(m.close), Y = Math.min(n, T), x = Math.max(n, T), f = Math.max(1, x - Y), v = b < 1 && y !== "none";
|
|
400
|
+
let g = w - r, A = Y, S = f, R = 1;
|
|
401
|
+
if (v) {
|
|
402
|
+
const C = z(b, y, {
|
|
403
|
+
x: w - r,
|
|
404
|
+
barWidth: c,
|
|
405
|
+
anchorY: n,
|
|
406
|
+
topY: Y,
|
|
407
|
+
bottomY: x
|
|
408
|
+
});
|
|
409
|
+
g = C.x, A = C.topY, S = Math.max(1, C.bottomY - C.topY), R = C.alpha;
|
|
410
|
+
}
|
|
411
|
+
v && t.save();
|
|
412
|
+
const p = Array.isArray(M) ? M[0] : M, u = L && S > 2, B = u ? this.#r(t, M, S) : p;
|
|
413
|
+
t.fillStyle = B, v && (t.globalAlpha = R), u && t.translate(0, A);
|
|
414
|
+
const D = u ? 0 : A, W = Q(k.radius, 0, Math.min(o, S) / 2);
|
|
415
|
+
W < 1 && !this.options.candlePainter ? t.fillRect(g, D, o, S) : k.painter(k.env, {
|
|
416
|
+
geom: { x: g, y: D, width: o, height: S },
|
|
417
|
+
fillStyle: B,
|
|
418
|
+
color: p,
|
|
419
|
+
corners: nt,
|
|
420
|
+
radius: W,
|
|
421
|
+
progress: b,
|
|
422
|
+
isBullish: m.close >= m.open
|
|
423
|
+
}), u && t.translate(0, -A), v && t.restore();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Cached 2-stop body gradient anchored at y = 0..height. Heights quantize
|
|
428
|
+
* to whole device px (sub-pixel ramp drift is invisible) so same-height
|
|
429
|
+
* bodies share one `CanvasGradient` instead of allocating per candle per
|
|
430
|
+
* frame. The map is hard-bounded — entrance animations sweep heights
|
|
431
|
+
* continuously, and a full clear is cheaper than an eviction policy.
|
|
432
|
+
*/
|
|
433
|
+
#r(t, e, s) {
|
|
434
|
+
const i = Math.max(3, Math.round(s)), r = `${e[0]}|${e[1]}|${i}`, o = this.#s.get(r);
|
|
435
|
+
if (o !== void 0) return o;
|
|
436
|
+
this.#s.size >= 512 && this.#s.clear();
|
|
437
|
+
const l = t.createLinearGradient(0, 0, 0, i);
|
|
438
|
+
return l.addColorStop(0, e[0]), l.addColorStop(1, e[1]), this.#s.set(r, l), l;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function z(a, t, e) {
|
|
442
|
+
switch (t) {
|
|
443
|
+
case "none":
|
|
444
|
+
return { x: e.x, topY: e.topY, bottomY: e.bottomY, alpha: 1 };
|
|
445
|
+
case "fade":
|
|
446
|
+
return { x: e.x, topY: e.topY, bottomY: e.bottomY, alpha: a };
|
|
447
|
+
case "unfold":
|
|
448
|
+
return {
|
|
449
|
+
x: e.x,
|
|
450
|
+
topY: V(e.anchorY, e.topY, a),
|
|
451
|
+
bottomY: V(e.anchorY, e.bottomY, a),
|
|
452
|
+
alpha: 1
|
|
453
|
+
};
|
|
454
|
+
case "slide":
|
|
455
|
+
return {
|
|
456
|
+
x: e.x + (1 - a) * e.barWidth,
|
|
457
|
+
topY: e.topY,
|
|
458
|
+
bottomY: e.bottomY,
|
|
459
|
+
alpha: a
|
|
460
|
+
};
|
|
461
|
+
case "fade-unfold":
|
|
462
|
+
return {
|
|
463
|
+
x: e.x,
|
|
464
|
+
topY: V(e.anchorY, e.topY, a),
|
|
465
|
+
bottomY: V(e.anchorY, e.bottomY, a),
|
|
466
|
+
alpha: a
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const xt = {
|
|
471
|
+
type: "candlestick",
|
|
472
|
+
create: ({ theme: a }, t) => new at(new J(), {
|
|
473
|
+
up: { ...a.candlestick.up },
|
|
474
|
+
down: { ...a.candlestick.down },
|
|
475
|
+
...t
|
|
476
|
+
})
|
|
477
|
+
};
|
|
478
|
+
export {
|
|
479
|
+
at as CandlestickRenderer,
|
|
480
|
+
xt as CandlestickSeriesDef
|
|
481
|
+
};
|