@wick-charts/vue 0.8.1 → 0.8.2

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 (38) hide show
  1. package/dist/BandSeries.vue.js +25 -24
  2. package/dist/BarSeries.vue.js +27 -26
  3. package/dist/CandlestickSeries.vue.js +25 -24
  4. package/dist/HeatmapSeries.vue.js +21 -20
  5. package/dist/LineSeries.vue.js +24 -23
  6. package/dist/PieSeries.vue.js +25 -24
  7. package/dist/composables.js +63 -36
  8. package/dist/core/dist/axis/dom-labels.js +64 -53
  9. package/dist/core/dist/chart/options.js +1 -1
  10. package/dist/core/dist/chart/y-target.js +38 -32
  11. package/dist/core/dist/chart.js +475 -439
  12. package/dist/core/dist/data/decimation.js +52 -51
  13. package/dist/core/dist/data/store.js +38 -20
  14. package/dist/core/dist/data/sync.js +80 -69
  15. package/dist/core/dist/perf/perf-hud.js +3 -3
  16. package/dist/core/dist/scales/tick-tracker.js +2 -1
  17. package/dist/core/dist/series/band.js +1 -1
  18. package/dist/core/dist/series/bar.js +240 -211
  19. package/dist/core/dist/series/base-multi-layer.js +267 -249
  20. package/dist/core/dist/series/candlestick.js +116 -116
  21. package/dist/core/dist/series/line.js +540 -455
  22. package/dist/core/dist/snapshots.js +96 -77
  23. package/dist/index.d.ts +22 -4
  24. package/dist/ui/InfoBar.vue.js +68 -68
  25. package/dist/ui/NumberFlow.vue.js +36 -27
  26. package/dist/ui/Tooltip.vue.js +127 -130
  27. package/package.json +2 -2
  28. package/src/BandSeries.vue +5 -3
  29. package/src/BarSeries.vue +5 -3
  30. package/src/CandlestickSeries.vue +5 -4
  31. package/src/ChartContainer.vue +1 -1
  32. package/src/HeatmapSeries.vue +5 -4
  33. package/src/LineSeries.vue +5 -3
  34. package/src/PieSeries.vue +6 -5
  35. package/src/composables.ts +64 -1
  36. package/src/ui/InfoBar.vue +11 -3
  37. package/src/ui/NumberFlow.vue +17 -3
  38. package/src/ui/Tooltip.vue +23 -12
