@wick-charts/vue 0.4.1 → 0.5.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.
Files changed (146) hide show
  1. package/README.md +5 -3
  2. package/dist/BarSeries.vue.js +44 -0
  3. package/dist/BarSeries.vue2.js +4 -0
  4. package/dist/CandlestickSeries.vue.js +45 -0
  5. package/dist/CandlestickSeries.vue2.js +4 -0
  6. package/dist/ChartContainer.vue.js +168 -0
  7. package/dist/ChartContainer.vue2.js +4 -0
  8. package/dist/LineSeries.vue.js +44 -0
  9. package/dist/LineSeries.vue2.js +4 -0
  10. package/dist/PieSeries.vue.js +34 -0
  11. package/dist/PieSeries.vue2.js +4 -0
  12. package/dist/composables.js +44 -0
  13. package/dist/context.js +25 -0
  14. package/dist/core/dist/animation/animator.js +72 -0
  15. package/dist/core/dist/animation/config.js +145 -0
  16. package/dist/core/dist/animation/easing.js +4 -0
  17. package/dist/core/dist/animation/scalar-spring.js +57 -0
  18. package/dist/core/dist/animation/spring.js +11 -0
  19. package/dist/core/dist/animation/time.js +19 -0
  20. package/dist/core/dist/animation/viewport-engine.js +174 -0
  21. package/dist/core/dist/animation/visible-range-spring.js +64 -0
  22. package/dist/core/dist/animation/y-range-hermite.js +72 -0
  23. package/dist/core/dist/animation/y-range-snap.js +31 -0
  24. package/dist/core/dist/animation/y-range-spring.js +59 -0
  25. package/dist/core/dist/axis/dom-labels.js +60 -0
  26. package/dist/core/dist/canvas-manager.js +109 -0
  27. package/dist/core/dist/chart/edge-indicators.js +26 -0
  28. package/dist/core/dist/chart/fit-to-data.js +9 -0
  29. package/dist/core/dist/chart/last-value.js +31 -0
  30. package/dist/core/dist/chart/options.js +35 -0
  31. package/dist/core/dist/chart/pan-zoom-math.js +66 -0
  32. package/dist/core/dist/chart/streaming-cadence.js +70 -0
  33. package/dist/core/dist/chart/streaming-target.js +22 -0
  34. package/dist/core/dist/chart/viewport-padding.js +6 -0
  35. package/dist/core/dist/chart/y-target.js +24 -0
  36. package/dist/core/dist/chart.js +1495 -0
  37. package/dist/core/dist/components/crosshair.js +9 -0
  38. package/dist/core/dist/components/edge-indicator.js +36 -0
  39. package/dist/core/dist/components/grid.js +22 -0
  40. package/dist/core/dist/components/marker.js +41 -0
  41. package/dist/core/dist/components/reference-line.js +48 -0
  42. package/dist/core/dist/components/time-region.js +35 -0
  43. package/dist/core/dist/data/decimation.js +54 -0
  44. package/dist/core/dist/data/store.js +76 -0
  45. package/dist/core/dist/data/sync.js +47 -0
  46. package/dist/core/dist/events.js +19 -0
  47. package/dist/core/dist/interactions/handler.js +84 -0
  48. package/dist/core/dist/interactions/pan.js +26 -0
  49. package/dist/core/dist/interactions/zoom.js +21 -0
  50. package/dist/core/dist/navigator/controller.js +374 -0
  51. package/dist/core/dist/navigator/decimate.js +44 -0
  52. package/dist/core/dist/navigator/interactions.js +28 -0
  53. package/dist/core/dist/navigator/render.js +68 -0
  54. package/dist/core/dist/perf/counting-context.js +19 -0
  55. package/dist/core/dist/perf/perf-hud.js +453 -0
  56. package/dist/core/dist/perf/perf-monitor.js +132 -0
  57. package/dist/core/dist/render-scheduler.js +31 -0
  58. package/dist/core/dist/scales/tick-tracker.js +110 -0
  59. package/dist/core/dist/scales/x-scale.js +145 -0
  60. package/dist/core/dist/scales/y-scale.js +156 -0
  61. package/dist/core/dist/series/bar.js +341 -0
  62. package/dist/core/dist/series/base-multi-layer.js +438 -0
  63. package/dist/core/dist/series/candlestick.js +481 -0
  64. package/dist/core/dist/series/definition.js +11 -0
  65. package/dist/core/dist/series/line.js +446 -0
  66. package/dist/core/dist/series/painters/builtins.js +36 -0
  67. package/dist/core/dist/series/painters/canvas-path.js +79 -0
  68. package/dist/core/dist/series/painters/resolve.js +32 -0
  69. package/dist/core/dist/series/pie.js +545 -0
  70. package/dist/core/dist/series/register-builtins.js +11 -0
  71. package/dist/core/dist/series/stack-math.js +17 -0
  72. package/dist/core/dist/snapshots.js +107 -0
  73. package/dist/core/dist/theme/create.js +176 -0
  74. package/dist/core/dist/theme/resolve.js +14 -0
  75. package/dist/core/dist/theme/themes/andromeda.js +33 -0
  76. package/dist/core/dist/theme/themes/ayu-mirage.js +33 -0
  77. package/dist/core/dist/theme/themes/catppuccin.js +33 -0
  78. package/dist/core/dist/theme/themes/dracula.js +35 -0
  79. package/dist/core/dist/theme/themes/github-light.js +34 -0
  80. package/dist/core/dist/theme/themes/gruvbox.js +33 -0
  81. package/dist/core/dist/theme/themes/handwritten.js +101 -0
  82. package/dist/core/dist/theme/themes/high-contrast.js +33 -0
  83. package/dist/core/dist/theme/themes/lavender-mist.js +33 -0
  84. package/dist/core/dist/theme/themes/light-pink.js +34 -0
  85. package/dist/core/dist/theme/themes/material-palenight.js +33 -0
  86. package/dist/core/dist/theme/themes/minimal-light.js +34 -0
  87. package/dist/core/dist/theme/themes/mint-breeze.js +33 -0
  88. package/dist/core/dist/theme/themes/monokai-pro.js +33 -0
  89. package/dist/core/dist/theme/themes/night-owl.js +35 -0
  90. package/dist/core/dist/theme/themes/one-dark-pro.js +33 -0
  91. package/dist/core/dist/theme/themes/panda.js +33 -0
  92. package/dist/core/dist/theme/themes/peach-cream.js +33 -0
  93. package/dist/core/dist/theme/themes/quiet-light.js +37 -0
  94. package/dist/core/dist/theme/themes/rose-pine-dawn.js +33 -0
  95. package/dist/core/dist/theme/themes/sand-dune.js +33 -0
  96. package/dist/core/dist/theme/themes/solarized-light.js +34 -0
  97. package/dist/core/dist/tooltip-position.js +10 -0
  98. package/dist/core/dist/utils/color.js +37 -0
  99. package/dist/core/dist/utils/format.js +29 -0
  100. package/dist/core/dist/utils/math.js +26 -0
  101. package/dist/core/dist/utils/poisoned-data-reporter.js +15 -0
  102. package/dist/core/dist/utils/time.js +41 -0
  103. package/dist/index.d.ts +1096 -63
  104. package/dist/index.js +142 -7754
  105. package/dist/ui/Crosshair.vue.js +53 -0
  106. package/dist/ui/Crosshair.vue2.js +4 -0
  107. package/dist/ui/InfoBar.vue.js +143 -0
  108. package/dist/ui/InfoBar.vue2.js +4 -0
  109. package/dist/ui/Legend.vue.js +212 -0
  110. package/dist/ui/Legend.vue2.js +4 -0
  111. package/dist/ui/Marker.vue.js +47 -0
  112. package/dist/ui/Marker.vue2.js +4 -0
  113. package/dist/ui/Navigator.vue.js +58 -0
  114. package/dist/ui/Navigator.vue2.js +4 -0
  115. package/dist/ui/NumberFlow.vue.js +69 -0
  116. package/dist/ui/NumberFlow.vue2.js +4 -0
  117. package/dist/ui/PieLegend.vue.js +132 -0
  118. package/dist/ui/PieLegend.vue2.js +4 -0
  119. package/dist/ui/PieTooltip.vue.js +135 -0
  120. package/dist/ui/PieTooltip.vue2.js +4 -0
  121. package/dist/ui/ReferenceLine.vue.js +44 -0
  122. package/dist/ui/ReferenceLine.vue2.js +4 -0
  123. package/dist/ui/TimeAxis.vue.js +43 -0
  124. package/dist/ui/TimeAxis.vue2.js +4 -0
  125. package/dist/ui/TimeRegion.vue.js +41 -0
  126. package/dist/ui/TimeRegion.vue2.js +4 -0
  127. package/dist/ui/Title.vue.js +54 -0
  128. package/dist/ui/Title.vue2.js +4 -0
  129. package/dist/ui/Tooltip.vue.js +195 -0
  130. package/dist/ui/Tooltip.vue2.js +4 -0
  131. package/dist/ui/YAxis.vue.js +42 -0
  132. package/dist/ui/YAxis.vue2.js +4 -0
  133. package/dist/ui/YLabel.vue.js +105 -0
  134. package/dist/ui/YLabel.vue2.js +4 -0
  135. package/package.json +2 -2
  136. package/src/BarSeries.vue +12 -13
  137. package/src/CandlestickSeries.vue +17 -6
  138. package/src/LineSeries.vue +12 -13
  139. package/src/PieSeries.vue +2 -1
  140. package/src/composables.ts +12 -2
  141. package/src/index.ts +42 -0
  142. package/src/ui/Crosshair.vue +28 -3
  143. package/src/ui/Legend.vue +1 -2
  144. package/src/ui/Marker.vue +62 -0
  145. package/src/ui/ReferenceLine.vue +58 -0
  146. package/src/ui/TimeRegion.vue +54 -0
