@wick-charts/vue 0.3.6 → 0.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wick-charts/vue",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
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": {
@@ -8,7 +8,7 @@
8
8
  "url": "git+https://github.com/mo4islona/wick-charts.git",
9
9
  "directory": "packages/vue"
10
10
  },
11
- "homepage": "https://mo4islona.github.io/wick-charts/",
11
+ "homepage": "https://wick-charts.eeff.io/",
12
12
  "bugs": "https://github.com/mo4islona/wick-charts/issues",
13
13
  "keywords": [
14
14
  "charts",
@@ -45,7 +45,7 @@
45
45
  "vue": ">=3.3.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@wick-charts/core": "^0.3.6"
48
+ "@wick-charts/core": "^0.4.0"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "vite build"
package/src/BarSeries.vue CHANGED
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type { BarSeriesOptions, TimePoint } from '@wick-charts/core';
2
+ import type { BarSeriesOptions, SeriesSyncState, TimePoint } from '@wick-charts/core';
3
+ import { EMPTY_SYNC_STATE, syncSeriesLayer } from '@wick-charts/core';
3
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
4
5
 
5
6
  import { useChartInstance } from './context';
@@ -13,20 +14,28 @@ const props = defineProps<{
13
14
 
14
15
  const chart = useChartInstance();
15
16
  const seriesId = ref<string | null>(null);
17
+ // Per-layer sync state — drives the shared append/keepLast/update/replace
18
+ // reconciliation so streaming eases instead of snapping Y every tick.
19
+ let prevSync: SeriesSyncState[] = [];
20
+
21
+ function applyData(id: string, data: TimePoint[][]): void {
22
+ chart.batch(() => {
23
+ for (let i = 0; i < data.length; i++) {
24
+ prevSync[i] = syncSeriesLayer({ chart, id, data: data[i], prev: prevSync[i] ?? EMPTY_SYNC_STATE, layerIndex: i });
25
+ }
26
+ });
27
+ }
16
28
 
17
29
  onMounted(() => {
18
- const id = chart.addBarSeries({
30
+ const id = chart.addSeries('bar', {
19
31
  ...props.options,
20
32
  layers: props.data.length,
21
33
  id: props.id,
22
34
  });
23
35
  seriesId.value = id;
36
+ prevSync = props.data.map(() => EMPTY_SYNC_STATE);
24
37
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
25
- chart.batch(() => {
26
- for (let i = 0; i < props.data.length; i++) {
27
- chart.setSeriesData(id, props.data[i], i);
28
- }
29
- });
38
+ applyData(id, props.data);
30
39
  });
31
40
 
32
41
  onUnmounted(() => {
@@ -36,12 +45,10 @@ onUnmounted(() => {
36
45
  watch(
37
46
  () => props.data,
38
47
  (data) => {
39
- if (!seriesId.value) return;
40
- chart.batch(() => {
41
- for (let i = 0; i < data.length; i++) {
42
- chart.setSeriesData(seriesId.value!, data[i], i);
43
- }
44
- });
48
+ const id = seriesId.value;
49
+ if (!id) return;
50
+
51
+ applyData(id, data);
45
52
  },
46
53
  );
47
54
 
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type { CandlestickSeriesOptions, OHLCInput } from '@wick-charts/core';
2
+ import type { CandlestickSeriesOptions, OHLCInput, SeriesSyncState } from '@wick-charts/core';
3
+ import { EMPTY_SYNC_STATE, syncSeriesLayer } from '@wick-charts/core';
3
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
4
5
 
5
6
  import { useChartInstance } from './context';
@@ -13,18 +14,18 @@ const props = defineProps<{
13
14
 
14
15
  const chart = useChartInstance();
15
16
  const seriesId = ref<string | null>(null);
16
- let prevLen = 0;
17
+ // Sync state — drives the shared append/keepLast/update/replace reconciliation
18
+ // so a rolling-window stream eases instead of snapping Y (and a 6–20 candle
19
+ // burst takes the smooth append path, matching React's 20-candle threshold).
20
+ let prevSync: SeriesSyncState = EMPTY_SYNC_STATE;
17
21
 
18
22
  onMounted(() => {
19
- const id = chart.addCandlestickSeries({ ...props.options, id: props.id });
23
+ const id = chart.addSeries('candlestick', { ...props.options, id: props.id });
20
24
  seriesId.value = id;
21
25
  // Initial data load — Vue's `watch` is lazy by default, so the watcher
22
26
  // below only fires on subsequent `data` prop mutations. Explicitly apply
23
27
  // the first value here so components with static data render immediately.
24
- if (props.data.length > 0) {
25
- chart.setSeriesData(id, props.data);
26
- prevLen = props.data.length;
27
- }
28
+ prevSync = syncSeriesLayer({ chart, id, data: props.data, prev: prevSync });
28
29
  });
29
30
 
30
31
  onUnmounted(() => {
@@ -37,23 +38,7 @@ watch(
37
38
  const id = seriesId.value;
38
39
  if (!id) return;
39
40
 
40
- if (data.length === 0) {
41
- chart.setSeriesData(id, []);
42
- prevLen = 0;
43
- return;
44
- }
45
-
46
- if (prevLen === 0 || data.length < prevLen || data.length - prevLen > 5) {
47
- chart.setSeriesData(id, data);
48
- } else if (data.length === prevLen) {
49
- chart.updateData(id, data[data.length - 1]);
50
- } else {
51
- for (let i = prevLen; i < data.length; i++) {
52
- chart.appendData(id, data[i]);
53
- }
54
- }
55
-
56
- prevLen = data.length;
41
+ prevSync = syncSeriesLayer({ chart, id, data, prev: prevSync });
57
42
  },
58
43
  );
59
44
 
@@ -6,6 +6,7 @@ import {
6
6
  type ChartOptions,
7
7
  type ChartTheme,
8
8
  type EdgeReachedInfo,
9
+ type VisibleRangeSpec,
9
10
  catppuccin,
10
11
  } from '@wick-charts/core';
11
12
  import { computed, nextTick, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
@@ -38,6 +39,28 @@ const props = withDefaults(
38
39
  right?: number | { intervals: number };
39
40
  left?: number | { intervals: number };
40
41
  };
42
+ /**
43
+ * Viewport-level streaming behavior. Captured at mount only — changing
44
+ * this prop after the chart is created is ignored.
45
+ */
46
+ viewport?: {
47
+ /**
48
+ * Width of the visible window in data bars, set on the first data load
49
+ * to `maxVisibleBars * dataInterval`. While the dataset is smaller than
50
+ * this width, streaming ticks render into the empty right-side gap and
51
+ * the viewport stays put; once the data reaches the right edge, the
52
+ * viewport pans forward to keep the latest bar pinned (tail-scroll).
53
+ * Default: 200.
54
+ */
55
+ maxVisibleBars?: number;
56
+ /**
57
+ * Initial visible range applied before the first paint with data. Same
58
+ * shape as the imperative `chart.setVisibleRange` — pass a bar count
59
+ * (e.g. `35`), an explicit `{from, to}` window, or `{from, bars}` for
60
+ * a warm-up pair. Captured at mount.
61
+ */
62
+ initialRange?: VisibleRangeSpec;
63
+ };
41
64
  /** Show the chart background gradient. Defaults to true. */
42
65
  gradient?: boolean;
43
66
  /** Enable zoom, pan, and crosshair interactions. Defaults to true. */
@@ -51,16 +74,15 @@ const props = withDefaults(
51
74
  */
52
75
  headerLayout?: 'overlay' | 'inline';
53
76
  /**
54
- * Chart-level animation configuration. See `AnimationsConfig` for the full shape.
55
- *
56
- * Two layers — chart-level (this prop) sets defaults for every series; per-series
57
- * options on `<LineSeries>`/`<CandlestickSeries>`/`<BarSeries>` override that
58
- * default for that one series.
77
+ * Animation control. `true` / omitted uses built-in defaults; `false`
78
+ * disables every category. Per-series options on `<LineSeries>` /
79
+ * `<CandlestickSeries>` / `<BarSeries>` override these chart-level
80
+ * defaults unless the category here is explicitly `false`.
59
81
  *
60
- * Shorthands: `true` / omitted — built-in defaults; `false` — disables every
61
- * animation category; `{ points: false }` / `{ viewport: false }` disables a
62
- * category. Updating this prop calls `chart.setAnimations(...)` so the new
63
- * durations take effect on the next animation / render.
82
+ * **Init-only by reference identity.** A new `animations` reference
83
+ * recreates the underlying `ChartInstance`. Hoist the object outside
84
+ * the render scope — an inline literal tears the chart down on every
85
+ * re-render.
64
86
  */
65
87
  animations?: boolean | AnimationsConfig;
66
88
  /**
@@ -159,6 +181,7 @@ onMounted(async () => {
159
181
  if (props.axis) options.axis = props.axis;
160
182
  if (props.theme) options.theme = props.theme;
161
183
  if (props.padding) options.padding = props.padding;
184
+ if (props.viewport) options.viewport = props.viewport;
162
185
  if (props.interactive !== undefined) options.interactive = props.interactive;
163
186
  if (props.grid !== undefined) options.grid = props.grid;
164
187
  if (perfAtMount !== undefined) options.perf = perfAtMount;
@@ -213,14 +236,35 @@ watch(
213
236
  },
214
237
  );
215
238
 
216
- // Stringify the animations config so callers can pass a fresh object identity
217
- // without thrashing animator state when nothing has actually changed.
239
+ // Init-only: post-mount `animations` reference change tears down the
240
+ // instance and rebuilds with the new config. Reference equality matters
241
+ // — passing an inline literal recreates on every render.
242
+ //
243
+ // Children get the chart via `useChartInstance()` which snapshots
244
+ // `chartRef.value` once at setup time. To force them to re-setup against
245
+ // the new instance, we flip `chart.value` to `null` first — the `v-if`
246
+ // guard in the template unmounts the slot — then `await nextTick()` so
247
+ // Vue commits the unmount before the new ChartInstance is constructed.
248
+ // When `chart.value` becomes the new instance, the slot re-mounts and
249
+ // every child's `setup()` runs again against the fresh chart.
218
250
  watch(
219
- () => JSON.stringify(props.animations),
220
- () => {
221
- if (chart.value && props.animations !== undefined) {
222
- chart.value.setAnimations(props.animations);
223
- }
251
+ () => props.animations,
252
+ async (next) => {
253
+ if (!chart.value || !containerRef.value) return;
254
+ chart.value.destroy();
255
+ chart.value = null;
256
+ await nextTick();
257
+ const opts: ChartOptions = {};
258
+ if (props.axis) opts.axis = props.axis;
259
+ if (props.theme) opts.theme = props.theme;
260
+ if (props.padding) opts.padding = props.padding;
261
+ if (props.viewport) opts.viewport = props.viewport;
262
+ if (props.interactive !== undefined) opts.interactive = props.interactive;
263
+ if (props.grid !== undefined) opts.grid = props.grid;
264
+ if (perfAtMount !== undefined) opts.perf = perfAtMount;
265
+ if (onEdgeReachedAtMount) opts.onEdgeReached = onEdgeReachedAtMount;
266
+ if (next !== undefined) opts.animations = next;
267
+ chart.value = new ChartInstance(containerRef.value, opts);
224
268
  },
225
269
  );
226
270
 
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type { LineSeriesOptions, TimePoint } from '@wick-charts/core';
2
+ import type { LineSeriesOptions, SeriesSyncState, TimePoint } from '@wick-charts/core';
3
+ import { EMPTY_SYNC_STATE, syncSeriesLayer } from '@wick-charts/core';
3
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
4
5
 
5
6
  import { useChartInstance } from './context';
@@ -13,20 +14,28 @@ const props = defineProps<{
13
14
 
14
15
  const chart = useChartInstance();
15
16
  const seriesId = ref<string | null>(null);
17
+ // Per-layer sync state — drives the shared append/keepLast/update/replace
18
+ // reconciliation so streaming eases instead of snapping Y every tick.
19
+ let prevSync: SeriesSyncState[] = [];
20
+
21
+ function applyData(id: string, data: TimePoint[][]): void {
22
+ chart.batch(() => {
23
+ for (let i = 0; i < data.length; i++) {
24
+ prevSync[i] = syncSeriesLayer({ chart, id, data: data[i], prev: prevSync[i] ?? EMPTY_SYNC_STATE, layerIndex: i });
25
+ }
26
+ });
27
+ }
16
28
 
17
29
  onMounted(() => {
18
- const id = chart.addLineSeries({
30
+ const id = chart.addSeries('line', {
19
31
  ...props.options,
20
32
  layers: props.data.length,
21
33
  id: props.id,
22
34
  });
23
35
  seriesId.value = id;
36
+ prevSync = props.data.map(() => EMPTY_SYNC_STATE);
24
37
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
25
- chart.batch(() => {
26
- for (let i = 0; i < props.data.length; i++) {
27
- chart.setSeriesData(id, props.data[i], i);
28
- }
29
- });
38
+ applyData(id, props.data);
30
39
  });
31
40
 
32
41
  onUnmounted(() => {
@@ -36,12 +45,10 @@ onUnmounted(() => {
36
45
  watch(
37
46
  () => props.data,
38
47
  (data) => {
39
- if (!seriesId.value) return;
40
- chart.batch(() => {
41
- for (let i = 0; i < data.length; i++) {
42
- chart.setSeriesData(seriesId.value!, data[i], i);
43
- }
44
- });
48
+ const id = seriesId.value;
49
+ if (!id) return;
50
+
51
+ applyData(id, data);
45
52
  },
46
53
  );
47
54
 
package/src/PieSeries.vue CHANGED
@@ -15,7 +15,7 @@ const chart = useChartInstance();
15
15
  const seriesId = ref<string | null>(null);
16
16
 
17
17
  onMounted(() => {
18
- const id = chart.addPieSeries({ ...props.options, id: props.id });
18
+ const id = chart.addSeries('pie', { ...props.options, id: props.id });
19
19
  seriesId.value = id;
20
20
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
21
21
  if (props.data.length > 0) {
package/src/index.ts CHANGED
@@ -13,7 +13,6 @@ export type {
13
13
  AxisConfig,
14
14
  BarSeriesOptions,
15
15
  /** @deprecated Use {@link StackingMode} instead. */
16
- BarStacking,
17
16
  BuildHoverSnapshotsArgs,
18
17
  BuildLastSnapshotsArgs,
19
18
  CandlestickSeriesOptions,
@@ -24,7 +23,6 @@ export type {
24
23
  HoverInfo,
25
24
  LegendItem,
26
25
  /** @deprecated Use {@link TimePoint} instead. */
27
- LineData,
28
26
  LineSeriesOptions,
29
27
  NavigatorCandlePoint,
30
28
  NavigatorControllerParams,
@@ -68,7 +66,6 @@ export {
68
66
  catppuccin,
69
67
  computeTooltipPosition,
70
68
  createTheme,
71
- darkTheme,
72
69
  detectInterval,
73
70
  dracula,
74
71
  formatCompact,
@@ -81,7 +78,6 @@ export {
81
78
  highContrast,
82
79
  lavenderMist,
83
80
  lightPink,
84
- lightTheme,
85
81
  materialPalenight,
86
82
  minimalLight,
87
83
  mintBreeze,
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { formatTime, resolveAxisFontSize, resolveAxisTextColor } from '@wick-charts/core';
3
- import { computed, onUnmounted, watch } from 'vue';
2
+ import { mountAxisLabels } from '@wick-charts/core';
3
+ import { onMounted, onUnmounted, ref, watch } from 'vue';
4
4
 
5
- import { useVisibleRange } from '../composables';
6
5
  import { useChartInstance } from '../context';
7
6
 
8
7
  const props = defineProps<{
@@ -12,13 +11,8 @@ const props = defineProps<{
12
11
  minLabelSpacing?: number;
13
12
  }>();
14
13
 
15
- interface TrackedTick {
16
- opacity: number;
17
- addedAt: number;
18
- }
19
-
20
14
  const chart = useChartInstance();
21
- const visibleRange = useVisibleRange(chart);
15
+ const containerRef = ref<HTMLDivElement | null>(null);
22
16
 
23
17
  const applyDensity = () => {
24
18
  chart.setTimeAxisLabelDensity({
@@ -32,51 +26,20 @@ onUnmounted(() => {
32
26
  chart.setTimeAxisLabelDensity({ labelCount: null, minLabelSpacing: null });
33
27
  });
34
28
 
35
- const tickMap = new Map<number, TrackedTick>();
36
-
37
- const theme = computed(() => chart.getTheme());
38
-
39
- const tickData = computed(() => {
40
- // Access visibleRange.value to track dependency
41
- void visibleRange.value;
42
-
43
- const dataInterval = chart.getDataInterval();
44
- const { ticks: currentTicks, tickInterval } = chart.timeScale.niceTickValues(dataInterval);
45
- const currentSet = new Set(currentTicks);
46
- const now = performance.now();
47
-
48
- // Mark current ticks as visible
49
- for (const t of currentTicks) {
50
- if (!tickMap.has(t)) {
51
- tickMap.set(t, { opacity: 1, addedAt: now });
52
- } else {
53
- tickMap.get(t)!.opacity = 1;
54
- }
55
- }
56
-
57
- // Mark missing ticks for fade-out
58
- for (const [t, entry] of tickMap) {
59
- if (!currentSet.has(t)) {
60
- entry.opacity = 0;
61
- }
62
- }
63
-
64
- // Clean up old faded-out ticks
65
- for (const [t, entry] of tickMap) {
66
- if (entry.opacity === 0 && now - entry.addedAt > 5000) {
67
- tickMap.delete(t);
68
- }
69
- }
70
-
71
- return {
72
- allTicks: Array.from(tickMap.entries()),
73
- tickInterval,
74
- };
29
+ let cleanup: (() => void) | null = null;
30
+ onMounted(() => {
31
+ if (containerRef.value === null) return;
32
+ cleanup = mountAxisLabels({ chart, container: containerRef.value, axis: 'x' });
33
+ });
34
+ onUnmounted(() => {
35
+ cleanup?.();
36
+ cleanup = null;
75
37
  });
76
38
  </script>
77
39
 
78
40
  <template>
79
41
  <div
42
+ ref="containerRef"
80
43
  :style="{
81
44
  position: 'absolute',
82
45
  left: '0',
@@ -87,25 +50,5 @@ const tickData = computed(() => {
87
50
  display: 'flex',
88
51
  alignItems: 'center',
89
52
  }"
90
- >
91
- <span
92
- v-for="[time, entry] in tickData.allTicks"
93
- :key="time"
94
- :style="{
95
- position: 'absolute',
96
- left: chart.timeScale.timeToX(time) + 'px',
97
- transform: 'translateX(-50%)',
98
- color: resolveAxisTextColor(theme, 'x'),
99
- fontSize: resolveAxisFontSize(theme, 'x') + 'px',
100
- fontFamily: theme.typography.fontFamily,
101
- userSelect: 'none',
102
- whiteSpace: 'nowrap',
103
- opacity: entry.opacity,
104
- transition: 'opacity 0.25s ease',
105
- willChange: 'opacity',
106
- }"
107
- >
108
- {{ formatTime(time, tickData.tickInterval) }}
109
- </span>
110
- </div>
53
+ />
111
54
  </template>
package/src/ui/YAxis.vue CHANGED
@@ -1,8 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { resolveAxisFontSize, resolveAxisTextColor, type ValueFormatter } from '@wick-charts/core';
3
- import { computed, onMounted, onUnmounted, watch } from 'vue';
2
+ import { type ValueFormatter, mountAxisLabels } from '@wick-charts/core';
3
+ import { onMounted, onUnmounted, ref, watch } from 'vue';
4
4
 
5
- import { useVisibleRange } from '../composables';
6
5
  import { useChartInstance } from '../context';
7
6
 
8
7
  const props = defineProps<{
@@ -17,14 +16,8 @@ const props = defineProps<{
17
16
  minLabelSpacing?: number;
18
17
  }>();
19
18
 
20
- interface TrackedTick {
21
- opacity: number;
22
- addedAt: number;
23
- }
24
-
25
19
  const chart = useChartInstance();
26
- // Subscribe to visible range to trigger re-renders on viewport changes
27
- const visibleRange = useVisibleRange(chart);
20
+ const containerRef = ref<HTMLDivElement | null>(null);
28
21
 
29
22
  // Route the formatter through yScale so Crosshair / YLabel fallback use
30
23
  // the same function as the axis labels.
@@ -39,53 +32,24 @@ const applyDensity = () => {
39
32
  minLabelSpacing: props.minLabelSpacing ?? null,
40
33
  });
41
34
  };
42
- // Run during setup (before first paint) to match TimeAxis.vue and avoid a
43
- // post-mount density change that would cause a visible tick "jump".
44
35
  applyDensity();
45
36
  watch(() => [props.labelCount, props.minLabelSpacing], applyDensity);
46
37
  onUnmounted(() => chart.setYAxisLabelDensity({ labelCount: null, minLabelSpacing: null }));
47
38
 
48
- const tickMap = new Map<number, TrackedTick>();
49
-
50
- const theme = computed(() => chart.getTheme());
51
-
52
- const allTicks = computed(() => {
53
- // Access visibleRange.value to track dependency
54
- void visibleRange.value;
55
-
56
- const currentTicks = chart.yScale.niceTickValues();
57
- const currentSet = new Set(currentTicks);
58
- const now = performance.now();
59
-
60
- // Mark current ticks as visible
61
- for (const p of currentTicks) {
62
- if (!tickMap.has(p)) {
63
- tickMap.set(p, { opacity: 1, addedAt: now });
64
- } else {
65
- tickMap.get(p)!.opacity = 1;
66
- }
67
- }
68
-
69
- // Mark missing ticks for fade-out
70
- for (const [p, entry] of tickMap) {
71
- if (!currentSet.has(p)) {
72
- entry.opacity = 0;
73
- }
74
- }
75
-
76
- // Clean up old faded-out ticks
77
- for (const [p, entry] of tickMap) {
78
- if (entry.opacity === 0 && now - entry.addedAt > 5000) {
79
- tickMap.delete(p);
80
- }
81
- }
82
-
83
- return Array.from(tickMap.entries());
39
+ let cleanup: (() => void) | null = null;
40
+ onMounted(() => {
41
+ if (containerRef.value === null) return;
42
+ cleanup = mountAxisLabels({ chart, container: containerRef.value, axis: 'y' });
43
+ });
44
+ onUnmounted(() => {
45
+ cleanup?.();
46
+ cleanup = null;
84
47
  });
85
48
  </script>
86
49
 
87
50
  <template>
88
51
  <div
52
+ ref="containerRef"
89
53
  :style="{
90
54
  position: 'absolute',
91
55
  right: '0',
@@ -94,26 +58,5 @@ const allTicks = computed(() => {
94
58
  width: chart.yAxisWidth + 'px',
95
59
  pointerEvents: 'none',
96
60
  }"
97
- >
98
- <span
99
- v-for="[price, entry] in allTicks"
100
- :key="price"
101
- :style="{
102
- position: 'absolute',
103
- right: '8px',
104
- top: chart.yScale.valueToY(price) + 'px',
105
- transform: 'translateY(-50%)',
106
- color: resolveAxisTextColor(theme, 'y'),
107
- fontSize: resolveAxisFontSize(theme, 'y') + 'px',
108
- fontFamily: theme.typography.fontFamily,
109
- fontVariantNumeric: 'tabular-nums',
110
- userSelect: 'none',
111
- opacity: entry.opacity,
112
- transition: 'opacity 0.25s ease',
113
- willChange: 'opacity',
114
- }"
115
- >
116
- {{ chart.yScale.formatY(price) }}
117
- </span>
118
- </div>
61
+ />
119
62
  </template>