@wick-charts/vue 0.3.5 → 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/README.md +6 -6
- package/dist/index.d.ts +726 -315
- package/dist/index.js +4658 -3907
- package/package.json +3 -3
- package/src/BarSeries.vue +20 -13
- package/src/CandlestickSeries.vue +9 -24
- package/src/ChartContainer.vue +72 -19
- package/src/LineSeries.vue +20 -13
- package/src/PieSeries.vue +1 -1
- package/src/index.ts +0 -4
- package/src/ui/TimeAxis.vue +13 -70
- package/src/ui/Title.vue +1 -1
- package/src/ui/YAxis.vue +13 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wick-charts/vue",
|
|
3
|
-
"version": "0.
|
|
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://
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/ChartContainer.vue
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
ChartInstance,
|
|
6
6
|
type ChartOptions,
|
|
7
7
|
type ChartTheme,
|
|
8
|
+
type EdgeReachedInfo,
|
|
9
|
+
type VisibleRangeSpec,
|
|
8
10
|
catppuccin,
|
|
9
11
|
} from '@wick-charts/core';
|
|
10
12
|
import { computed, nextTick, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
|
|
@@ -37,6 +39,28 @@ const props = withDefaults(
|
|
|
37
39
|
right?: number | { intervals: number };
|
|
38
40
|
left?: number | { intervals: number };
|
|
39
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
|
+
};
|
|
40
64
|
/** Show the chart background gradient. Defaults to true. */
|
|
41
65
|
gradient?: boolean;
|
|
42
66
|
/** Enable zoom, pan, and crosshair interactions. Defaults to true. */
|
|
@@ -50,16 +74,15 @@ const props = withDefaults(
|
|
|
50
74
|
*/
|
|
51
75
|
headerLayout?: 'overlay' | 'inline';
|
|
52
76
|
/**
|
|
53
|
-
*
|
|
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`.
|
|
54
81
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* Shorthands: `true` / omitted — built-in defaults; `false` — disables every
|
|
60
|
-
* animation category; `{ points: false }` / `{ viewport: false }` disables a
|
|
61
|
-
* category. Updating this prop calls `chart.setAnimations(...)` so the new
|
|
62
|
-
* 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.
|
|
63
86
|
*/
|
|
64
87
|
animations?: boolean | AnimationsConfig;
|
|
65
88
|
/**
|
|
@@ -67,6 +90,13 @@ const props = withDefaults(
|
|
|
67
90
|
* later changes are ignored.
|
|
68
91
|
*/
|
|
69
92
|
perf?: PerfOption;
|
|
93
|
+
/**
|
|
94
|
+
* Fired after the user releases a pan/zoom gesture that pulled the viewport
|
|
95
|
+
* past a data edge by more than ~10% of the visible range. Hosts typically
|
|
96
|
+
* respond by prefetching more history. Captured at mount only; changing
|
|
97
|
+
* the prop identity later is ignored.
|
|
98
|
+
*/
|
|
99
|
+
onEdgeReached?: (info: EdgeReachedInfo) => void;
|
|
70
100
|
}>(),
|
|
71
101
|
{
|
|
72
102
|
theme: () => catppuccin.theme,
|
|
@@ -82,6 +112,8 @@ const themeRef = shallowRef<ChartTheme>(props.theme);
|
|
|
82
112
|
// Capture perf at mount only — mirrors React's perfRef so a new object
|
|
83
113
|
// identity later doesn't recreate the chart.
|
|
84
114
|
const perfAtMount = props.perf;
|
|
115
|
+
// Same mount-only capture for the edge callback — the chart binds it once.
|
|
116
|
+
const onEdgeReachedAtMount = props.onEdgeReached;
|
|
85
117
|
|
|
86
118
|
const topOverlayRef = ref<HTMLElement | null>(null);
|
|
87
119
|
const titleAnchor = ref<HTMLElement | null>(null);
|
|
@@ -149,9 +181,11 @@ onMounted(async () => {
|
|
|
149
181
|
if (props.axis) options.axis = props.axis;
|
|
150
182
|
if (props.theme) options.theme = props.theme;
|
|
151
183
|
if (props.padding) options.padding = props.padding;
|
|
184
|
+
if (props.viewport) options.viewport = props.viewport;
|
|
152
185
|
if (props.interactive !== undefined) options.interactive = props.interactive;
|
|
153
186
|
if (props.grid !== undefined) options.grid = props.grid;
|
|
154
187
|
if (perfAtMount !== undefined) options.perf = perfAtMount;
|
|
188
|
+
if (onEdgeReachedAtMount) options.onEdgeReached = onEdgeReachedAtMount;
|
|
155
189
|
if (props.animations !== undefined) options.animations = props.animations;
|
|
156
190
|
chart.value = new ChartInstance(containerRef.value, options);
|
|
157
191
|
|
|
@@ -202,14 +236,35 @@ watch(
|
|
|
202
236
|
},
|
|
203
237
|
);
|
|
204
238
|
|
|
205
|
-
//
|
|
206
|
-
//
|
|
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.
|
|
207
250
|
watch(
|
|
208
|
-
() =>
|
|
209
|
-
() => {
|
|
210
|
-
if (chart.value
|
|
211
|
-
|
|
212
|
-
|
|
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);
|
|
213
268
|
},
|
|
214
269
|
);
|
|
215
270
|
|
|
@@ -231,9 +286,7 @@ const rootStyle = computed(() => {
|
|
|
231
286
|
const t = themeRef.value;
|
|
232
287
|
const [gtop, gbot] = t?.chartGradient ?? ['transparent', 'transparent'];
|
|
233
288
|
const bg = t?.background ?? 'transparent';
|
|
234
|
-
const background = props.gradient
|
|
235
|
-
? `linear-gradient(to bottom, ${gtop} 0%, ${bg} 70%, ${gbot} 100%)`
|
|
236
|
-
: bg;
|
|
289
|
+
const background = props.gradient ? `linear-gradient(to bottom, ${gtop} 0%, ${bg} 70%, ${gbot} 100%)` : bg;
|
|
237
290
|
|
|
238
291
|
return (
|
|
239
292
|
'position: relative; display: flex; flex-direction: column; width: 100%; height: 100%; overflow: hidden; ' +
|
package/src/LineSeries.vue
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
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,
|
package/src/ui/TimeAxis.vue
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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/Title.vue
CHANGED
package/src/ui/YAxis.vue
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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>
|