@@ -1,165 +1,162 @@
1
- import { defineComponent as X, computed as h, ref as z, onMounted as $, onUnmounted as Y, useSlots as Z, watchEffect as K, openBlock as i, createElementBlock as n, Fragment as f, unref as d, normalizeStyle as m, createElementVNode as s, createTextVNode as H, toDisplayString as c, createCommentVNode as w, renderList as L, renderSlot as U } from "vue";
2
- import { buildHoverSnapshots as j, formatHeaderX as q } from "../core/dist/snapshots.js";
3
- import { resolveCandlestickBodyColor as G } from "../core/dist/theme/resolve.js";
4
- import { computeTooltipPosition as I } from "../core/dist/tooltip-position.js";
5
- import { formatCompact as J, formatPriceAdaptive as Q } from "../core/dist/utils/format.js";
6
- import { formatDate as ee } from "../core/dist/utils/time.js";
7
- import { useCrosshairPosition as te } from "../composables.js";
8
- import { useChartInstance as oe } from "../context.js";
9
- const ae = {
1
+ import { defineComponent as Y, computed as c, ref as z, onMounted as $, onUnmounted as K, useSlots as U, watchEffect as j, openBlock as r, createElementBlock as s, Fragment as x, unref as f, normalizeStyle as v, createElementVNode as u, toDisplayString as h, renderList as I, createCommentVNode as F, renderSlot as q } from "vue";
2
+ import { buildHoverSnapshots as G, formatTooltipHeader as J, formatRowValue as Q } from "../core/dist/snapshots.js";
3
+ import { resolveCandlestickBodyColor as Z } from "../core/dist/theme/resolve.js";
4
+ import { computeTooltipPosition as L } from "../core/dist/tooltip-position.js";
5
+ import { formatCompact as ee, formatPriceAdaptive as te } from "../core/dist/utils/format.js";
6
+ import { useCrosshairPosition as oe } from "../composables.js";
7
+ import { useChartInstance as ae } from "../context.js";
8
+ const le = {
10
9
  key: 0,
11
10
  style: { display: "grid", gridTemplateColumns: "auto 1fr", gap: "4px 12px" }
12
- }, le = { style: { opacity: 0.5 } }, ie = {
11
+ }, ie = { style: { opacity: 0.5 } }, ne = {
13
12
  key: 1,
14
13
  style: { display: "flex", alignItems: "center", gap: "8px", padding: "2px 0" }
15
- }, ne = { style: {
14
+ }, re = { style: {
16
15
  opacity: 0.6,
17
16
  flex: "1",
18
17
  minWidth: 0,
19
18
  overflow: "hidden",
20
19
  textOverflow: "ellipsis",
21
20
  whiteSpace: "nowrap"
22
- } }, re = {
21
+ } }, se = {
23
22
  key: 2,
24
23
  style: { display: "flex", alignItems: "center", gap: "8px", padding: "2px 0" }
25
- }, se = { style: { opacity: 0.6, flex: "1" } }, ue = ["data-measured"], ye = /* @__PURE__ */ X({
24
+ }, ue = { style: { opacity: 0.6, flex: "1" } }, pe = ["data-measured"], ge = /* @__PURE__ */ Y({
26
25
  __name: "Tooltip",
27
26
  props: {
28
27
  sort: {},
29
28
  format: { type: Function }
30
29
  },
31
- setup(T) {
32
- const F = T, O = h(() => F.sort ?? "none"), x = h(
33
- () => F.format ?? ((a, r) => r === "volume" ? J(a) : Q(a))
34
- ), t = oe(), u = te(t), C = z(0), W = () => {
35
- C.value++;
30
+ setup(V) {
31
+ const W = V, O = c(() => W.sort ?? "none"), g = c(
32
+ () => W.format ?? ((l, n) => n === "volume" ? ee(l) : te(l))
33
+ ), o = ae(), p = oe(o), y = z(0), w = () => {
34
+ y.value++;
36
35
  };
37
36
  $(() => {
38
- t.on("overlayChange", W), t.getSeriesIds().length > 0 && C.value++;
39
- }), Y(() => {
40
- t.off("overlayChange", W);
37
+ o.on("overlayChange", w), o.on("seriesFrame", w), o.getSeriesIds().length > 0 && y.value++;
38
+ }), K(() => {
39
+ o.off("overlayChange", w), o.off("seriesFrame", w);
41
40
  });
42
- const o = h(() => t.getTheme()), V = h(() => t.getDataInterval()), v = h(() => (C.value, u.value ? j(t, {
43
- time: u.value.time,
41
+ const t = c(() => (y.value, o.getTheme())), T = c(() => (y.value, o.getDataInterval())), d = c(() => (y.value, p.value ? G(o, {
42
+ time: p.value.time,
44
43
  sort: O.value,
45
44
  cacheKey: "tooltip"
46
- }) : [])), _ = h(() => u.value?.time ?? 0), A = Z(), y = h(() => typeof A.default == "function"), B = z(null), b = z(null);
47
- let S = null;
48
- K((a) => {
49
- if (!y.value) return;
50
- const r = B.value;
51
- !r || typeof ResizeObserver > "u" || (S = new ResizeObserver((e) => {
52
- const l = e[0]?.contentRect;
53
- if (!l) return;
54
- const k = b.value;
55
- (!k || k.width !== l.width || k.height !== l.height) && (b.value = { width: l.width, height: l.height });
56
- }), S.observe(r), a(() => {
57
- S?.disconnect(), S = null;
45
+ }) : [])), R = c(() => p.value?.time ?? 0), A = U(), b = c(() => typeof A.default == "function"), B = z(null), k = z(null);
46
+ let C = null;
47
+ j((l) => {
48
+ if (!b.value) return;
49
+ const n = B.value;
50
+ !n || typeof ResizeObserver > "u" || (C = new ResizeObserver((e) => {
51
+ const a = e[0]?.contentRect;
52
+ if (!a) return;
53
+ const S = k.value;
54
+ (!S || S.width !== a.width || S.height !== a.height) && (k.value = { width: a.width, height: a.height });
55
+ }), C.observe(n), l(() => {
56
+ C?.disconnect(), C = null;
58
57
  }));
59
58
  });
60
- const g = h(() => {
61
- if (!u.value || v.value.length === 0) return null;
62
- const a = t.getMediaSize(), r = t.plotLeft, e = t.getLayout().chartArea.width, l = a.height - t.xAxisHeight;
63
- if (y.value) {
64
- const p = b.value;
65
- return p ? I({
66
- x: u.value.mediaX,
67
- y: u.value.mediaY,
68
- chartLeft: r,
59
+ const m = c(() => {
60
+ if (!p.value || d.value.length === 0) return null;
61
+ const l = o.getMediaSize(), n = o.plotLeft, e = o.getLayout().chartArea.width, a = l.height - o.xAxisHeight;
62
+ if (b.value) {
63
+ const i = k.value;
64
+ return i ? L({
65
+ x: p.value.mediaX,
66
+ y: p.value.mediaY,
67
+ chartLeft: n,
69
68
  chartWidth: e,
70
- chartHeight: l,
71
- tooltipWidth: p.width,
72
- tooltipHeight: p.height
73
- }) : { left: r, top: 0 };
69
+ chartHeight: a,
70
+ tooltipWidth: i.width,
71
+ tooltipHeight: i.height
72
+ }) : { left: n, top: 0 };
74
73
  }
75
- const k = v.value.some((p) => "open" in p.data), P = v.value.filter((p) => !("open" in p.data)).length, D = v.value.some((p) => "high" in p.data && !("open" in p.data)) ? 220 : 160, M = k ? 140 : 40 + P * 22;
76
- return I({
77
- x: u.value.mediaX,
78
- y: u.value.mediaY,
79
- chartLeft: r,
74
+ const S = d.value.some((i) => "open" in i.data), P = d.value.filter((i) => !("open" in i.data)).length, M = d.value.some((i) => "high" in i.data && !("open" in i.data)), D = d.value.some((i) => i.share !== void 0);
75
+ let _ = 160;
76
+ M && (_ = 220), D && (_ = 240);
77
+ const X = S ? 140 : 40 + P * 22;
78
+ return L({
79
+ x: p.value.mediaX,
80
+ y: p.value.mediaY,
81
+ chartLeft: n,
80
82
  chartWidth: e,
81
- chartHeight: l,
82
- tooltipWidth: D,
83
- tooltipHeight: M
83
+ chartHeight: a,
84
+ tooltipWidth: _,
85
+ tooltipHeight: X
84
86
  });
85
- }), E = h(() => !y.value || b.value !== null);
86
- function R(a) {
87
- return "open" in a;
87
+ }), E = c(() => !b.value || k.value !== null);
88
+ function H(l) {
89
+ return "open" in l;
88
90
  }
89
- function N(a) {
90
- return "high" in a && !R(a);
91
+ function N(l) {
92
+ return "high" in l && !H(l);
91
93
  }
92
- return (a, r) => (i(), n(f, null, [
93
- d(u) && v.value.length > 0 && g.value && !y.value ? (i(), n("div", {
94
+ return (l, n) => (r(), s(x, null, [
95
+ f(p) && d.value.length > 0 && m.value && !b.value ? (r(), s("div", {
94
96
  key: 0,
95
- style: m({
97
+ style: v({
96
98
  position: "absolute",
97
- left: g.value.left + "px",
98
- top: g.value.top + "px",
99
+ left: m.value.left + "px",
100
+ top: m.value.top + "px",
99
101
  pointerEvents: "none",
100
- background: o.value.tooltip.background,
102
+ background: t.value.tooltip.background,
101
103
  backdropFilter: "blur(12px)",
102
104
  WebkitBackdropFilter: "blur(12px)",
103
- border: "1px solid " + o.value.tooltip.borderColor,
105
+ border: "1px solid " + t.value.tooltip.borderColor,
104
106
  borderRadius: "8px",
105
107
  padding: "10px 14px",
106
108
  boxShadow: "0 4px 16px rgba(0,0,0,0.1), 0 1px 4px rgba(0,0,0,0.06)",
107
- fontSize: o.value.tooltip.fontSize + "px",
108
- fontFamily: o.value.typography.fontFamily,
109
+ fontSize: t.value.tooltip.fontSize + "px",
110
+ fontFamily: t.value.typography.fontFamily,
109
111
  fontVariantNumeric: "tabular-nums",
110
- color: o.value.tooltip.textColor,
112
+ color: t.value.tooltip.textColor,
111
113
  minWidth: "140px",
112
114
  zIndex: 10,
113
115
  transition: "opacity 0.15s ease"
114
116
  })
115
117
  }, [
116
- s("div", {
117
- style: m({
118
- fontSize: o.value.axis.fontSize + "px",
119
- color: o.value.axis.textColor,
118
+ u("div", {
119
+ style: v({
120
+ fontSize: t.value.axis.fontSize + "px",
121
+ color: t.value.axis.textColor,
120
122
  marginBottom: "8px",
121
123
  paddingBottom: "6px",
122
- borderBottom: "1px solid " + o.value.tooltip.borderColor,
124
+ borderBottom: "1px solid " + t.value.tooltip.borderColor,
123
125
  letterSpacing: "0.02em"
124
126
  })
125
- }, [
126
- d(t).timeScale.getType() === "time" ? (i(), n(f, { key: 0 }, [
127
- H(c(d(ee)(_.value, { locale: d(t).timeScale.getLocale(), timeZone: d(t).timeScale.getTimeZone() })), 1)
128
- ], 64)) : w("", !0),
129
- H(" " + c(d(q)(d(t), { time: _.value, snapshots: v.value, interval: V.value })), 1)
130
- ], 4),
131
- (i(!0), n(f, null, L(v.value, (e) => (i(), n(f, {
127
+ }, h(f(J)(f(o), { time: R.value, snapshots: d.value, interval: T.value })), 5),
128
+ (r(!0), s(x, null, I(d.value, (e) => (r(), s(x, {
132
129
  key: e.id
133
130
  }, [
134
- R(e.data) ? (i(), n("div", ae, [
135
- (i(!0), n(f, null, L([
131
+ H(e.data) ? (r(), s("div", le, [
132
+ (r(!0), s(x, null, I([
136
133
  { label: "Open", val: e.data.open, field: "open" },
137
134
  { label: "High", val: e.data.high, field: "high" },
138
135
  { label: "Low", val: e.data.low, field: "low" },
139
136
  { label: "Close", val: e.data.close, field: "close" }
140
- ], (l) => (i(), n(f, {
141
- key: l.label
137
+ ], (a) => (r(), s(x, {
138
+ key: a.label
142
139
  }, [
143
- s("span", le, c(l.label), 1),
144
- s("span", {
145
- style: m({
140
+ u("span", ie, h(a.label), 1),
141
+ u("span", {
142
+ style: v({
146
143
  fontWeight: 600,
147
- color: d(G)(
148
- e.data.close >= e.data.open ? o.value.candlestick.up.body : o.value.candlestick.down.body
144
+ color: f(Z)(
145
+ e.data.close >= e.data.open ? t.value.candlestick.up.body : t.value.candlestick.down.body
149
146
  ),
150
147
  textAlign: "right"
151
148
  })
152
- }, c(x.value(l.val, l.field)), 5)
149
+ }, h(g.value(a.val, a.field)), 5)
153
150
  ], 64))), 128)),
154
- e.data.volume != null ? (i(), n(f, { key: 0 }, [
155
- r[0] || (r[0] = s("span", { style: { opacity: 0.5 } }, "Volume", -1)),
156
- s("span", {
157
- style: m({ fontWeight: 600, color: o.value.tooltip.textColor, textAlign: "right" })
158
- }, c(x.value(e.data.volume, "volume")), 5)
159
- ], 64)) : w("", !0)
160
- ])) : N(e.data) ? (i(), n("div", ie, [
161
- s("span", {
162
- style: m({
151
+ e.data.volume != null ? (r(), s(x, { key: 0 }, [
152
+ n[0] || (n[0] = u("span", { style: { opacity: 0.5 } }, "Volume", -1)),
153
+ u("span", {
154
+ style: v({ fontWeight: 600, color: t.value.tooltip.textColor, textAlign: "right" })
155
+ }, h(g.value(e.data.volume, "volume")), 5)
156
+ ], 64)) : F("", !0)
157
+ ])) : N(e.data) ? (r(), s("div", ne, [
158
+ u("span", {
159
+ style: v({
163
160
  width: "8px",
164
161
  height: "8px",
165
162
  borderRadius: "2px",
@@ -167,13 +164,13 @@ const ae = {
167
164
  flexShrink: 0
168
165
  })
169
166
  }, null, 4),
170
- s("span", ne, c(e.label ?? "Range"), 1),
171
- s("span", {
172
- style: m({ fontWeight: 600, color: e.color, whiteSpace: "nowrap" })
173
- }, c(x.value(e.data.low, "low")) + " – " + c(x.value(e.data.high, "high")), 5)
174
- ])) : (i(), n("div", re, [
175
- s("span", {
176
- style: m({
167
+ u("span", re, h(e.label ?? "Range"), 1),
168
+ u("span", {
169
+ style: v({ fontWeight: 600, color: e.color, whiteSpace: "nowrap" })
170
+ }, h(g.value(e.data.low, "low")) + " – " + h(g.value(e.data.high, "high")), 5)
171
+ ])) : (r(), s("div", se, [
172
+ u("span", {
173
+ style: v({
177
174
  width: "8px",
178
175
  height: "8px",
179
176
  borderRadius: "50%",
@@ -181,49 +178,49 @@ const ae = {
181
178
  flexShrink: 0
182
179
  })
183
180
  }, null, 4),
184
- s("span", se, c(e.label ?? "Value"), 1),
185
- s("span", {
186
- style: m({ fontWeight: 600, color: e.color })
187
- }, c(x.value(e.data.value, "value")), 5)
181
+ u("span", ue, h(e.label ?? "Value"), 1),
182
+ u("span", {
183
+ style: v({ fontWeight: 600, color: e.color })
184
+ }, h(f(Q)(e, (a) => g.value(a, "value"))), 5)
188
185
  ]))
189
186
  ], 64))), 128))
190
- ], 4)) : w("", !0),
191
- d(u) && v.value.length > 0 && g.value && y.value ? (i(), n("div", {
187
+ ], 4)) : F("", !0),
188
+ f(p) && d.value.length > 0 && m.value && b.value ? (r(), s("div", {
192
189
  key: 1,
193
190
  ref_key: "containerRef",
194
191
  ref: B,
195
192
  "data-measured": E.value ? "true" : "false",
196
- style: m({
193
+ style: v({
197
194
  position: "absolute",
198
- left: g.value.left + "px",
199
- top: g.value.top + "px",
195
+ left: m.value.left + "px",
196
+ top: m.value.top + "px",
200
197
  pointerEvents: "none",
201
- background: o.value.tooltip.background,
198
+ background: t.value.tooltip.background,
202
199
  backdropFilter: "blur(12px)",
203
200
  WebkitBackdropFilter: "blur(12px)",
204
- border: "1px solid " + o.value.tooltip.borderColor,
201
+ border: "1px solid " + t.value.tooltip.borderColor,
205
202
  borderRadius: "8px",
206
203
  padding: "10px 14px",
207
204
  boxShadow: "0 4px 16px rgba(0,0,0,0.1), 0 1px 4px rgba(0,0,0,0.06)",
208
- fontFamily: o.value.typography.fontFamily,
209
- fontSize: o.value.tooltip.fontSize + "px",
205
+ fontFamily: t.value.typography.fontFamily,
206
+ fontSize: t.value.tooltip.fontSize + "px",
210
207
  fontVariantNumeric: "tabular-nums",
211
- color: o.value.tooltip.textColor,
208
+ color: t.value.tooltip.textColor,
212
209
  width: "max-content",
213
- maxWidth: d(t).getLayout().chartArea.width + "px",
210
+ maxWidth: f(o).getLayout().chartArea.width + "px",
214
211
  boxSizing: "border-box",
215
212
  zIndex: 10,
216
- visibility: b.value !== null ? "visible" : "hidden"
213
+ visibility: k.value !== null ? "visible" : "hidden"
217
214
  })
218
215
  }, [
219
- U(a.$slots, "default", {
220
- snapshots: v.value,
221
- time: _.value
216
+ q(l.$slots, "default", {
217
+ snapshots: d.value,
218
+ time: R.value
222
219
  })
223
- ], 12, ue)) : w("", !0)
220
+ ], 12, pe)) : F("", !0)
224
221
  ], 64));
225
222
  }
226
223
  });
227
224
  export {
228
- ye as default
225
+ ge as default
229
226
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wick-charts/vue",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Realtime, animated canvas charts for Vue: candlestick, line, bar, heatmap, pie and more. Tree-shakeable, zero runtime deps.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,7 +51,7 @@
51
51
  "vue": ">=3.3.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@wick-charts/core": "^0.8.1"
54
+ "@wick-charts/core": "^0.8.2"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "vite build"
@@ -3,6 +3,7 @@ import type { BandData, BandSeriesOptions, SeriesSyncState } from '@wick-charts/
3
3
  import { BandSeriesDef, EMPTY_SYNC_STATE, syncLayers, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
+ import { rawSeriesData, seriesDataSource } from './composables';
6
7
  import { useChartInstance } from './context';
7
8
 
8
9
  const props = withDefaults(
@@ -58,7 +59,7 @@ function createSeries(data: BandData): void {
58
59
  }
59
60
 
60
61
  onMounted(() => {
61
- createSeries(props.data);
62
+ createSeries(rawSeriesData(props.data));
62
63
  });
63
64
 
64
65
  onUnmounted(() => {
@@ -66,8 +67,9 @@ onUnmounted(() => {
66
67
  });
67
68
 
68
69
  watch(
69
- () => props.data,
70
- (data) => {
70
+ seriesDataSource(() => props.data),
71
+ () => {
72
+ const data = rawSeriesData(props.data);
71
73
  const id = seriesId.value;
72
74
  if (!id) return;
73
75
 
package/src/BarSeries.vue CHANGED
@@ -3,6 +3,7 @@ import type { BarSeriesOptions, MultiLayerData, SeriesSyncState } from '@wick-ch
3
3
  import { BarSeriesDef, EMPTY_SYNC_STATE, syncLayers, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
+ import { rawSeriesData, seriesDataSource } from './composables';
6
7
  import { useChartInstance } from './context';
7
8
 
8
9
  const props = withDefaults(
@@ -54,7 +55,7 @@ function createSeries(data: MultiLayerData): void {
54
55
  }
55
56
 
56
57
  onMounted(() => {
57
- createSeries(props.data);
58
+ createSeries(rawSeriesData(props.data));
58
59
  });
59
60
 
60
61
  onUnmounted(() => {
@@ -62,8 +63,9 @@ onUnmounted(() => {
62
63
  });
63
64
 
64
65
  watch(
65
- () => props.data,
66
- (data) => {
66
+ seriesDataSource(() => props.data),
67
+ () => {
68
+ const data = rawSeriesData(props.data);
67
69
  const id = seriesId.value;
68
70
  if (!id) return;
69
71
 
@@ -3,6 +3,7 @@ import type { CandlestickData, CandlestickSeriesOptions, OHLCInput, SeriesSyncSt
3
3
  import { CandlestickSeriesDef, EMPTY_SYNC_STATE, syncSeriesLayer, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
+ import { rawSeriesData, seriesDataSource } from './composables';
6
7
  import { useChartInstance } from './context';
7
8
 
8
9
  const props = withDefaults(
@@ -47,7 +48,7 @@ onMounted(() => {
47
48
  // Initial data load — Vue's `watch` is lazy by default, so the watcher
48
49
  // below only fires on subsequent `data` prop mutations. Explicitly apply
49
50
  // the first value here so components with static data render immediately.
50
- applyData(id, props.data);
51
+ applyData(id, rawSeriesData(props.data));
51
52
  chart.setSeriesVisible(id, props.visible);
52
53
  });
53
54
 
@@ -56,12 +57,12 @@ onUnmounted(() => {
56
57
  });
57
58
 
58
59
  watch(
59
- () => props.data,
60
- (data) => {
60
+ seriesDataSource(() => props.data),
61
+ () => {
61
62
  const id = seriesId.value;
62
63
  if (!id) return;
63
64
 
64
- applyData(id, data);
65
+ applyData(id, rawSeriesData(props.data));
65
66
  },
66
67
  );
67
68
 
@@ -48,7 +48,7 @@ const props = withDefaults(
48
48
  * Viewport padding. `top`/`bottom` are in pixels. `left`/`right` accept either pixels (`50`)
49
49
  * or data intervals (`{ intervals: 3 }`). Set to 0 for edge-to-edge sparklines. Updates are
50
50
  * applied reactively when this prop or the measured header height changes.
51
- * Defaults: `{ top: 20, bottom: 20, right: { intervals: 3 }, left: { intervals: 0 } }`.
51
+ * Defaults: `{ top: 20, bottom: 20, right: { intervals: 3 }, left: { intervals: 0.5 } }`.
52
52
  */
53
53
  padding?: {
54
54
  top?: number;
@@ -3,6 +3,7 @@ import type { HeatmapCellData, HeatmapSeriesOptions } from '@wick-charts/core';
3
3
  import { HeatmapSeriesDef } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
+ import { rawSeriesData, seriesDataSource } from './composables';
6
7
  import { useChartInstance } from './context';
7
8
 
8
9
  const props = withDefaults(
@@ -28,7 +29,7 @@ onMounted(() => {
28
29
  seriesId.value = id;
29
30
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
30
31
  if (props.data.length > 0) {
31
- chart.setSeriesData(id, props.data);
32
+ chart.setSeriesData(id, rawSeriesData(props.data));
32
33
  }
33
34
  chart.setSeriesVisible(id, props.visible);
34
35
  });
@@ -38,12 +39,12 @@ onUnmounted(() => {
38
39
  });
39
40
 
40
41
  watch(
41
- () => props.data,
42
- (data) => {
42
+ seriesDataSource(() => props.data),
43
+ () => {
43
44
  // No length guard: an empty array is a legitimate update (filter yields
44
45
  // no rows) and must clear the previous cells, matching the React wrapper.
45
46
  if (seriesId.value) {
46
- chart.setSeriesData(seriesId.value, data);
47
+ chart.setSeriesData(seriesId.value, rawSeriesData(props.data));
47
48
  }
48
49
  },
49
50
  );
@@ -3,6 +3,7 @@ import type { LineSeriesOptions, MultiLayerData, SeriesSyncState } from '@wick-c
3
3
  import { EMPTY_SYNC_STATE, LineSeriesDef, syncLayers, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
+ import { rawSeriesData, seriesDataSource } from './composables';
6
7
  import { useChartInstance } from './context';
7
8
 
8
9
  const props = withDefaults(
@@ -54,7 +55,7 @@ function createSeries(data: MultiLayerData): void {
54
55
  }
55
56
 
56
57
  onMounted(() => {
57
- createSeries(props.data);
58
+ createSeries(rawSeriesData(props.data));
58
59
  });
59
60
 
60
61
  onUnmounted(() => {
@@ -62,8 +63,9 @@ onUnmounted(() => {
62
63
  });
63
64
 
64
65
  watch(
65
- () => props.data,
66
- (data) => {
66
+ seriesDataSource(() => props.data),
67
+ () => {
68
+ const data = rawSeriesData(props.data);
67
69
  const id = seriesId.value;
68
70
  if (!id) return;
69
71
 
package/src/PieSeries.vue CHANGED
@@ -3,6 +3,7 @@ import type { PieSeriesOptions, PieSliceData } from '@wick-charts/core';
3
3
  import { PieSeriesDef } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
+ import { rawSeriesData, seriesDataSource } from './composables';
6
7
  import { useChartInstance } from './context';
7
8
 
8
9
  const props = withDefaults(
@@ -28,7 +29,7 @@ onMounted(() => {
28
29
  seriesId.value = id;
29
30
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
30
31
  if (props.data.length > 0) {
31
- chart.setSeriesData(id, props.data);
32
+ chart.setSeriesData(id, rawSeriesData(props.data));
32
33
  }
33
34
  chart.setSeriesVisible(id, props.visible);
34
35
  });
@@ -38,10 +39,10 @@ onUnmounted(() => {
38
39
  });
39
40
 
40
41
  watch(
41
- () => props.data,
42
- (data) => {
43
- if (seriesId.value && data.length > 0) {
44
- chart.setSeriesData(seriesId.value, data);
42
+ seriesDataSource(() => props.data),
43
+ () => {
44
+ if (seriesId.value && props.data.length > 0) {
45
+ chart.setSeriesData(seriesId.value, rawSeriesData(props.data));
45
46
  }
46
47
  },
47
48
  );
@@ -1,5 +1,68 @@
1
1
  import type { ChartInstance, CrosshairPosition, VisibleRange, YAxisPosition, YRange } from '@wick-charts/core';
2
- import { type Ref, onMounted, onUnmounted, ref } from 'vue';
2
+ import { type Ref, onMounted, onUnmounted, ref, toRaw } from 'vue';
3
+
4
+ /** A `{ label?, color?, data }` layer — an object carrying a `data` array. */
5
+ function isLayerObject(value: unknown): value is { data: unknown[] } {
6
+ return (
7
+ typeof value === 'object' && value !== null && !Array.isArray(value) && Array.isArray(Reflect.get(value, 'data'))
8
+ );
9
+ }
10
+
11
+ /** Each layer's point array, read through whatever Proxy the prop arrived in. */
12
+ function layerArrays(data: unknown): unknown[][] {
13
+ if (isLayerObject(data)) return [data.data];
14
+ if (!Array.isArray(data)) return [];
15
+
16
+ const layered = Array.isArray(data[0]) || isLayerObject(data[0]);
17
+ if (!layered) return [data];
18
+
19
+ return data.map((layer) => (isLayerObject(layer) ? layer.data : (layer as unknown[])));
20
+ }
21
+
22
+ /**
23
+ * A series `data` prop with Vue's reactive Proxies taken off the top level and
24
+ * each layer, so the chart reads plain arrays: a `ref` / `reactive` array of
25
+ * 100k points read through its Proxy costs the engine several times more.
26
+ */
27
+ export function rawSeriesData<D>(data: D): D {
28
+ const raw = toRaw(data);
29
+ const rawLayer = (layer: unknown) => {
30
+ if (!isLayerObject(layer)) return toRaw(layer);
31
+
32
+ const plain = toRaw(layer);
33
+
34
+ return { ...plain, data: toRaw(plain.data) };
35
+ };
36
+
37
+ if (!Array.isArray(raw)) return rawLayer(raw) as D;
38
+
39
+ const layered = Array.isArray(raw[0]) || isLayerObject(raw[0]);
40
+ if (!layered) return raw;
41
+
42
+ return raw.map(rawLayer) as D;
43
+ }
44
+
45
+ /**
46
+ * Watch source for a series `data` prop. Besides a new array, it re-fires on
47
+ * an in-place `push` / `splice` or an edit of a layer's last point on a
48
+ * reactive array — it reads each layer's length and last point through the
49
+ * Proxy instead of deep-watching every point. A `shallowRef` still needs a
50
+ * new array (or `triggerRef`).
51
+ */
52
+ export function seriesDataSource(getData: () => unknown): () => unknown[] {
53
+ return () => {
54
+ const data = getData();
55
+ const deps: unknown[] = [data];
56
+ for (const points of layerArrays(data)) {
57
+ deps.push(points, points.length);
58
+
59
+ const last = points[points.length - 1];
60
+ if (typeof last === 'object' && last !== null) deps.push(...Object.values(last));
61
+ }
62
+
63
+ return deps;
64
+ };
65
+ }
3
66
 
4
67
  export function useVisibleRange(chart: ChartInstance): Ref<VisibleRange> {
5
68
  const range = ref<VisibleRange>(chart.getVisibleRange()) as Ref<VisibleRange>;