@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,110 @@
|
|
|
1
|
+
import { DEFAULT_TICKS_MS as a } from "../animation/config.js";
|
|
2
|
+
import { Animator as h } from "../animation/animator.js";
|
|
3
|
+
const u = { entries: [], isAnimating: !1 }, c = (s, t, e) => s + (t - s) * e;
|
|
4
|
+
function f(s, t) {
|
|
5
|
+
if (s === t) return !0;
|
|
6
|
+
if (s.length !== t.length) return !1;
|
|
7
|
+
for (let e = 0; e < s.length; e++)
|
|
8
|
+
if (s[e] !== t[e]) return !1;
|
|
9
|
+
return !0;
|
|
10
|
+
}
|
|
11
|
+
class m {
|
|
12
|
+
#e = [];
|
|
13
|
+
#r = [];
|
|
14
|
+
#t = /* @__PURE__ */ new Map();
|
|
15
|
+
#i;
|
|
16
|
+
/**
|
|
17
|
+
* `false` during the initial mount phase: `setCurrentTicks` snaps each
|
|
18
|
+
* entering/exiting animator to its target (duration `0`) so the very
|
|
19
|
+
* first niceTickValues sets land at full opacity with no fade. The
|
|
20
|
+
* chart's renderMain flips this once the chart has reached a settled
|
|
21
|
+
* frame.
|
|
22
|
+
*/
|
|
23
|
+
#s = !1;
|
|
24
|
+
constructor(t = {}) {
|
|
25
|
+
this.#i = t.fadeMs ?? a;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Record the latest tick set. Diffs against the current set and either
|
|
29
|
+
* starts a `0 → 1` fade-in for new values or a `1 → 0` fade-out for
|
|
30
|
+
* dropped ones. Idempotent on element-wise equal arrays.
|
|
31
|
+
*
|
|
32
|
+
* Callers must invoke {@link tick} every frame to advance the animators —
|
|
33
|
+
* `snapshot()` reads the current state but does not advance time.
|
|
34
|
+
*/
|
|
35
|
+
setCurrentTicks(t) {
|
|
36
|
+
if (f(t, this.#e)) return;
|
|
37
|
+
const e = this.#s ? this.#i : 0, o = new Set(t), r = new Set(this.#e);
|
|
38
|
+
for (const i of t) {
|
|
39
|
+
if (r.has(i)) continue;
|
|
40
|
+
let n = this.#t.get(i);
|
|
41
|
+
n === void 0 && (n = new h({ initial: 0, duration: this.#i, lerp: c }), this.#t.set(i, n)), n.setTarget(1, { duration: e });
|
|
42
|
+
}
|
|
43
|
+
for (const i of this.#e) {
|
|
44
|
+
if (o.has(i)) continue;
|
|
45
|
+
const n = this.#t.get(i);
|
|
46
|
+
n !== void 0 && n.setTarget(0, { duration: e });
|
|
47
|
+
}
|
|
48
|
+
this.#r = this.#e, this.#e = t.slice();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Advance each tracked animator against `now`. Animators that settle
|
|
52
|
+
* at zero are dropped from the map; non-zero settled animators stay so
|
|
53
|
+
* a subsequent re-entering tick can pick them up without a flicker.
|
|
54
|
+
* Returns `true` while any animator is still in flight.
|
|
55
|
+
*/
|
|
56
|
+
tick(t) {
|
|
57
|
+
let e = !1;
|
|
58
|
+
for (const [o, r] of this.#t) {
|
|
59
|
+
if (r.tick(t)) {
|
|
60
|
+
e = !0;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
r.current === 0 && this.#t.delete(o);
|
|
64
|
+
}
|
|
65
|
+
return e;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build a renderer-ready snapshot from the tracker's own animator state.
|
|
69
|
+
* No external opacity map is consulted — `setCurrentTicks` + `tick(now)`
|
|
70
|
+
* are the only inputs.
|
|
71
|
+
*/
|
|
72
|
+
snapshot() {
|
|
73
|
+
if (this.#t.size === 0) return u;
|
|
74
|
+
const t = [];
|
|
75
|
+
let e = !1;
|
|
76
|
+
for (const [o, r] of this.#t) {
|
|
77
|
+
const i = r.current;
|
|
78
|
+
i <= 0 || (t.push({ value: o, opacity: i }), r.animating && (e = !0));
|
|
79
|
+
}
|
|
80
|
+
return { entries: t, isAnimating: e };
|
|
81
|
+
}
|
|
82
|
+
getCurrentTicks() {
|
|
83
|
+
return this.#e;
|
|
84
|
+
}
|
|
85
|
+
getPreviousTicks() {
|
|
86
|
+
return this.#r;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Reconfigure the cross-fade duration. Chart calls this after resolving
|
|
90
|
+
* `animations.axis.ticks` so the tracker honors the user's config
|
|
91
|
+
* without plumbing options through the scale constructors.
|
|
92
|
+
*/
|
|
93
|
+
setFadeMs(t) {
|
|
94
|
+
this.#i = t;
|
|
95
|
+
}
|
|
96
|
+
/** Whether subsequent tick-set changes should fade-in or snap. */
|
|
97
|
+
markArmed() {
|
|
98
|
+
this.#s = !0;
|
|
99
|
+
}
|
|
100
|
+
get isArmed() {
|
|
101
|
+
return this.#s;
|
|
102
|
+
}
|
|
103
|
+
/** Drop all tracked ticks. Use after dataset swap so stale values don't linger. */
|
|
104
|
+
reset() {
|
|
105
|
+
this.#e = [], this.#r = [], this.#t.clear(), this.#s = !1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
m as AxisTickTracker
|
|
110
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { niceTimeIntervals as u } from "../utils/time.js";
|
|
2
|
+
import { AxisTickTracker as m } from "./tick-tracker.js";
|
|
3
|
+
const c = 50, f = 80;
|
|
4
|
+
class p {
|
|
5
|
+
/**
|
|
6
|
+
* Shared fade state for time ticks. Grid lines (canvas) and DOM tick labels
|
|
7
|
+
* read opacity from the same tracker, so a tick fading in/out looks
|
|
8
|
+
* identical on both surfaces.
|
|
9
|
+
*/
|
|
10
|
+
tickTracker = new m();
|
|
11
|
+
from = 0;
|
|
12
|
+
to = 0;
|
|
13
|
+
width = 1;
|
|
14
|
+
pixelRatio = 1;
|
|
15
|
+
dataInterval = null;
|
|
16
|
+
// Label density knobs — written by <TimeAxis labelCount=… minLabelSpacing=…>.
|
|
17
|
+
labelCountHintValue = null;
|
|
18
|
+
minSpacingValue = null;
|
|
19
|
+
resolvedInterval = null;
|
|
20
|
+
lastInterval = null;
|
|
21
|
+
/**
|
|
22
|
+
* "want" (desired interval post-floor) at the time lastInterval was snapped.
|
|
23
|
+
* Hysteresis band anchors to this so micro range drift back across a tier
|
|
24
|
+
* boundary doesn't flip the label density — mirrors YScale, see there for
|
|
25
|
+
* the full reasoning.
|
|
26
|
+
*/
|
|
27
|
+
lastWant = null;
|
|
28
|
+
/** Identifies the dataInterval bucket whose tier list drove lastInterval. */
|
|
29
|
+
lastBucketKey = null;
|
|
30
|
+
get labelCountHint() {
|
|
31
|
+
return this.labelCountHintValue;
|
|
32
|
+
}
|
|
33
|
+
get minLabelSpacing() {
|
|
34
|
+
return this.minSpacingValue ?? f;
|
|
35
|
+
}
|
|
36
|
+
update(t, s, a, e) {
|
|
37
|
+
if (this.from = t.from, this.to = t.to, this.width = s, this.pixelRatio = a, e !== void 0 && e > 0) {
|
|
38
|
+
const n = h(e);
|
|
39
|
+
this.lastBucketKey !== null && this.lastBucketKey !== n && this.resetHysteresis(), this.lastBucketKey = n, this.dataInterval = e;
|
|
40
|
+
}
|
|
41
|
+
this.resolveInterval();
|
|
42
|
+
}
|
|
43
|
+
setLabelCount(t) {
|
|
44
|
+
this.labelCountHintValue = v(t), this.resetHysteresis(), this.resolveInterval();
|
|
45
|
+
}
|
|
46
|
+
setMinSpacing(t) {
|
|
47
|
+
this.minSpacingValue = d(t), this.resetHysteresis(), this.resolveInterval();
|
|
48
|
+
}
|
|
49
|
+
resetHysteresis() {
|
|
50
|
+
this.lastInterval = null, this.lastWant = null;
|
|
51
|
+
}
|
|
52
|
+
timeToX(t) {
|
|
53
|
+
return this.to <= this.from ? 0 : (t - this.from) / (this.to - this.from) * this.width;
|
|
54
|
+
}
|
|
55
|
+
timeToBitmapX(t) {
|
|
56
|
+
return Math.round(this.timeToX(t) * this.pixelRatio);
|
|
57
|
+
}
|
|
58
|
+
xToTime(t) {
|
|
59
|
+
return this.to <= this.from ? this.from : this.from + t / this.width * (this.to - this.from);
|
|
60
|
+
}
|
|
61
|
+
pixelDeltaToTimeDelta(t) {
|
|
62
|
+
return this.to <= this.from ? 0 : t / this.width * (this.to - this.from);
|
|
63
|
+
}
|
|
64
|
+
barWidthMedia(t) {
|
|
65
|
+
return this.to <= this.from ? 0 : t / (this.to - this.from) * this.width;
|
|
66
|
+
}
|
|
67
|
+
barWidthBitmap(t) {
|
|
68
|
+
return Math.max(1, Math.round(this.barWidthMedia(t) * this.pixelRatio));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Evenly spaced "nice" tick times. Resolution uses the cached
|
|
72
|
+
* `dataInterval` set by `update()`; callers that bypass `update()` can
|
|
73
|
+
* still pass it here for back-compat.
|
|
74
|
+
*/
|
|
75
|
+
niceTickValues(t) {
|
|
76
|
+
if (this.to <= this.from) return { ticks: [], tickInterval: 0 };
|
|
77
|
+
if (this.dataInterval !== t) {
|
|
78
|
+
const r = h(t);
|
|
79
|
+
this.lastBucketKey !== null && this.lastBucketKey !== r && this.resetHysteresis(), this.lastBucketKey = r, this.dataInterval = t, this.resolveInterval();
|
|
80
|
+
}
|
|
81
|
+
if (this.resolvedInterval == null) return { ticks: [], tickInterval: 0 };
|
|
82
|
+
const s = this.resolvedInterval, a = Math.ceil(this.from / s) * s, e = Math.max(0, Math.min(c, Math.floor((this.to - a) / s) + 1)), n = [];
|
|
83
|
+
for (let r = 0; r < e; r++) n.push(a + r * s);
|
|
84
|
+
return { ticks: n, tickInterval: s };
|
|
85
|
+
}
|
|
86
|
+
getRange() {
|
|
87
|
+
return { from: this.from, to: this.to };
|
|
88
|
+
}
|
|
89
|
+
getMediaWidth() {
|
|
90
|
+
return this.width;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the next tick interval. Walks `niceTimeIntervals(dataInterval)`
|
|
94
|
+
* looking for the smallest tier ≥ desired spacing; retains the previous
|
|
95
|
+
* tier inside a ratio band so micro pan/zoom doesn't flip label density.
|
|
96
|
+
*/
|
|
97
|
+
resolveInterval() {
|
|
98
|
+
if (this.to <= this.from || this.width <= 0 || this.dataInterval == null || this.dataInterval <= 0) {
|
|
99
|
+
this.resolvedInterval = null;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const t = u(this.dataInterval), s = this.to - this.from, a = s / this.width * this.minLabelSpacing;
|
|
103
|
+
let e;
|
|
104
|
+
if (this.labelCountHint != null) {
|
|
105
|
+
const l = Math.max(1, this.labelCountHint);
|
|
106
|
+
e = Math.max(s / l, a);
|
|
107
|
+
} else
|
|
108
|
+
e = a;
|
|
109
|
+
if (this.lastInterval != null && this.lastWant != null && t.includes(this.lastInterval)) {
|
|
110
|
+
const l = e >= this.lastWant * 0.8 && e <= this.lastWant * 1.25, o = this.lastInterval >= a;
|
|
111
|
+
if (l && o) {
|
|
112
|
+
this.resolvedInterval = this.lastInterval;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let n = t[t.length - 1], r = t.length - 1;
|
|
117
|
+
for (let l = 0; l < t.length; l++)
|
|
118
|
+
if (t[l] >= e) {
|
|
119
|
+
n = t[l], r = l;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (this.labelCountHint != null && r > 0 && this.countTicks(n) < this.labelCountHint) {
|
|
123
|
+
const l = t[r - 1];
|
|
124
|
+
l >= a && (n = l);
|
|
125
|
+
}
|
|
126
|
+
this.resolvedInterval = n, this.lastInterval = n, this.lastWant = e;
|
|
127
|
+
}
|
|
128
|
+
countTicks(t) {
|
|
129
|
+
if (!(t > 0)) return 0;
|
|
130
|
+
const s = Math.ceil(this.from / t) * t;
|
|
131
|
+
return Math.max(0, Math.floor((this.to - s) / t) + 1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function h(i) {
|
|
135
|
+
return i < 6e4 ? 0 : i < 36e5 ? 1 : i < 864e5 ? 2 : 3;
|
|
136
|
+
}
|
|
137
|
+
function v(i) {
|
|
138
|
+
return typeof i == "number" && Number.isFinite(i) && i >= 2 ? Math.floor(i) : null;
|
|
139
|
+
}
|
|
140
|
+
function d(i) {
|
|
141
|
+
return typeof i == "number" && Number.isFinite(i) && i > 0 ? i : null;
|
|
142
|
+
}
|
|
143
|
+
export {
|
|
144
|
+
p as XScale
|
|
145
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { formatCompact as h } from "../utils/format.js";
|
|
2
|
+
import { AxisTickTracker as o } from "./tick-tracker.js";
|
|
3
|
+
function u(n) {
|
|
4
|
+
if (!Number.isFinite(n) || n <= 0) return 1;
|
|
5
|
+
const t = 10 ** Math.floor(Math.log10(n)), i = n / t;
|
|
6
|
+
return (i <= 1 ? 1 : i <= 2 ? 2 : i <= 5 ? 5 : 10) * t;
|
|
7
|
+
}
|
|
8
|
+
function m(n) {
|
|
9
|
+
if (!Number.isFinite(n) || n <= 0) return 1;
|
|
10
|
+
const t = 10 ** Math.floor(Math.log10(n)), i = n / t;
|
|
11
|
+
return (i >= 5 ? 5 : i >= 2 ? 2 : 1) * t;
|
|
12
|
+
}
|
|
13
|
+
const c = 50, v = 50;
|
|
14
|
+
class M {
|
|
15
|
+
/** Shared fade state for Y ticks; see {@link TimeScale.tickTracker}. */
|
|
16
|
+
tickTracker = new o();
|
|
17
|
+
min = 0;
|
|
18
|
+
max = 0;
|
|
19
|
+
height = 1;
|
|
20
|
+
pixelRatio = 1;
|
|
21
|
+
// Label density knobs — written by <YAxis labelCount=… minLabelSpacing=…>.
|
|
22
|
+
labelCountHintValue = null;
|
|
23
|
+
minSpacingValue = null;
|
|
24
|
+
/** Cached {1,2,5}×10^k interval resolved at update-time; null when degenerate. */
|
|
25
|
+
resolvedInterval = null;
|
|
26
|
+
/** Previous resolved interval — drives the ratio-band hysteresis. */
|
|
27
|
+
lastInterval = null;
|
|
28
|
+
/**
|
|
29
|
+
* rawInterval at the moment `lastInterval` was snapped. Anchoring the
|
|
30
|
+
* hysteresis band to *raw-at-snap* (not `lastInterval`) is what creates
|
|
31
|
+
* naturally asymmetric tier hysteresis: after an escalation the band
|
|
32
|
+
* starts just above the tier boundary, so live range drift back across
|
|
33
|
+
* the boundary doesn't instantly flip back.
|
|
34
|
+
*/
|
|
35
|
+
lastRawInterval = null;
|
|
36
|
+
/** Custom formatter (driven by `<YAxis format=…>`). */
|
|
37
|
+
customFormat = null;
|
|
38
|
+
get labelCountHint() {
|
|
39
|
+
return this.labelCountHintValue;
|
|
40
|
+
}
|
|
41
|
+
get minLabelSpacing() {
|
|
42
|
+
return this.minSpacingValue ?? v;
|
|
43
|
+
}
|
|
44
|
+
/** Recalculate the scale with a new Y range, chart height, and device pixel ratio. */
|
|
45
|
+
update(t, i, a) {
|
|
46
|
+
this.min = t.min, this.max = t.max, this.height = i, this.pixelRatio = a, this.resolveInterval();
|
|
47
|
+
}
|
|
48
|
+
/** Desired label count. Invalid values (NaN, <2, Infinity) clear the hint. */
|
|
49
|
+
setLabelCount(t) {
|
|
50
|
+
this.labelCountHintValue = f(t), this.resetHysteresis(), this.resolveInterval();
|
|
51
|
+
}
|
|
52
|
+
/** Minimum pixel gap between adjacent labels. */
|
|
53
|
+
setMinSpacing(t) {
|
|
54
|
+
this.minSpacingValue = g(t), this.resetHysteresis(), this.resolveInterval();
|
|
55
|
+
}
|
|
56
|
+
resetHysteresis() {
|
|
57
|
+
this.lastInterval = null, this.lastRawInterval = null;
|
|
58
|
+
}
|
|
59
|
+
/** Install (or clear) a custom formatter. */
|
|
60
|
+
setFormat(t) {
|
|
61
|
+
this.customFormat = t;
|
|
62
|
+
}
|
|
63
|
+
/** Read back the currently installed formatter — null when the built-in is active. */
|
|
64
|
+
getFormat() {
|
|
65
|
+
return this.customFormat;
|
|
66
|
+
}
|
|
67
|
+
/** Convert a value to a Y position in CSS (media) pixels. */
|
|
68
|
+
valueToY(t) {
|
|
69
|
+
const i = this.max - this.min;
|
|
70
|
+
return i === 0 ? this.height / 2 : (1 - (t - this.min) / i) * this.height;
|
|
71
|
+
}
|
|
72
|
+
/** Convert a value to a Y position in physical (bitmap) pixels. */
|
|
73
|
+
valueToBitmapY(t) {
|
|
74
|
+
return Math.round(this.valueToY(t) * this.pixelRatio);
|
|
75
|
+
}
|
|
76
|
+
/** Convert a Y position in CSS pixels back to a value. */
|
|
77
|
+
yToValue(t) {
|
|
78
|
+
const i = this.max - this.min;
|
|
79
|
+
return i === 0 ? this.min : this.max - t / this.height * i;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generate evenly spaced "nice" tick values for the current range and
|
|
83
|
+
* chart height. Pure read — resolution happens in `update()`.
|
|
84
|
+
*/
|
|
85
|
+
niceTickValues() {
|
|
86
|
+
if (this.resolvedInterval == null) return [];
|
|
87
|
+
const t = this.resolvedInterval, i = Math.ceil(this.min / t) * t, a = Math.max(0, Math.min(c, Math.floor((this.max - i) / t) + 1)), e = [];
|
|
88
|
+
for (let s = 0; s < a; s++) e.push(i + s * t);
|
|
89
|
+
return e;
|
|
90
|
+
}
|
|
91
|
+
getRange() {
|
|
92
|
+
return { min: this.min, max: this.max };
|
|
93
|
+
}
|
|
94
|
+
getMediaHeight() {
|
|
95
|
+
return this.height;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Format a tick value for display. When a custom formatter is installed
|
|
99
|
+
* (via {@link setFormat} or the `format` prop on `<YAxis>`), it wins;
|
|
100
|
+
* otherwise decimals are driven by the resolved tick interval — so
|
|
101
|
+
* ticks at step 100 never render as "42000.0" just because the visible
|
|
102
|
+
* range happens to be < 1000.
|
|
103
|
+
*/
|
|
104
|
+
formatY(t) {
|
|
105
|
+
if (this.customFormat) return this.customFormat(t);
|
|
106
|
+
if (this.max - this.min >= 1e6) return h(t);
|
|
107
|
+
const i = this.resolvedInterval, a = i != null && i > 0 ? Math.max(0, -Math.floor(Math.log10(i))) : 1;
|
|
108
|
+
return t.toFixed(a);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolve the interval to a {1,2,5}×10^k tick size that satisfies the pixel
|
|
112
|
+
* floor and (optionally) targets `labelCountHint` labels.
|
|
113
|
+
*
|
|
114
|
+
* Hysteresis: the band [0.8×, 1.25×] is anchored to **rawInterval at last
|
|
115
|
+
* snap**, not to `lastInterval`. After an escalation (e.g. 100 → 200),
|
|
116
|
+
* `lastRawInterval` sits just past the tier boundary, so typical live
|
|
117
|
+
* range drift back across the boundary stays inside the band and does
|
|
118
|
+
* *not* de-escalate. This prevents the 2-tier flicker users see when new
|
|
119
|
+
* candles nudge the Y range across a raw-interval threshold.
|
|
120
|
+
*/
|
|
121
|
+
resolveInterval() {
|
|
122
|
+
if (this.max <= this.min || this.height <= 0) {
|
|
123
|
+
this.resolvedInterval = null;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const t = this.max - this.min, i = t * this.minLabelSpacing / this.height, a = this.labelCountHint != null ? Math.max(1, this.labelCountHint) : Math.max(1, Math.floor(this.height / this.minLabelSpacing));
|
|
127
|
+
let e = t / a;
|
|
128
|
+
if (e < i && (e = i), this.lastInterval != null && this.lastRawInterval != null) {
|
|
129
|
+
const l = e >= this.lastRawInterval * 0.8 && e <= this.lastRawInterval * 1.25, r = this.lastInterval >= i;
|
|
130
|
+
if (l && r) {
|
|
131
|
+
this.resolvedInterval = this.lastInterval;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let s = u(e);
|
|
136
|
+
if (this.labelCountHint != null && this.countTicks(s) < this.labelCountHint) {
|
|
137
|
+
const l = m(e);
|
|
138
|
+
l >= i && l > 0 && (s = l);
|
|
139
|
+
}
|
|
140
|
+
this.resolvedInterval = s, this.lastInterval = s, this.lastRawInterval = e;
|
|
141
|
+
}
|
|
142
|
+
countTicks(t) {
|
|
143
|
+
if (!(t > 0)) return 0;
|
|
144
|
+
const i = Math.ceil(this.min / t) * t;
|
|
145
|
+
return Math.max(0, Math.floor((this.max - i) / t) + 1);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function f(n) {
|
|
149
|
+
return typeof n == "number" && Number.isFinite(n) && n >= 2 ? Math.floor(n) : null;
|
|
150
|
+
}
|
|
151
|
+
function g(n) {
|
|
152
|
+
return typeof n == "number" && Number.isFinite(n) && n > 0 ? n : null;
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
M as YScale
|
|
156
|
+
};
|