@wick-charts/vue 0.6.3 → 0.6.4
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 +2 -2
- package/dist/BandSeries.vue.js +50 -0
- package/dist/BandSeries.vue2.js +4 -0
- package/dist/ChartContainer.vue.js +16 -16
- package/dist/LineSeries.vue.js +2 -2
- package/dist/core/dist/animation/config.js +92 -75
- package/dist/core/dist/chart/badge-animator.js +30 -22
- package/dist/core/dist/chart/edge-indicators.js +22 -21
- package/dist/core/dist/chart/last-value.js +33 -26
- package/dist/core/dist/chart.js +168 -167
- package/dist/core/dist/data/decimation.js +58 -39
- package/dist/core/dist/navigator/controller.js +10 -10
- package/dist/core/dist/perf/perf-hud.js +6 -6
- package/dist/core/dist/series/band.js +670 -0
- package/dist/core/dist/series/bar.js +14 -14
- package/dist/core/dist/series/candlestick.js +31 -31
- package/dist/core/dist/series/heatmap.js +5 -5
- package/dist/core/dist/series/line-intro.js +47 -37
- package/dist/core/dist/series/line.js +485 -445
- package/dist/core/dist/series/painters/builtins.js +4 -4
- package/dist/core/dist/series/painters/canvas-path.js +64 -52
- package/dist/core/dist/series/register-builtins.js +9 -8
- package/dist/core/dist/snapshots.js +36 -35
- package/dist/core/dist/utils/point.js +10 -0
- package/dist/index.d.ts +255 -97
- package/dist/index.js +195 -191
- package/dist/ui/InfoBar.vue.js +100 -81
- package/dist/ui/Tooltip.vue.js +118 -91
- package/dist/ui/YLabel.vue.js +62 -69
- package/package.json +2 -2
- package/src/BandSeries.vue +87 -0
- package/src/index.ts +8 -0
- package/src/ui/InfoBar.vue +20 -1
- package/src/ui/Tooltip.vue +36 -2
- package/src/ui/YLabel.vue +2 -13
package/dist/ui/YLabel.vue.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
import { prefersReducedMotion as
|
|
3
|
-
import { resolveAnimate as X, firstVisibleValue as Z, BadgeAnimator as ee } from "../core/dist/chart/badge-animator.js";
|
|
1
|
+
import { defineComponent as $, useSlots as j, computed as a, ref as I, onMounted as G, onUnmounted as O, openBlock as w, createElementBlock as A, Fragment as B, renderSlot as W, createElementVNode as _, normalizeStyle as Y, unref as g, createVNode as H, createCommentVNode as J } from "vue";
|
|
2
|
+
import { prefersReducedMotion as K } from "../core/dist/animation/reduced-motion.js";
|
|
3
|
+
import { resolveAnimate as Q, resolveYLabelSeriesId as X, firstVisibleValue as Z, BadgeAnimator as ee } from "../core/dist/chart/badge-animator.js";
|
|
4
4
|
import { useChartInstance as te } from "../context.js";
|
|
5
|
-
import
|
|
6
|
-
const ie = /* @__PURE__ */
|
|
5
|
+
import ae from "./NumberFlow.vue.js";
|
|
6
|
+
const ie = /* @__PURE__ */ $({
|
|
7
7
|
__name: "YLabel",
|
|
8
8
|
props: {
|
|
9
9
|
seriesId: {},
|
|
@@ -11,55 +11,48 @@ const ie = /* @__PURE__ */ q({
|
|
|
11
11
|
format: { type: Function },
|
|
12
12
|
animate: { type: [Boolean, Object] }
|
|
13
13
|
},
|
|
14
|
-
setup(
|
|
15
|
-
const
|
|
16
|
-
let
|
|
17
|
-
const N =
|
|
18
|
-
function
|
|
14
|
+
setup(z) {
|
|
15
|
+
const c = z, e = te(), M = j(), E = a(() => typeof M.default == "function"), i = I(0), o = I(null);
|
|
16
|
+
let p = null, y = null, u = null;
|
|
17
|
+
const N = a(() => Q(c.animate));
|
|
18
|
+
function L() {
|
|
19
19
|
u = null;
|
|
20
|
-
const t = s.value,
|
|
21
|
-
if (t === null || !
|
|
22
|
-
|
|
20
|
+
const t = s.value, n = t !== null ? e.getStackedLastValue(t) : null;
|
|
21
|
+
if (t === null || !n) {
|
|
22
|
+
p = null, y = null;
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
const
|
|
26
|
-
(
|
|
27
|
-
const { glide:
|
|
28
|
-
target:
|
|
25
|
+
const m = n.value;
|
|
26
|
+
(p === null || y !== t) && (p = new ee(m), y = t);
|
|
27
|
+
const { glide: P, countUp: U, settleMs: q } = N.value, S = e.getIntroFront(t), v = p.frame({
|
|
28
|
+
target: m,
|
|
29
29
|
// Only needed for the intro count-up; skip the visible-range binary search
|
|
30
30
|
// entirely once the reveal has settled (the hot streaming path).
|
|
31
|
-
firstVisible:
|
|
32
|
-
introFront:
|
|
31
|
+
firstVisible: S < 1 ? Z(e, t, m) : m,
|
|
32
|
+
introFront: S,
|
|
33
33
|
now: performance.now(),
|
|
34
|
-
glide:
|
|
34
|
+
glide: P,
|
|
35
35
|
countUp: U,
|
|
36
|
-
settleMs:
|
|
37
|
-
reducedMotion:
|
|
38
|
-
}),
|
|
39
|
-
(
|
|
36
|
+
settleMs: q,
|
|
37
|
+
reducedMotion: K()
|
|
38
|
+
}), d = o.value;
|
|
39
|
+
(d === null || d.positionValue !== v.positionValue || d.textValue !== v.textValue || d.opacity !== v.opacity) && (o.value = { positionValue: v.positionValue, textValue: v.textValue, opacity: v.opacity }), v.animating && u === null && (u = requestAnimationFrame(L));
|
|
40
40
|
}
|
|
41
|
-
function
|
|
42
|
-
u === null && (u = requestAnimationFrame(
|
|
41
|
+
function R() {
|
|
42
|
+
u === null && (u = requestAnimationFrame(L));
|
|
43
43
|
}
|
|
44
|
-
const
|
|
45
|
-
i.value++,
|
|
44
|
+
const f = () => {
|
|
45
|
+
i.value++, R();
|
|
46
46
|
};
|
|
47
47
|
G(() => {
|
|
48
|
-
e.setYLabel(!0), e.on("overlayChange",
|
|
49
|
-
}),
|
|
50
|
-
e.setYLabel(!1), e.off("overlayChange",
|
|
48
|
+
e.setYLabel(!0), e.on("overlayChange", f), e.on("viewportChange", f), e.getSeriesIds().length > 0 && f();
|
|
49
|
+
}), O(() => {
|
|
50
|
+
e.setYLabel(!1), e.off("overlayChange", f), e.off("viewportChange", f), u !== null && cancelAnimationFrame(u), u = null;
|
|
51
51
|
});
|
|
52
|
-
|
|
53
|
-
if (l
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
const d = t.getSeriesIdsByType("time", { visibleOnly: !0 });
|
|
57
|
-
return d.length > 0 ? d[0] : null;
|
|
58
|
-
}
|
|
59
|
-
const s = n(() => (i.value, R(e, f.seriesId))), a = n(() => (i.value, s.value !== null ? e.getStackedLastValue(s.value) : null)), b = n(() => (i.value, s.value !== null ? e.getPreviousClose(s.value) : null)), r = n(() => (i.value, e.getTheme())), D = n(() => o.value !== null ? o.value.positionValue : a.value?.value ?? 0), P = n(() => o.value !== null ? o.value.textValue : a.value?.value ?? 0), h = n(() => o.value !== null ? o.value.opacity : s.value === null ? 1 : e.getIntroFront(s.value) < 1 ? 0 : 1), V = n(() => (i.value, a.value === null ? 0 : e.yScale.valueToY(D.value))), k = n(() => a.value === null || b.value === null ? "neutral" : a.value.value > b.value ? "up" : a.value.value < b.value ? "down" : "neutral"), L = n(() => {
|
|
60
|
-
if (a.value === null) return r.value.yLabel.neutralBackground;
|
|
61
|
-
if (!a.value.isLive) return r.value.axis.textColor;
|
|
62
|
-
if (f.color) return f.color;
|
|
52
|
+
const s = a(() => (i.value, X(e, c.seriesId))), l = a(() => (i.value, s.value !== null ? e.getStackedLastValue(s.value) : null)), x = a(() => (i.value, s.value !== null ? e.getPreviousClose(s.value) : null)), r = a(() => (i.value, e.getTheme())), T = a(() => o.value !== null ? o.value.positionValue : l.value?.value ?? 0), D = a(() => o.value !== null ? o.value.textValue : l.value?.value ?? 0), b = a(() => o.value !== null ? o.value.opacity : s.value === null ? 1 : e.getIntroFront(s.value) < 1 ? 0 : 1), h = a(() => (i.value, l.value === null ? 0 : e.yScale.valueToY(T.value))), k = a(() => l.value === null || x.value === null ? "neutral" : l.value.value > x.value ? "up" : l.value.value < x.value ? "down" : "neutral"), V = a(() => {
|
|
53
|
+
if (l.value === null) return r.value.yLabel.neutralBackground;
|
|
54
|
+
if (!l.value.isLive) return r.value.axis.textColor;
|
|
55
|
+
if (c.color) return c.color;
|
|
63
56
|
switch (k.value) {
|
|
64
57
|
case "up":
|
|
65
58
|
return r.value.yLabel.upBackground;
|
|
@@ -68,53 +61,53 @@ const ie = /* @__PURE__ */ q({
|
|
|
68
61
|
default:
|
|
69
62
|
return r.value.yLabel.neutralBackground;
|
|
70
63
|
}
|
|
71
|
-
}), C =
|
|
64
|
+
}), C = a(() => {
|
|
72
65
|
i.value;
|
|
73
|
-
const t = e.yScale.getRange(),
|
|
74
|
-
return
|
|
75
|
-
}), F =
|
|
76
|
-
if (
|
|
66
|
+
const t = e.yScale.getRange(), n = t.max - t.min;
|
|
67
|
+
return n < 0.1 ? 6 : n < 10 ? 4 : n < 1e3 ? 2 : 0;
|
|
68
|
+
}), F = a(() => {
|
|
69
|
+
if (c.format) return c.format;
|
|
77
70
|
const t = new Intl.NumberFormat("en-US", {
|
|
78
71
|
minimumFractionDigits: C.value,
|
|
79
72
|
maximumFractionDigits: C.value,
|
|
80
73
|
useGrouping: !1
|
|
81
74
|
});
|
|
82
|
-
return (
|
|
75
|
+
return (n) => t.format(n);
|
|
83
76
|
});
|
|
84
|
-
return (t,
|
|
85
|
-
|
|
77
|
+
return (t, n) => l.value !== null ? (w(), A(B, { key: 0 }, [
|
|
78
|
+
E.value ? W(t.$slots, "default", {
|
|
86
79
|
key: 0,
|
|
87
|
-
value:
|
|
88
|
-
y:
|
|
89
|
-
bgColor:
|
|
90
|
-
isLive:
|
|
80
|
+
value: l.value.value,
|
|
81
|
+
y: h.value,
|
|
82
|
+
bgColor: V.value,
|
|
83
|
+
isLive: l.value.isLive,
|
|
91
84
|
direction: k.value,
|
|
92
85
|
format: F.value,
|
|
93
|
-
opacity:
|
|
94
|
-
}) : (
|
|
95
|
-
|
|
96
|
-
style:
|
|
86
|
+
opacity: b.value
|
|
87
|
+
}) : (w(), A(B, { key: 1 }, [
|
|
88
|
+
_("div", {
|
|
89
|
+
style: Y({
|
|
97
90
|
position: "absolute",
|
|
98
91
|
left: g(e).plotLeft + "px",
|
|
99
92
|
right: (g(e).yAxisPosition === "left" ? 0 : g(e).yAxisWidth) + "px",
|
|
100
|
-
top:
|
|
93
|
+
top: h.value + "px",
|
|
101
94
|
height: "0",
|
|
102
|
-
borderTop: "1px dashed " +
|
|
103
|
-
opacity:
|
|
95
|
+
borderTop: "1px dashed " + V.value,
|
|
96
|
+
opacity: b.value * 0.5,
|
|
104
97
|
pointerEvents: "none",
|
|
105
98
|
zIndex: 2
|
|
106
99
|
})
|
|
107
100
|
}, null, 4),
|
|
108
|
-
|
|
109
|
-
style:
|
|
101
|
+
_("div", {
|
|
102
|
+
style: Y({
|
|
110
103
|
position: "absolute",
|
|
111
104
|
[g(e).yAxisPosition]: "4px",
|
|
112
|
-
top:
|
|
105
|
+
top: h.value + "px",
|
|
113
106
|
transform: "translateY(-50%)",
|
|
114
|
-
opacity:
|
|
107
|
+
opacity: b.value,
|
|
115
108
|
pointerEvents: "auto",
|
|
116
109
|
zIndex: 3,
|
|
117
|
-
background:
|
|
110
|
+
background: V.value,
|
|
118
111
|
color: r.value.yLabel.textColor,
|
|
119
112
|
fontSize: r.value.yLabel.fontSize + "px",
|
|
120
113
|
fontFamily: r.value.typography.fontFamily,
|
|
@@ -124,14 +117,14 @@ const ie = /* @__PURE__ */ q({
|
|
|
124
117
|
transition: "background-color 0.3s ease"
|
|
125
118
|
})
|
|
126
119
|
}, [
|
|
127
|
-
|
|
128
|
-
value:
|
|
120
|
+
H(ae, {
|
|
121
|
+
value: D.value,
|
|
129
122
|
format: F.value,
|
|
130
123
|
"spin-duration": 350
|
|
131
124
|
}, null, 8, ["value", "format"])
|
|
132
125
|
], 4)
|
|
133
126
|
], 64))
|
|
134
|
-
], 64)) :
|
|
127
|
+
], 64)) : J("", !0);
|
|
135
128
|
}
|
|
136
129
|
});
|
|
137
130
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wick-charts/vue",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "High-performance canvas timeseries charts for Vue — candlestick, line, bar, pie. Tree-shakeable, zero runtime deps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"vue": ">=3.3.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@wick-charts/core": "^0.6.
|
|
48
|
+
"@wick-charts/core": "^0.6.4"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "vite build"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BandData, BandSeriesOptions, SeriesSyncState } from '@wick-charts/core';
|
|
3
|
+
import { BandSeriesDef, EMPTY_SYNC_STATE, syncLayers, toLayers } from '@wick-charts/core';
|
|
4
|
+
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
|
5
|
+
|
|
6
|
+
import { useChartInstance } from './context';
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
/**
|
|
11
|
+
* Band data — one band per layer. A single band passes a flat
|
|
12
|
+
* `{ time, low, high }[]`; several bands pass `BandPoint[][]`. To name/color
|
|
13
|
+
* a band, pass `{ label, color?, data }` (or an array of them). Time accepts
|
|
14
|
+
* ms or `Date`.
|
|
15
|
+
*/
|
|
16
|
+
data: BandData;
|
|
17
|
+
/** Visual options override — fill, edge stroke, curve, entrance / intro / smoothing. */
|
|
18
|
+
options?: Partial<BandSeriesOptions>;
|
|
19
|
+
/** Stable series ID — same value across remounts. */
|
|
20
|
+
id?: string;
|
|
21
|
+
/** Show/hide the series without unmounting it — excludes it from the Y-range fit and tooltip/legend. Default `true`. Live. */
|
|
22
|
+
visible?: boolean;
|
|
23
|
+
}>(),
|
|
24
|
+
// Vue's type-based `defineProps` casts an absent Boolean-typed prop to
|
|
25
|
+
// `false` (not `undefined`) without an explicit default — see the
|
|
26
|
+
// `interactive` fix in ChartContainer.vue for the full explanation.
|
|
27
|
+
{ visible: true },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const chart = useChartInstance();
|
|
31
|
+
const seriesId = ref<string | null>(null);
|
|
32
|
+
// Per-layer sync state — drives the shared append/keepLast/update/replace
|
|
33
|
+
// reconciliation (plus per-layer labels/colors) so streaming eases instead of
|
|
34
|
+
// snapping Y every tick.
|
|
35
|
+
let prevSync: SeriesSyncState[] = [];
|
|
36
|
+
|
|
37
|
+
function applyData(id: string, data: BandData): void {
|
|
38
|
+
prevSync = syncLayers({ chart, id, data, prev: prevSync });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onMounted(() => {
|
|
42
|
+
const layerCount = toLayers(props.data).length;
|
|
43
|
+
const id = chart.addSeries(BandSeriesDef, {
|
|
44
|
+
...props.options,
|
|
45
|
+
layers: layerCount,
|
|
46
|
+
id: props.id,
|
|
47
|
+
});
|
|
48
|
+
seriesId.value = id;
|
|
49
|
+
prevSync = new Array(layerCount).fill(EMPTY_SYNC_STATE);
|
|
50
|
+
// Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
|
|
51
|
+
applyData(id, props.data);
|
|
52
|
+
chart.setSeriesVisible(id, props.visible);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
onUnmounted(() => {
|
|
56
|
+
if (seriesId.value) chart.removeSeries(seriesId.value);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
watch(
|
|
60
|
+
() => props.data,
|
|
61
|
+
(data) => {
|
|
62
|
+
const id = seriesId.value;
|
|
63
|
+
if (!id) return;
|
|
64
|
+
|
|
65
|
+
applyData(id, data);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
watch(
|
|
70
|
+
() => props.options,
|
|
71
|
+
(options) => {
|
|
72
|
+
if (seriesId.value && options) {
|
|
73
|
+
chart.updateSeriesOptions(seriesId.value, options);
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{ deep: true },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
watch(
|
|
80
|
+
() => props.visible,
|
|
81
|
+
(visible) => {
|
|
82
|
+
if (seriesId.value) chart.setSeriesVisible(seriesId.value, visible);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template><span v-if="false" /></template>
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,11 @@ export type {
|
|
|
13
13
|
AnimationsConfig,
|
|
14
14
|
AxisBound,
|
|
15
15
|
AxisConfig,
|
|
16
|
+
BandData,
|
|
17
|
+
BandEntryAnimation,
|
|
18
|
+
BandPoint,
|
|
19
|
+
BandPointInput,
|
|
20
|
+
BandSeriesOptions,
|
|
16
21
|
BarElementTransform,
|
|
17
22
|
BarEntryAnimation,
|
|
18
23
|
BarIntroDirectives,
|
|
@@ -51,6 +56,7 @@ export type {
|
|
|
51
56
|
HeatmapSeriesOptions,
|
|
52
57
|
HeatmapSeriesRenderer,
|
|
53
58
|
HoverInfo,
|
|
59
|
+
LayerSnapshot,
|
|
54
60
|
LegendItem,
|
|
55
61
|
LineIntroDirectives,
|
|
56
62
|
LineIntroFn,
|
|
@@ -136,6 +142,7 @@ export type {
|
|
|
136
142
|
} from '@wick-charts/core';
|
|
137
143
|
// Painters — custom per-element drawing for bar / candle / line series.
|
|
138
144
|
export {
|
|
145
|
+
BandSeriesDef,
|
|
139
146
|
BarSeriesDef,
|
|
140
147
|
CandlestickSeriesDef,
|
|
141
148
|
ChartInstance,
|
|
@@ -219,6 +226,7 @@ export {
|
|
|
219
226
|
wipeIntro,
|
|
220
227
|
} from '@wick-charts/core';
|
|
221
228
|
|
|
229
|
+
export { default as BandSeries } from './BandSeries.vue';
|
|
222
230
|
export { default as BarSeries } from './BarSeries.vue';
|
|
223
231
|
export { default as CandlestickSeries } from './CandlestickSeries.vue';
|
|
224
232
|
// Vue components
|
package/src/ui/InfoBar.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
|
+
type BandPoint,
|
|
3
4
|
type OHLCData,
|
|
4
5
|
type SeriesSnapshot,
|
|
5
6
|
type SnapshotSort,
|
|
@@ -98,9 +99,13 @@ const displayTime = computed(() => {
|
|
|
98
99
|
const slots = useSlots();
|
|
99
100
|
const hasCustomSlot = computed(() => typeof slots.default === 'function');
|
|
100
101
|
|
|
101
|
-
function isOHLC(data:
|
|
102
|
+
function isOHLC(data: SeriesSnapshot['data']): data is OHLCData {
|
|
102
103
|
return 'open' in data;
|
|
103
104
|
}
|
|
105
|
+
|
|
106
|
+
function isBand(data: SeriesSnapshot['data']): data is BandPoint {
|
|
107
|
+
return 'high' in data && !isOHLC(data);
|
|
108
|
+
}
|
|
104
109
|
</script>
|
|
105
110
|
|
|
106
111
|
<template>
|
|
@@ -174,6 +179,20 @@ function isOHLC(data: OHLCData | TimePoint): data is OHLCData {
|
|
|
174
179
|
</span>
|
|
175
180
|
</template>
|
|
176
181
|
</span>
|
|
182
|
+
<span v-else-if="isBand(s.data)" :style="{ display: 'inline-flex', alignItems: 'center', gap: '3px' }">
|
|
183
|
+
<span
|
|
184
|
+
:style="{
|
|
185
|
+
width: '6px',
|
|
186
|
+
height: '6px',
|
|
187
|
+
borderRadius: '1px',
|
|
188
|
+
background: s.color,
|
|
189
|
+
flexShrink: 0,
|
|
190
|
+
}"
|
|
191
|
+
/>
|
|
192
|
+
<span :style="{ color: s.color, fontWeight: 500 }">
|
|
193
|
+
{{ effectiveFormat((s.data as BandPoint).low, 'low') }} – {{ effectiveFormat((s.data as BandPoint).high, 'high') }}
|
|
194
|
+
</span>
|
|
195
|
+
</span>
|
|
177
196
|
<span v-else :style="{ display: 'inline-flex', alignItems: 'center', gap: '3px' }">
|
|
178
197
|
<span
|
|
179
198
|
:style="{
|
package/src/ui/Tooltip.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
|
+
type BandPoint,
|
|
3
4
|
type OHLCData,
|
|
4
5
|
type SeriesSnapshot,
|
|
5
6
|
type SnapshotSort,
|
|
@@ -125,7 +126,9 @@ const floatingPos = computed<{ left: number; top: number } | null>(() => {
|
|
|
125
126
|
|
|
126
127
|
const hasOHLC = snapshots.value.some((s) => 'open' in s.data);
|
|
127
128
|
const lineCount = snapshots.value.filter((s) => !('open' in s.data)).length;
|
|
128
|
-
|
|
129
|
+
// A band row carries two numbers, so it needs a wider box than a line row.
|
|
130
|
+
const hasBand = snapshots.value.some((s) => 'high' in s.data && !('open' in s.data));
|
|
131
|
+
const tooltipWidth = hasBand ? 220 : 160;
|
|
129
132
|
const tooltipHeight = hasOHLC ? 140 : 40 + lineCount * 22;
|
|
130
133
|
|
|
131
134
|
return computeTooltipPosition({
|
|
@@ -141,9 +144,13 @@ const floatingPos = computed<{ left: number; top: number } | null>(() => {
|
|
|
141
144
|
|
|
142
145
|
const measured = computed(() => !hasCustomSlot.value || measuredSize.value !== null);
|
|
143
146
|
|
|
144
|
-
function isOHLC(data:
|
|
147
|
+
function isOHLC(data: SeriesSnapshot['data']): data is OHLCData {
|
|
145
148
|
return 'open' in data;
|
|
146
149
|
}
|
|
150
|
+
|
|
151
|
+
function isBand(data: SeriesSnapshot['data']): data is BandPoint {
|
|
152
|
+
return 'high' in data && !isOHLC(data);
|
|
153
|
+
}
|
|
147
154
|
</script>
|
|
148
155
|
|
|
149
156
|
<template>
|
|
@@ -215,6 +222,33 @@ function isOHLC(data: OHLCData | TimePoint): data is OHLCData {
|
|
|
215
222
|
</span>
|
|
216
223
|
</template>
|
|
217
224
|
</div>
|
|
225
|
+
<div
|
|
226
|
+
v-else-if="isBand(s.data)"
|
|
227
|
+
:style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '2px 0' }"
|
|
228
|
+
>
|
|
229
|
+
<span
|
|
230
|
+
:style="{
|
|
231
|
+
width: '8px',
|
|
232
|
+
height: '8px',
|
|
233
|
+
borderRadius: '2px',
|
|
234
|
+
background: s.color,
|
|
235
|
+
flexShrink: 0,
|
|
236
|
+
}"
|
|
237
|
+
/>
|
|
238
|
+
<span
|
|
239
|
+
:style="{
|
|
240
|
+
opacity: 0.6,
|
|
241
|
+
flex: '1',
|
|
242
|
+
minWidth: 0,
|
|
243
|
+
overflow: 'hidden',
|
|
244
|
+
textOverflow: 'ellipsis',
|
|
245
|
+
whiteSpace: 'nowrap',
|
|
246
|
+
}"
|
|
247
|
+
>{{ s.label ?? 'Range' }}</span>
|
|
248
|
+
<span :style="{ fontWeight: 600, color: s.color, whiteSpace: 'nowrap' }">
|
|
249
|
+
{{ effectiveFormat((s.data as BandPoint).low, 'low') }} – {{ effectiveFormat((s.data as BandPoint).high, 'high') }}
|
|
250
|
+
</span>
|
|
251
|
+
</div>
|
|
218
252
|
<div v-else :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '2px 0' }">
|
|
219
253
|
<span
|
|
220
254
|
:style="{
|
package/src/ui/YLabel.vue
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
3
|
BadgeAnimator,
|
|
4
|
-
type ChartInstance,
|
|
5
4
|
type ValueFormatter,
|
|
6
5
|
type YLabelAnimate,
|
|
7
6
|
firstVisibleValue,
|
|
8
7
|
prefersReducedMotion,
|
|
9
8
|
resolveAnimate,
|
|
9
|
+
resolveYLabelSeriesId,
|
|
10
10
|
} from '@wick-charts/core';
|
|
11
11
|
import { computed, onMounted, onUnmounted, ref, useSlots } from 'vue';
|
|
12
12
|
|
|
@@ -145,21 +145,10 @@ onUnmounted(() => {
|
|
|
145
145
|
rafId = null;
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
function resolveSeriesId(c: ChartInstance, explicit: string | undefined): string | null {
|
|
149
|
-
if (explicit !== undefined) return explicit;
|
|
150
|
-
|
|
151
|
-
const singleLayer = c.getSeriesIdsByType('time', { visibleOnly: true, singleLayerOnly: true });
|
|
152
|
-
if (singleLayer.length > 0) return singleLayer[0];
|
|
153
|
-
|
|
154
|
-
const anyTime = c.getSeriesIdsByType('time', { visibleOnly: true });
|
|
155
|
-
|
|
156
|
-
return anyTime.length > 0 ? anyTime[0] : null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
148
|
const resolvedId = computed<string | null>(() => {
|
|
160
149
|
void bump.value;
|
|
161
150
|
|
|
162
|
-
return
|
|
151
|
+
return resolveYLabelSeriesId(chart, props.seriesId);
|
|
163
152
|
});
|
|
164
153
|
|
|
165
154
|
const last = computed<{ value: number; isLive: boolean } | null>(() => {
|