@wick-charts/vue 0.5.0 → 0.6.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 +18 -3
- package/dist/BarSeries.vue.js +26 -20
- package/dist/CandlestickSeries.vue.js +30 -24
- package/dist/ChartContainer.vue.js +88 -65
- package/dist/HeatmapSeries.vue.js +40 -0
- package/dist/HeatmapSeries.vue2.js +4 -0
- package/dist/LineSeries.vue.js +26 -20
- package/dist/PieSeries.vue.js +24 -18
- package/dist/core/dist/animation/animator.js +21 -21
- package/dist/core/dist/animation/config.js +114 -87
- package/dist/core/dist/animation/easing.js +3 -1
- package/dist/core/dist/animation/intro-wave.js +68 -0
- package/dist/core/dist/axis/dom-labels.js +13 -14
- package/dist/core/dist/chart/badge-animator.js +56 -0
- package/dist/core/dist/chart/edge-indicators.js +28 -13
- package/dist/core/dist/chart/last-value.js +18 -17
- package/dist/core/dist/chart/y-target.js +13 -12
- package/dist/core/dist/chart.js +908 -626
- package/dist/core/dist/components/annotation-chip.js +28 -0
- package/dist/core/dist/components/edge-indicator.js +58 -26
- package/dist/core/dist/components/loading-indicator.js +46 -0
- package/dist/core/dist/components/marker.js +49 -31
- package/dist/core/dist/components/reference-line.js +27 -36
- package/dist/core/dist/components/time-region.js +30 -28
- package/dist/core/dist/data/store.js +38 -19
- package/dist/core/dist/data/sync.js +60 -38
- package/dist/core/dist/heatmap-tooltip.js +16 -0
- package/dist/core/dist/interactions/handler.js +67 -43
- package/dist/core/dist/perf/perf-hud.js +2 -2
- package/dist/core/dist/scales/x-scale.js +85 -37
- package/dist/core/dist/scales/y-scale.js +38 -27
- package/dist/core/dist/series/bar-intro.js +18 -0
- package/dist/core/dist/series/bar.js +337 -205
- package/dist/core/dist/series/base-multi-layer.js +224 -96
- package/dist/core/dist/series/candlestick-intro.js +57 -0
- package/dist/core/dist/series/candlestick.js +454 -276
- package/dist/core/dist/series/heatmap.js +387 -0
- package/dist/core/dist/series/line-intro.js +85 -0
- package/dist/core/dist/series/line.js +590 -282
- package/dist/core/dist/series/painters/builtins.js +2 -2
- package/dist/core/dist/series/pie.js +396 -259
- package/dist/core/dist/series/register-builtins.js +8 -7
- package/dist/core/dist/series/types.js +6 -0
- package/dist/core/dist/series/wave-intro.js +26 -0
- package/dist/core/dist/theme/create.js +69 -63
- package/dist/core/dist/theme/themes/mint-breeze.js +2 -2
- package/dist/core/dist/theme/themes/sand-dune.js +2 -2
- package/dist/core/dist/utils/color.js +109 -34
- package/dist/core/dist/utils/deep-equal.js +18 -0
- package/dist/core/dist/utils/time.js +25 -19
- package/dist/index.d.ts +1411 -122
- package/dist/index.js +177 -130
- package/dist/ui/Crosshair.vue.js +20 -21
- package/dist/ui/HeatmapTooltip.vue.js +96 -0
- package/dist/ui/HeatmapTooltip.vue2.js +4 -0
- package/dist/ui/InfoBar.vue.js +46 -47
- package/dist/ui/Legend.vue.js +80 -79
- package/dist/ui/PieLegend.vue.js +5 -5
- package/dist/ui/PieTooltip.vue.js +77 -67
- package/dist/ui/Tooltip.vue.js +60 -60
- package/dist/ui/YAxis.vue.js +2 -2
- package/dist/ui/YLabel.vue.js +94 -61
- package/package.json +2 -2
- package/src/BarSeries.vue +23 -7
- package/src/CandlestickSeries.vue +23 -7
- package/src/ChartContainer.vue +134 -12
- package/src/HeatmapSeries.vue +69 -0
- package/src/LineSeries.vue +23 -7
- package/src/PieSeries.vue +22 -6
- package/src/index.ts +84 -3
- package/src/ui/Crosshair.vue +1 -2
- package/src/ui/HeatmapTooltip.vue +144 -0
- package/src/ui/InfoBar.vue +1 -2
- package/src/ui/Legend.vue +13 -0
- package/src/ui/Marker.vue +1 -1
- package/src/ui/PieTooltip.vue +10 -0
- package/src/ui/ReferenceLine.vue +2 -2
- package/src/ui/TimeRegion.vue +2 -2
- package/src/ui/Tooltip.vue +2 -2
- package/src/ui/YLabel.vue +123 -8
package/src/ui/YLabel.vue
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
BadgeAnimator,
|
|
4
|
+
type ChartInstance,
|
|
5
|
+
type ValueFormatter,
|
|
6
|
+
type YLabelAnimate,
|
|
7
|
+
firstVisibleValue,
|
|
8
|
+
prefersReducedMotion,
|
|
9
|
+
resolveAnimate,
|
|
10
|
+
} from '@wick-charts/core';
|
|
3
11
|
import { computed, onMounted, onUnmounted, ref, useSlots } from 'vue';
|
|
4
12
|
|
|
5
13
|
import { useChartInstance } from '../context';
|
|
6
14
|
import NumberFlow from './NumberFlow.vue';
|
|
7
15
|
|
|
16
|
+
// Badge motion tuning types + the resolve/reduced-motion/count-up helpers live
|
|
17
|
+
// in core next to BadgeAnimator so React / Vue / Svelte share one set of
|
|
18
|
+
// defaults. Re-exported here to keep YLabel's public surface stable.
|
|
19
|
+
export type { YLabelAnimate, YLabelAnimateOptions } from '@wick-charts/core';
|
|
20
|
+
|
|
8
21
|
/** Direction of the current value vs. previous close. Drives the badge color in the default UI. */
|
|
9
22
|
export type YLabelDirection = 'up' | 'down' | 'neutral';
|
|
10
23
|
|
|
@@ -18,6 +31,11 @@ const props = defineProps<{
|
|
|
18
31
|
color?: string;
|
|
19
32
|
/** Custom formatter; routed through NumberFlow's `formatter` so the digit animation still plays. */
|
|
20
33
|
format?: ValueFormatter;
|
|
34
|
+
/**
|
|
35
|
+
* Badge motion. `true` (default) glides + counts up + fades in with the
|
|
36
|
+
* intro; `false` snaps with no easing; an object tunes individual parts.
|
|
37
|
+
*/
|
|
38
|
+
animate?: YLabelAnimate;
|
|
21
39
|
}>();
|
|
22
40
|
|
|
23
41
|
defineSlots<{
|
|
@@ -28,6 +46,7 @@ defineSlots<{
|
|
|
28
46
|
isLive: boolean;
|
|
29
47
|
direction: YLabelDirection;
|
|
30
48
|
format: ValueFormatter;
|
|
49
|
+
opacity: number;
|
|
31
50
|
}): unknown;
|
|
32
51
|
}>();
|
|
33
52
|
|
|
@@ -35,25 +54,95 @@ const chart = useChartInstance();
|
|
|
35
54
|
const slots = useSlots();
|
|
36
55
|
const hasCustomSlot = computed(() => typeof slots.default === 'function');
|
|
37
56
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
57
|
+
// Re-render bump for viewport/data/theme changes: `viewportChange` covers
|
|
58
|
+
// pixel-Y drift on pan/zoom (value unchanged, badge must move); the others
|
|
59
|
+
// cover value/visibility/theme. The badge's own value glide + intro reveal
|
|
60
|
+
// ride the rAF loop below instead — this bump only keeps `top` in sync with
|
|
61
|
+
// the current yScale.
|
|
41
62
|
const bump = ref(0);
|
|
63
|
+
|
|
64
|
+
// rAF-driven badge motion via the shared core BadgeAnimator, so React / Vue /
|
|
65
|
+
// Svelte animate the badge identically. `null` until the first frame runs.
|
|
66
|
+
const frame = ref<{ positionValue: number; textValue: number; opacity: number } | null>(null);
|
|
67
|
+
let animator: BadgeAnimator | null = null;
|
|
68
|
+
let animatorId: string | null = null;
|
|
69
|
+
let rafId: number | null = null;
|
|
70
|
+
|
|
71
|
+
// Resolve once per `animate` change (memoized by computed) rather than
|
|
72
|
+
// allocating a fresh object every rAF frame on the hot path.
|
|
73
|
+
const resolvedAnimate = computed(() => resolveAnimate(props.animate));
|
|
74
|
+
|
|
75
|
+
function runFrame() {
|
|
76
|
+
rafId = null;
|
|
77
|
+
|
|
78
|
+
const id = resolvedId.value;
|
|
79
|
+
const snapshot = id !== null ? chart.getStackedLastValue(id) : null;
|
|
80
|
+
if (id === null || !snapshot) {
|
|
81
|
+
animator = null;
|
|
82
|
+
animatorId = null;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const target = snapshot.value;
|
|
87
|
+
// Recreate the animator when the tracked series changes (not just when it
|
|
88
|
+
// goes null) so the badge shows the new series' value immediately instead of
|
|
89
|
+
// gliding from the previous series' stale last value.
|
|
90
|
+
if (animator === null || animatorId !== id) {
|
|
91
|
+
animator = new BadgeAnimator(target);
|
|
92
|
+
animatorId = id;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { glide, countUp, settleMs } = resolvedAnimate.value;
|
|
96
|
+
const introFront = chart.getIntroFront(id);
|
|
97
|
+
|
|
98
|
+
const out = animator.frame({
|
|
99
|
+
target,
|
|
100
|
+
// Only needed for the intro count-up; skip the visible-range binary search
|
|
101
|
+
// entirely once the reveal has settled (the hot streaming path).
|
|
102
|
+
firstVisible: introFront < 1 ? firstVisibleValue(chart, id, target) : target,
|
|
103
|
+
introFront,
|
|
104
|
+
now: performance.now(),
|
|
105
|
+
glide,
|
|
106
|
+
countUp,
|
|
107
|
+
settleMs,
|
|
108
|
+
reducedMotion: prefersReducedMotion(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const prev = frame.value;
|
|
112
|
+
if (
|
|
113
|
+
prev === null ||
|
|
114
|
+
prev.positionValue !== out.positionValue ||
|
|
115
|
+
prev.textValue !== out.textValue ||
|
|
116
|
+
prev.opacity !== out.opacity
|
|
117
|
+
) {
|
|
118
|
+
frame.value = { positionValue: out.positionValue, textValue: out.textValue, opacity: out.opacity };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (out.animating && rafId === null) rafId = requestAnimationFrame(runFrame);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function kick() {
|
|
125
|
+
if (rafId === null) rafId = requestAnimationFrame(runFrame);
|
|
126
|
+
}
|
|
127
|
+
|
|
42
128
|
const onChange = () => {
|
|
43
129
|
bump.value++;
|
|
130
|
+
kick();
|
|
44
131
|
};
|
|
45
132
|
|
|
46
133
|
onMounted(() => {
|
|
47
134
|
chart.setYLabel(true);
|
|
48
135
|
chart.on('overlayChange', onChange);
|
|
49
136
|
chart.on('viewportChange', onChange);
|
|
50
|
-
if (chart.getSeriesIds().length > 0)
|
|
137
|
+
if (chart.getSeriesIds().length > 0) onChange();
|
|
51
138
|
});
|
|
52
139
|
|
|
53
140
|
onUnmounted(() => {
|
|
54
141
|
chart.setYLabel(false);
|
|
55
142
|
chart.off('overlayChange', onChange);
|
|
56
143
|
chart.off('viewportChange', onChange);
|
|
144
|
+
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
145
|
+
rafId = null;
|
|
57
146
|
});
|
|
58
147
|
|
|
59
148
|
function resolveSeriesId(c: ChartInstance, explicit: string | undefined): string | null {
|
|
@@ -91,13 +180,37 @@ const theme = computed(() => {
|
|
|
91
180
|
return chart.getTheme();
|
|
92
181
|
});
|
|
93
182
|
|
|
183
|
+
// Position eases (the glide); the digit value steps discretely (once per real
|
|
184
|
+
// point, not per frame) so the roll doesn't churn. Before the first rAF frame
|
|
185
|
+
// lands, fall back to the settled value (hidden if an intro is still sweeping).
|
|
186
|
+
const positionValue = computed<number>(() => {
|
|
187
|
+
if (frame.value !== null) return frame.value.positionValue;
|
|
188
|
+
|
|
189
|
+
return last.value?.value ?? 0;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const textValue = computed<number>(() => {
|
|
193
|
+
if (frame.value !== null) return frame.value.textValue;
|
|
194
|
+
|
|
195
|
+
return last.value?.value ?? 0;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const opacity = computed<number>(() => {
|
|
199
|
+
if (frame.value !== null) return frame.value.opacity;
|
|
200
|
+
if (resolvedId.value === null) return 1;
|
|
201
|
+
|
|
202
|
+
return chart.getIntroFront(resolvedId.value) < 1 ? 0 : 1;
|
|
203
|
+
});
|
|
204
|
+
|
|
94
205
|
const y = computed(() => {
|
|
95
206
|
void bump.value;
|
|
96
207
|
if (last.value === null) return 0;
|
|
97
208
|
|
|
98
|
-
return chart.yScale.valueToY(
|
|
209
|
+
return chart.yScale.valueToY(positionValue.value);
|
|
99
210
|
});
|
|
100
211
|
|
|
212
|
+
// Direction / color track the settled last value (not the mid-glide display
|
|
213
|
+
// value) so the accent doesn't flicker while the badge eases into place.
|
|
101
214
|
const direction = computed<YLabelDirection>(() => {
|
|
102
215
|
if (last.value === null || previousClose.value === null) return 'neutral';
|
|
103
216
|
if (last.value.value > previousClose.value) return 'up';
|
|
@@ -154,6 +267,7 @@ const effectiveFormat = computed<ValueFormatter>(() => {
|
|
|
154
267
|
:is-live="last.isLive"
|
|
155
268
|
:direction="direction"
|
|
156
269
|
:format="effectiveFormat"
|
|
270
|
+
:opacity="opacity"
|
|
157
271
|
/>
|
|
158
272
|
<template v-else>
|
|
159
273
|
<div
|
|
@@ -164,7 +278,7 @@ const effectiveFormat = computed<ValueFormatter>(() => {
|
|
|
164
278
|
top: y + 'px',
|
|
165
279
|
height: '0',
|
|
166
280
|
borderTop: '1px dashed ' + bgColor,
|
|
167
|
-
opacity: 0.5,
|
|
281
|
+
opacity: opacity * 0.5,
|
|
168
282
|
pointerEvents: 'none',
|
|
169
283
|
zIndex: 2,
|
|
170
284
|
}"
|
|
@@ -175,6 +289,7 @@ const effectiveFormat = computed<ValueFormatter>(() => {
|
|
|
175
289
|
right: '4px',
|
|
176
290
|
top: y + 'px',
|
|
177
291
|
transform: 'translateY(-50%)',
|
|
292
|
+
opacity: opacity,
|
|
178
293
|
pointerEvents: 'auto',
|
|
179
294
|
zIndex: 3,
|
|
180
295
|
background: bgColor,
|
|
@@ -187,7 +302,7 @@ const effectiveFormat = computed<ValueFormatter>(() => {
|
|
|
187
302
|
transition: 'background-color 0.3s ease',
|
|
188
303
|
}"
|
|
189
304
|
>
|
|
190
|
-
<NumberFlow :value="
|
|
305
|
+
<NumberFlow :value="textValue" :format="effectiveFormat" :spin-duration="350" />
|
|
191
306
|
</div>
|
|
192
307
|
</template>
|
|
193
308
|
</template>
|