@wick-charts/vue 0.5.1 → 0.6.1

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.
Files changed (82) hide show
  1. package/README.md +45 -6
  2. package/dist/BarSeries.vue.js +26 -20
  3. package/dist/CandlestickSeries.vue.js +30 -24
  4. package/dist/ChartContainer.vue.js +125 -72
  5. package/dist/HeatmapSeries.vue.js +40 -0
  6. package/dist/HeatmapSeries.vue2.js +4 -0
  7. package/dist/LineSeries.vue.js +26 -20
  8. package/dist/PieSeries.vue.js +24 -18
  9. package/dist/core/dist/animation/animator.js +21 -21
  10. package/dist/core/dist/animation/config.js +114 -87
  11. package/dist/core/dist/animation/easing.js +3 -1
  12. package/dist/core/dist/animation/intro-wave.js +68 -0
  13. package/dist/core/dist/axis/dom-labels.js +13 -14
  14. package/dist/core/dist/chart/badge-animator.js +56 -0
  15. package/dist/core/dist/chart/edge-indicators.js +28 -13
  16. package/dist/core/dist/chart/last-value.js +18 -17
  17. package/dist/core/dist/chart/options.js +27 -16
  18. package/dist/core/dist/chart/y-target.js +13 -12
  19. package/dist/core/dist/chart.js +868 -441
  20. package/dist/core/dist/components/annotation-chip.js +28 -0
  21. package/dist/core/dist/components/crosshair.js +8 -6
  22. package/dist/core/dist/components/edge-indicator.js +58 -26
  23. package/dist/core/dist/components/loading-indicator.js +46 -0
  24. package/dist/core/dist/components/marker.js +49 -31
  25. package/dist/core/dist/components/reference-line.js +27 -36
  26. package/dist/core/dist/components/time-region.js +30 -28
  27. package/dist/core/dist/data/store.js +38 -19
  28. package/dist/core/dist/data/sync.js +60 -38
  29. package/dist/core/dist/heatmap-tooltip.js +16 -0
  30. package/dist/core/dist/interactions/handler.js +86 -46
  31. package/dist/core/dist/perf/perf-hud.js +5 -5
  32. package/dist/core/dist/scales/x-scale.js +85 -37
  33. package/dist/core/dist/scales/y-scale.js +38 -27
  34. package/dist/core/dist/series/bar-intro.js +18 -0
  35. package/dist/core/dist/series/bar.js +337 -205
  36. package/dist/core/dist/series/base-multi-layer.js +224 -96
  37. package/dist/core/dist/series/candlestick-intro.js +57 -0
  38. package/dist/core/dist/series/candlestick.js +454 -276
  39. package/dist/core/dist/series/heatmap.js +387 -0
  40. package/dist/core/dist/series/line-intro.js +85 -0
  41. package/dist/core/dist/series/line.js +590 -282
  42. package/dist/core/dist/series/painters/builtins.js +2 -2
  43. package/dist/core/dist/series/pie.js +396 -259
  44. package/dist/core/dist/series/register-builtins.js +8 -7
  45. package/dist/core/dist/series/types.js +6 -0
  46. package/dist/core/dist/series/wave-intro.js +26 -0
  47. package/dist/core/dist/theme/create.js +69 -63
  48. package/dist/core/dist/theme/themes/mint-breeze.js +2 -2
  49. package/dist/core/dist/theme/themes/sand-dune.js +2 -2
  50. package/dist/core/dist/utils/color.js +109 -34
  51. package/dist/core/dist/utils/deep-equal.js +18 -0
  52. package/dist/core/dist/utils/time.js +25 -19
  53. package/dist/index.d.ts +1494 -122
  54. package/dist/index.js +177 -130
  55. package/dist/ui/Crosshair.vue.js +20 -21
  56. package/dist/ui/HeatmapTooltip.vue.js +96 -0
  57. package/dist/ui/HeatmapTooltip.vue2.js +4 -0
  58. package/dist/ui/InfoBar.vue.js +46 -47
  59. package/dist/ui/Legend.vue.js +80 -79
  60. package/dist/ui/PieLegend.vue.js +5 -5
  61. package/dist/ui/PieTooltip.vue.js +77 -67
  62. package/dist/ui/Tooltip.vue.js +60 -60
  63. package/dist/ui/YAxis.vue.js +2 -2
  64. package/dist/ui/YLabel.vue.js +94 -61
  65. package/package.json +2 -2
  66. package/src/BarSeries.vue +23 -7
  67. package/src/CandlestickSeries.vue +23 -7
  68. package/src/ChartContainer.vue +250 -13
  69. package/src/HeatmapSeries.vue +69 -0
  70. package/src/LineSeries.vue +23 -7
  71. package/src/PieSeries.vue +22 -6
  72. package/src/index.ts +84 -3
  73. package/src/ui/Crosshair.vue +1 -2
  74. package/src/ui/HeatmapTooltip.vue +144 -0
  75. package/src/ui/InfoBar.vue +1 -2
  76. package/src/ui/Legend.vue +13 -0
  77. package/src/ui/Marker.vue +1 -1
  78. package/src/ui/PieTooltip.vue +10 -0
  79. package/src/ui/ReferenceLine.vue +2 -2
  80. package/src/ui/TimeRegion.vue +2 -2
  81. package/src/ui/Tooltip.vue +2 -2
  82. 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 type { ChartInstance, ValueFormatter } from '@wick-charts/core';
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
- // Single subscription covering data/visibility/theme/options changes, plus
39
- // viewportChange for pixel-Y drift on pan/zoom where the value is unchanged
40
- // but the badge must move.
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) bump.value++;
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(last.value.value);
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="last.value" :format="effectiveFormat" :spin-duration="350" />
305
+ <NumberFlow :value="textValue" :format="effectiveFormat" :spin-duration="350" />
191
306
  </div>
192
307
  </template>
193
308
  </template>