@@ -0,0 +1,4 @@
1
+ import f from "./Tooltip.vue.js";
2
+ export {
3
+ f as default
4
+ };
@@ -0,0 +1,42 @@
1
+ import { defineComponent as m, ref as p, onMounted as r, watch as s, onUnmounted as o, openBlock as f, createElementBlock as b, normalizeStyle as y, unref as u } from "vue";
2
+ import { mountAxisLabels as x } from "../core/dist/axis/dom-labels.js";
3
+ import { useChartInstance as h } from "../context.js";
4
+ const _ = /* @__PURE__ */ m({
5
+ __name: "YAxis",
6
+ props: {
7
+ format: { type: Function },
8
+ labelCount: {},
9
+ minLabelSpacing: {}
10
+ },
11
+ setup(c) {
12
+ const e = c, n = h(), t = p(null), a = () => n.yScale.setFormat(e.format ?? null);
13
+ r(a), s(() => e.format, a), o(() => n.yScale.setFormat(null));
14
+ const i = () => {
15
+ n.setYAxisLabelDensity({
16
+ labelCount: e.labelCount ?? null,
17
+ minLabelSpacing: e.minLabelSpacing ?? null
18
+ });
19
+ };
20
+ i(), s(() => [e.labelCount, e.minLabelSpacing], i), o(() => n.setYAxisLabelDensity({ labelCount: null, minLabelSpacing: null }));
21
+ let l = null;
22
+ return r(() => {
23
+ t.value !== null && (l = x({ chart: n, container: t.value, axis: "y" }));
24
+ }), o(() => {
25
+ l?.(), l = null;
26
+ }), (L, S) => (f(), b("div", {
27
+ ref_key: "containerRef",
28
+ ref: t,
29
+ style: y({
30
+ position: "absolute",
31
+ right: "0",
32
+ top: "0",
33
+ bottom: u(n).xAxisHeight + "px",
34
+ width: u(n).yAxisWidth + "px",
35
+ pointerEvents: "none"
36
+ })
37
+ }, null, 4));
38
+ }
39
+ });
40
+ export {
41
+ _ as default
42
+ };
@@ -0,0 +1,4 @@
1
+ import f from "./YAxis.vue.js";
2
+ export {
3
+ f as default
4
+ };
@@ -0,0 +1,105 @@
1
+ import { defineComponent as B, useSlots as F, computed as n, ref as _, onMounted as T, onUnmounted as z, openBlock as h, createElementBlock as b, Fragment as x, renderSlot as Y, createElementVNode as C, normalizeStyle as L, unref as E, createVNode as N, createCommentVNode as V } from "vue";
2
+ import { useChartInstance as D } from "../context.js";
3
+ import O from "./NumberFlow.vue.js";
4
+ const A = /* @__PURE__ */ B({
5
+ __name: "YLabel",
6
+ props: {
7
+ seriesId: {},
8
+ color: {},
9
+ format: { type: Function }
10
+ },
11
+ setup(S) {
12
+ const u = S, e = D(), k = F(), I = n(() => typeof k.default == "function"), r = _(0), i = () => {
13
+ r.value++;
14
+ };
15
+ T(() => {
16
+ e.setYLabel(!0), e.on("overlayChange", i), e.on("viewportChange", i), e.getSeriesIds().length > 0 && r.value++;
17
+ }), z(() => {
18
+ e.setYLabel(!1), e.off("overlayChange", i), e.off("viewportChange", i);
19
+ });
20
+ function w(a, l) {
21
+ if (l !== void 0) return l;
22
+ const g = a.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 });
23
+ if (g.length > 0) return g[0];
24
+ const y = a.getSeriesIdsByType("time", { visibleOnly: !0 });
25
+ return y.length > 0 ? y[0] : null;
26
+ }
27
+ const s = n(() => (r.value, w(e, u.seriesId))), t = n(() => (r.value, s.value !== null ? e.getStackedLastValue(s.value) : null)), v = n(() => (r.value, s.value !== null ? e.getPreviousClose(s.value) : null)), o = n(() => (r.value, e.getTheme())), c = n(() => (r.value, t.value === null ? 0 : e.yScale.valueToY(t.value.value))), f = n(() => t.value === null || v.value === null ? "neutral" : t.value.value > v.value ? "up" : t.value.value < v.value ? "down" : "neutral"), d = n(() => {
28
+ if (t.value === null) return o.value.yLabel.neutralBackground;
29
+ if (!t.value.isLive) return o.value.axis.textColor;
30
+ if (u.color) return u.color;
31
+ switch (f.value) {
32
+ case "up":
33
+ return o.value.yLabel.upBackground;
34
+ case "down":
35
+ return o.value.yLabel.downBackground;
36
+ default:
37
+ return o.value.yLabel.neutralBackground;
38
+ }
39
+ }), m = n(() => {
40
+ r.value;
41
+ const a = e.yScale.getRange(), l = a.max - a.min;
42
+ return l < 0.1 ? 6 : l < 10 ? 4 : l < 1e3 ? 2 : 0;
43
+ }), p = n(() => {
44
+ if (u.format) return u.format;
45
+ const a = new Intl.NumberFormat("en-US", {
46
+ minimumFractionDigits: m.value,
47
+ maximumFractionDigits: m.value,
48
+ useGrouping: !1
49
+ });
50
+ return (l) => a.format(l);
51
+ });
52
+ return (a, l) => t.value !== null ? (h(), b(x, { key: 0 }, [
53
+ I.value ? Y(a.$slots, "default", {
54
+ key: 0,
55
+ value: t.value.value,
56
+ y: c.value,
57
+ bgColor: d.value,
58
+ isLive: t.value.isLive,
59
+ direction: f.value,
60
+ format: p.value
61
+ }) : (h(), b(x, { key: 1 }, [
62
+ C("div", {
63
+ style: L({
64
+ position: "absolute",
65
+ left: "0",
66
+ right: E(e).yAxisWidth + "px",
67
+ top: c.value + "px",
68
+ height: "0",
69
+ borderTop: "1px dashed " + d.value,
70
+ opacity: 0.5,
71
+ pointerEvents: "none",
72
+ zIndex: 2
73
+ })
74
+ }, null, 4),
75
+ C("div", {
76
+ style: L({
77
+ position: "absolute",
78
+ right: "4px",
79
+ top: c.value + "px",
80
+ transform: "translateY(-50%)",
81
+ pointerEvents: "auto",
82
+ zIndex: 3,
83
+ background: d.value,
84
+ color: o.value.yLabel.textColor,
85
+ fontSize: o.value.yLabel.fontSize + "px",
86
+ fontFamily: o.value.typography.fontFamily,
87
+ padding: "3px 8px",
88
+ borderRadius: "3px",
89
+ whiteSpace: "nowrap",
90
+ transition: "background-color 0.3s ease"
91
+ })
92
+ }, [
93
+ N(O, {
94
+ value: t.value.value,
95
+ format: p.value,
96
+ "spin-duration": 350
97
+ }, null, 8, ["value", "format"])
98
+ ], 4)
99
+ ], 64))
100
+ ], 64)) : V("", !0);
101
+ }
102
+ });
103
+ export {
104
+ A as default
105
+ };
@@ -0,0 +1,4 @@
1
+ import f from "./YLabel.vue.js";
2
+ export {
3
+ f as default
4
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wick-charts/vue",
3
- "version": "0.4.1",
3
+ "version": "0.5.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": {
@@ -45,7 +45,7 @@
45
45
  "vue": ">=3.3.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@wick-charts/core": "^0.4.1"
48
+ "@wick-charts/core": "^0.5.0"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "vite build"
package/src/BarSeries.vue CHANGED
@@ -1,12 +1,13 @@
1
1
  <script setup lang="ts">
2
- import type { BarSeriesOptions, SeriesSyncState, TimePoint } from '@wick-charts/core';
3
- import { EMPTY_SYNC_STATE, syncSeriesLayer } from '@wick-charts/core';
2
+ import type { BarSeriesOptions, MultiLayerData, SeriesSyncState } from '@wick-charts/core';
3
+ import { BarSeriesDef, EMPTY_SYNC_STATE, syncLayers, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
6
  import { useChartInstance } from './context';
7
7
 
8
8
  const props = defineProps<{
9
- data: TimePoint[][];
9
+ /** Flat `TimePoint[]`, `TimePoint[][]`, or named `{ label, color?, data }` layers. Time accepts ms or `Date`. */
10
+ data: MultiLayerData;
10
11
  options?: Partial<BarSeriesOptions>;
11
12
  /** Stable series ID — same value across remounts. */
12
13
  id?: string;
@@ -15,25 +16,23 @@ const props = defineProps<{
15
16
  const chart = useChartInstance();
16
17
  const seriesId = ref<string | null>(null);
17
18
  // Per-layer sync state — drives the shared append/keepLast/update/replace
18
- // reconciliation so streaming eases instead of snapping Y every tick.
19
+ // reconciliation (plus per-layer labels/colors) so streaming eases instead of
20
+ // snapping Y every tick.
19
21
  let prevSync: SeriesSyncState[] = [];
20
22
 
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
- });
23
+ function applyData(id: string, data: MultiLayerData): void {
24
+ prevSync = syncLayers({ chart, id, data, prev: prevSync });
27
25
  }
28
26
 
29
27
  onMounted(() => {
30
- const id = chart.addSeries('bar', {
28
+ const layerCount = toLayers(props.data).length;
29
+ const id = chart.addSeries(BarSeriesDef, {
31
30
  ...props.options,
32
- layers: props.data.length,
31
+ layers: layerCount,
33
32
  id: props.id,
34
33
  });
35
34
  seriesId.value = id;
36
- prevSync = props.data.map(() => EMPTY_SYNC_STATE);
35
+ prevSync = new Array(layerCount).fill(EMPTY_SYNC_STATE);
37
36
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
38
37
  applyData(id, props.data);
39
38
  });
@@ -1,12 +1,13 @@
1
1
  <script setup lang="ts">
2
- import type { CandlestickSeriesOptions, OHLCInput, SeriesSyncState } from '@wick-charts/core';
3
- import { EMPTY_SYNC_STATE, syncSeriesLayer } from '@wick-charts/core';
2
+ import type { CandlestickData, CandlestickSeriesOptions, OHLCInput, SeriesSyncState } from '@wick-charts/core';
3
+ import { CandlestickSeriesDef, EMPTY_SYNC_STATE, syncSeriesLayer, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
6
  import { useChartInstance } from './context';
7
7
 
8
8
  const props = defineProps<{
9
- data: OHLCInput[];
9
+ /** Flat `OHLCInput[]`, or `{ label, data }` to name the stream. */
10
+ data: CandlestickData;
10
11
  options?: Partial<CandlestickSeriesOptions>;
11
12
  /** Stable series ID — same value across remounts. */
12
13
  id?: string;
@@ -19,13 +20,23 @@ const seriesId = ref<string | null>(null);
19
20
  // burst takes the smooth append path, matching React's 20-candle threshold).
20
21
  let prevSync: SeriesSyncState = EMPTY_SYNC_STATE;
21
22
 
23
+ function applyData(id: string, data: CandlestickData): void {
24
+ const layer = toLayers<OHLCInput>(data)[0];
25
+ chart.setSeriesLabels(id, [layer.label]);
26
+ prevSync = syncSeriesLayer({ chart, id, data: layer.data, prev: prevSync });
27
+ }
28
+
22
29
  onMounted(() => {
23
- const id = chart.addSeries('candlestick', { ...props.options, id: props.id });
30
+ const id = chart.addSeries(CandlestickSeriesDef, {
31
+ ...props.options,
32
+ id: props.id,
33
+ label: toLayers<OHLCInput>(props.data)[0].label,
34
+ });
24
35
  seriesId.value = id;
25
36
  // Initial data load — Vue's `watch` is lazy by default, so the watcher
26
37
  // below only fires on subsequent `data` prop mutations. Explicitly apply
27
38
  // the first value here so components with static data render immediately.
28
- prevSync = syncSeriesLayer({ chart, id, data: props.data, prev: prevSync });
39
+ applyData(id, props.data);
29
40
  });
30
41
 
31
42
  onUnmounted(() => {
@@ -38,7 +49,7 @@ watch(
38
49
  const id = seriesId.value;
39
50
  if (!id) return;
40
51
 
41
- prevSync = syncSeriesLayer({ chart, id, data, prev: prevSync });
52
+ applyData(id, data);
42
53
  },
43
54
  );
44
55
 
@@ -1,12 +1,13 @@
1
1
  <script setup lang="ts">
2
- import type { LineSeriesOptions, SeriesSyncState, TimePoint } from '@wick-charts/core';
3
- import { EMPTY_SYNC_STATE, syncSeriesLayer } from '@wick-charts/core';
2
+ import type { LineSeriesOptions, MultiLayerData, SeriesSyncState } from '@wick-charts/core';
3
+ import { EMPTY_SYNC_STATE, LineSeriesDef, syncLayers, toLayers } from '@wick-charts/core';
4
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
5
5
 
6
6
  import { useChartInstance } from './context';
7
7
 
8
8
  const props = defineProps<{
9
- data: TimePoint[][];
9
+ /** Flat `TimePoint[]`, `TimePoint[][]`, or named `{ label, color?, data }` layers. Time accepts ms or `Date`. */
10
+ data: MultiLayerData;
10
11
  options?: Partial<LineSeriesOptions>;
11
12
  /** Stable series ID — same value across remounts. */
12
13
  id?: string;
@@ -15,25 +16,23 @@ const props = defineProps<{
15
16
  const chart = useChartInstance();
16
17
  const seriesId = ref<string | null>(null);
17
18
  // Per-layer sync state — drives the shared append/keepLast/update/replace
18
- // reconciliation so streaming eases instead of snapping Y every tick.
19
+ // reconciliation (plus per-layer labels/colors) so streaming eases instead of
20
+ // snapping Y every tick.
19
21
  let prevSync: SeriesSyncState[] = [];
20
22
 
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
- });
23
+ function applyData(id: string, data: MultiLayerData): void {
24
+ prevSync = syncLayers({ chart, id, data, prev: prevSync });
27
25
  }
28
26
 
29
27
  onMounted(() => {
30
- const id = chart.addSeries('line', {
28
+ const layerCount = toLayers(props.data).length;
29
+ const id = chart.addSeries(LineSeriesDef, {
31
30
  ...props.options,
32
- layers: props.data.length,
31
+ layers: layerCount,
33
32
  id: props.id,
34
33
  });
35
34
  seriesId.value = id;
36
- prevSync = props.data.map(() => EMPTY_SYNC_STATE);
35
+ prevSync = new Array(layerCount).fill(EMPTY_SYNC_STATE);
37
36
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
38
37
  applyData(id, props.data);
39
38
  });
package/src/PieSeries.vue CHANGED
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { PieSeriesOptions, PieSliceData } from '@wick-charts/core';
3
+ import { PieSeriesDef } from '@wick-charts/core';
3
4
  import { onMounted, onUnmounted, ref, watch } from 'vue';
4
5
 
5
6
  import { useChartInstance } from './context';
@@ -15,7 +16,7 @@ const chart = useChartInstance();
15
16
  const seriesId = ref<string | null>(null);
16
17
 
17
18
  onMounted(() => {
18
- const id = chart.addSeries('pie', { ...props.options, id: props.id });
19
+ const id = chart.addSeries(PieSeriesDef, { ...props.options, id: props.id });
19
20
  seriesId.value = id;
20
21
  // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
21
22
  if (props.data.length > 0) {
@@ -22,10 +22,20 @@ export function useYRange(chart: ChartInstance): Ref<YRange> {
22
22
  }
23
23
 
24
24
  export function useLastYValue(chart: ChartInstance, seriesId: string): Ref<{ value: number; isLive: boolean } | null> {
25
- const val = ref<{ value: number; isLive: boolean } | null>(chart.getLastValue(seriesId));
25
+ let snapshot = chart.getLastValue(seriesId);
26
+ // Track the pixel Y the snapshot maps to so a pan/zoom/resize that leaves the
27
+ // value unchanged but shifts the badge still re-emits — a consumer-positioned
28
+ // badge reads `yScale.valueToY` off this ref. Comparing prev/next against the
29
+ // live yScale alone would always be equal.
30
+ let lastY = snapshot ? chart.yScale.valueToY(snapshot.value) : null;
31
+ const val = ref<{ value: number; isLive: boolean } | null>(snapshot);
26
32
  const handler = () => {
27
33
  const next = chart.getLastValue(seriesId);
28
- if (val.value?.value === next?.value && val.value?.isLive === next?.isLive) return;
34
+ const nextY = next ? chart.yScale.valueToY(next.value) : null;
35
+ if (snapshot?.value === next?.value && snapshot?.isLive === next?.isLive && lastY === nextY) return;
36
+
37
+ snapshot = next;
38
+ lastY = nextY;
29
39
  val.value = next;
30
40
  };
31
41
  onMounted(() => {
package/src/index.ts CHANGED
@@ -11,19 +11,29 @@
11
11
  export type {
12
12
  AxisBound,
13
13
  AxisConfig,
14
+ BarPaintArgs,
15
+ BarPainter,
14
16
  BarSeriesOptions,
15
17
  /** @deprecated Use {@link StackingMode} instead. */
16
18
  BuildHoverSnapshotsArgs,
17
19
  BuildLastSnapshotsArgs,
20
+ CandlePaintArgs,
21
+ CandlePainter,
18
22
  CandlestickSeriesOptions,
19
23
  ChartLayout,
20
24
  ChartOptions,
21
25
  ChartTheme,
26
+ CornerMask,
22
27
  CrosshairPosition,
28
+ CurveKind,
23
29
  HoverInfo,
24
30
  LegendItem,
31
+ LinePaintArgs,
32
+ LinePainter,
25
33
  /** @deprecated Use {@link TimePoint} instead. */
26
34
  LineSeriesOptions,
35
+ MarkerConfig,
36
+ MarkerShape,
27
37
  NavigatorCandlePoint,
28
38
  NavigatorControllerParams,
29
39
  NavigatorData,
@@ -32,8 +42,18 @@ export type {
32
42
  NavigatorSeriesType,
33
43
  OHLCData,
34
44
  OHLCInput,
45
+ PaintEnv,
46
+ PerfConfig,
47
+ PerfMonitorOptions,
48
+ PerfStats,
35
49
  PieSeriesOptions,
36
50
  PieSliceData,
51
+ ReferenceLineConfig,
52
+ ReferenceLineOrientation,
53
+ ReferenceLineStyle,
54
+ RoundedRectArgs,
55
+ SeriesCreateEnv,
56
+ SeriesDefinition,
37
57
  SeriesSnapshot,
38
58
  SeriesType,
39
59
  SliceInfo,
@@ -43,11 +63,14 @@ export type {
43
63
  ThemePreset,
44
64
  TimePoint,
45
65
  TimePointInput,
66
+ TimeRegionConfig,
67
+ TimeRegionEnd,
46
68
  TimeValue,
47
69
  TooltipField,
48
70
  TooltipFormatter,
49
71
  TooltipPosition,
50
72
  TooltipPositionArgs,
73
+ TracePathArgs,
51
74
  Typography,
52
75
  ValueFormatter,
53
76
  VisibleRange,
@@ -55,9 +78,15 @@ export type {
55
78
  YAxisConfig,
56
79
  YRange,
57
80
  } from '@wick-charts/core';
81
+ // Painters — custom per-element drawing for bar / candle / line series.
58
82
  export {
83
+ BarSeriesDef,
84
+ CandlestickSeriesDef,
59
85
  ChartInstance,
86
+ LineSeriesDef,
60
87
  NavigatorController,
88
+ PerfMonitor,
89
+ PieSeriesDef,
61
90
  andromeda,
62
91
  autoGradient,
63
92
  ayuMirage,
@@ -68,6 +97,7 @@ export {
68
97
  createTheme,
69
98
  detectInterval,
70
99
  dracula,
100
+ fillRoundedRect,
71
101
  formatCompact,
72
102
  formatDate,
73
103
  formatPriceAdaptive,
@@ -87,13 +117,22 @@ export {
87
117
  oneDarkPro,
88
118
  panda,
89
119
  peachCream,
120
+ perfHud,
90
121
  quietLight,
122
+ registerBuiltinSeries,
123
+ registerSeriesDefinition,
91
124
  resolveAxisFontSize,
92
125
  resolveAxisTextColor,
93
126
  resolveCandlestickBodyColor,
94
127
  rosePineDawn,
128
+ roundedBarFill,
129
+ roundedCandleFill,
95
130
  sandDune,
131
+ smoothCurve,
96
132
  solarizedLight,
133
+ steppedCurve,
134
+ straightCurve,
135
+ tracePath,
97
136
  } from '@wick-charts/core';
98
137
 
99
138
  export { default as BarSeries } from './BarSeries.vue';
@@ -108,11 +147,14 @@ export { default as PieSeries } from './PieSeries.vue';
108
147
  export { default as Crosshair } from './ui/Crosshair.vue';
109
148
  export { default as InfoBar } from './ui/InfoBar.vue';
110
149
  export { default as Legend } from './ui/Legend.vue';
150
+ export { default as Marker } from './ui/Marker.vue';
111
151
  export { default as Navigator } from './ui/Navigator.vue';
112
152
  export { default as NumberFlow } from './ui/NumberFlow.vue';
113
153
  export { default as PieLegend } from './ui/PieLegend.vue';
114
154
  export { default as PieTooltip } from './ui/PieTooltip.vue';
155
+ export { default as ReferenceLine } from './ui/ReferenceLine.vue';
115
156
  export { default as TimeAxis } from './ui/TimeAxis.vue';
157
+ export { default as TimeRegion } from './ui/TimeRegion.vue';
116
158
  export { default as Title } from './ui/Title.vue';
117
159
  // UI overlays
118
160
  export { default as Tooltip } from './ui/Tooltip.vue';
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { formatTime } from '@wick-charts/core';
3
- import { computed } from 'vue';
3
+ import { computed, onMounted, onUnmounted, ref } from 'vue';
4
4
 
5
5
  import { useCrosshairPosition } from '../composables';
6
6
  import { useChartInstance } from '../context';
@@ -8,8 +8,33 @@ import { useChartInstance } from '../context';
8
8
  const chart = useChartInstance();
9
9
  const position = useCrosshairPosition(chart);
10
10
 
11
- const theme = computed(() => chart.getTheme());
11
+ // `setTheme` fires `overlayChange`; bump re-runs the theme computed so the
12
+ // label colors track a runtime theme swap instead of freezing on the
13
+ // first-render snapshot (matches Tooltip/PieTooltip).
14
+ const bump = ref(0);
15
+ const onOverlayChange = () => {
16
+ bump.value++;
17
+ };
18
+ onMounted(() => chart.on('overlayChange', onOverlayChange));
19
+ onUnmounted(() => chart.off('overlayChange', onOverlayChange));
20
+
21
+ const theme = computed(() => {
22
+ void bump.value;
23
+
24
+ return chart.getTheme();
25
+ });
12
26
  const dataInterval = computed(() => chart.getDataInterval());
27
+ // Format the time pill at the axis's *resolved* tick granularity, not the raw
28
+ // data interval — otherwise a time-of-day badge floats among date labels when
29
+ // zoomed out. `niceTickValues` reads the live visible range, which Vue can't
30
+ // track reactively, so depend on `position` (a wheel-zoom re-emits the
31
+ // crosshair) to re-resolve on zoom — matching React, which recomputes this on
32
+ // every render. Falls back to dataInterval on a degenerate range.
33
+ const tickInterval = computed(() => {
34
+ void position.value;
35
+
36
+ return chart.timeScale.niceTickValues(dataInterval.value).tickInterval || dataInterval.value;
37
+ });
13
38
 
14
39
  const labelStyle = computed(() => ({
15
40
  // Blend the theme's labelBackground at 80% opacity so the axis grid
@@ -54,7 +79,7 @@ const labelStyle = computed(() => ({
54
79
  ...labelStyle,
55
80
  }"
56
81
  >
57
- {{ formatTime(position.time, dataInterval) }}
82
+ {{ formatTime(position.time, tickInterval) }}
58
83
  </div>
59
84
  </template>
60
85
  </template>
package/src/ui/Legend.vue CHANGED
@@ -129,7 +129,6 @@ function buildLegendItems(c: ChartInstance): LegendItem[] {
129
129
  for (const seriesId of c.getSeriesIds()) {
130
130
  const layers = c.getSeriesLayers(seriesId);
131
131
  if (layers) {
132
- const baseLabel = c.getSeriesLabel(seriesId);
133
132
  for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
134
133
  const id = `${seriesId}_layer${layerIndex}`;
135
134
  const visible = c.isSeriesVisible(seriesId) && c.isLayerVisible(seriesId, layerIndex);
@@ -138,7 +137,7 @@ function buildLegendItems(c: ChartInstance): LegendItem[] {
138
137
  id,
139
138
  seriesId,
140
139
  layerIndex,
141
- label: baseLabel ? `${baseLabel} ${layerIndex + 1}` : `Series ${layerIndex + 1}`,
140
+ label: c.getLayerLabel(seriesId, layerIndex) ?? `Series ${layerIndex + 1}`,
142
141
  color: layers[layerIndex].color,
143
142
  isDisabled: !visible,
144
143
  }),
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import type { MarkerShape, TimeValue } from '@wick-charts/core';
3
+ import { onMounted, onUnmounted, watch } from 'vue';
4
+
5
+ import { useChartInstance } from '../context';
6
+
7
+ const props = defineProps<{
8
+ /** X anchor — epoch ms or a `Date`. */
9
+ time: TimeValue;
10
+ /** Y anchor as a data value. Omit and set `seriesId` to snap to that series' value at `time`. */
11
+ value?: number;
12
+ /** Snap Y to this series' value at `time` when `value` is omitted. Also the default color source. */
13
+ seriesId?: string;
14
+ /** Glyph at the anchor. Default: `'dot'`. Arrows point their tip at the anchor. */
15
+ shape?: MarkerShape;
16
+ /** Reuse the line pulse halo around the anchor. Default: `false`. */
17
+ pulse?: boolean;
18
+ /** Optional text label drawn in a pill next to the anchor. */
19
+ label?: string;
20
+ /** Color override. Falls back to the series color (when `seriesId` is set), then the theme line color. */
21
+ color?: string;
22
+ }>();
23
+
24
+ const chart = useChartInstance();
25
+ let markerId: string | null = null;
26
+
27
+ onMounted(() => {
28
+ markerId = chart.addMarker({
29
+ time: props.time,
30
+ value: props.value,
31
+ seriesId: props.seriesId,
32
+ shape: props.shape,
33
+ pulse: props.pulse,
34
+ label: props.label,
35
+ color: props.color,
36
+ });
37
+ });
38
+
39
+ onUnmounted(() => {
40
+ if (markerId !== null) chart.removeMarker(markerId);
41
+ markerId = null;
42
+ });
43
+
44
+ watch(
45
+ () => [props.time, props.value, props.seriesId, props.shape, props.pulse, props.label, props.color],
46
+ () => {
47
+ if (markerId === null) return;
48
+
49
+ chart.updateMarker(markerId, {
50
+ time: props.time,
51
+ value: props.value,
52
+ seriesId: props.seriesId,
53
+ shape: props.shape,
54
+ pulse: props.pulse,
55
+ label: props.label,
56
+ color: props.color,
57
+ });
58
+ },
59
+ );
60
+ </script>
61
+
62
+ <template><span v-if="false" /></template>