@wick-charts/vue 0.6.1 → 0.6.3
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/dist/ChartContainer.vue.js +43 -37
- package/dist/LineSeries.vue.js +2 -2
- package/dist/composables.js +39 -26
- package/dist/core/dist/animation/config.js +66 -64
- package/dist/core/dist/animation/intro-wave.js +19 -21
- package/dist/core/dist/animation/reduced-motion.js +6 -0
- package/dist/core/dist/axis/dom-labels.js +58 -45
- package/dist/core/dist/chart/badge-animator.js +15 -19
- package/dist/core/dist/chart/options.js +5 -5
- package/dist/core/dist/chart/scale-guards.js +26 -0
- package/dist/core/dist/chart/y-target.js +46 -19
- package/dist/core/dist/chart.js +752 -570
- package/dist/core/dist/components/crosshair.js +8 -8
- package/dist/core/dist/components/grid.js +28 -15
- package/dist/core/dist/components/marker.js +28 -28
- package/dist/core/dist/components/reference-line.js +16 -16
- package/dist/core/dist/interactions/handler.js +3 -2
- package/dist/core/dist/interactions/zoom.js +6 -5
- package/dist/core/dist/navigator/controller.js +43 -40
- package/dist/core/dist/perf/perf-hud.js +3 -3
- package/dist/core/dist/scales/log-scale.js +76 -0
- package/dist/core/dist/scales/tick-tracker.js +2 -2
- package/dist/core/dist/scales/x-scale.js +47 -32
- package/dist/core/dist/scales/y-scale.js +155 -38
- package/dist/core/dist/series/bar-intro.js +11 -11
- package/dist/core/dist/series/bar.js +299 -249
- package/dist/core/dist/series/base-multi-layer.js +100 -76
- package/dist/core/dist/series/candlestick.js +252 -249
- package/dist/core/dist/series/heatmap.js +16 -16
- package/dist/core/dist/series/line-intro.js +18 -18
- package/dist/core/dist/series/line.js +522 -424
- package/dist/core/dist/series/scale-stack.js +25 -0
- package/dist/core/dist/tooltip-position.js +5 -5
- package/dist/core/dist/utils/pixel-grid.js +10 -0
- package/dist/index.d.ts +279 -32
- package/dist/index.js +59 -57
- package/dist/ui/Crosshair.vue.js +14 -14
- package/dist/ui/HeatmapTooltip.vue.js +6 -6
- package/dist/ui/PieTooltip.vue.js +4 -4
- package/dist/ui/TimeAxis.vue.js +23 -22
- package/dist/ui/Tooltip.vue.js +71 -69
- package/dist/ui/YAxis.vue.js +25 -24
- package/dist/ui/YLabel.vue.js +50 -49
- package/package.json +2 -2
- package/src/ChartContainer.vue +20 -8
- package/src/composables.ts +35 -1
- package/src/index.ts +5 -0
- package/src/ui/Crosshair.vue +2 -2
- package/src/ui/TimeAxis.vue +7 -4
- package/src/ui/Tooltip.vue +4 -1
- package/src/ui/YAxis.vue +5 -3
- package/src/ui/YLabel.vue +3 -3
package/dist/ui/YAxis.vue.js
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
import { mountAxisLabels as
|
|
3
|
-
import {
|
|
4
|
-
|
|
1
|
+
import { defineComponent as p, ref as f, onMounted as u, watch as c, onUnmounted as a, openBlock as y, createElementBlock as b, normalizeStyle as x, unref as i } from "vue";
|
|
2
|
+
import { mountAxisLabels as L } from "../core/dist/axis/dom-labels.js";
|
|
3
|
+
import { useAxisLayout as A } from "../composables.js";
|
|
4
|
+
import { useChartInstance as S } from "../context.js";
|
|
5
|
+
const F = /* @__PURE__ */ p({
|
|
5
6
|
__name: "YAxis",
|
|
6
7
|
props: {
|
|
7
8
|
format: { type: Function },
|
|
8
9
|
labelCount: {},
|
|
9
10
|
minLabelSpacing: {}
|
|
10
11
|
},
|
|
11
|
-
setup(
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
labelCount:
|
|
17
|
-
minLabelSpacing:
|
|
12
|
+
setup(m) {
|
|
13
|
+
const n = m, t = S(), e = A(t), l = f(null), s = () => t.yScale.setFormat(n.format ?? null);
|
|
14
|
+
u(s), c(() => n.format, s), a(() => t.yScale.setFormat(null));
|
|
15
|
+
const r = () => {
|
|
16
|
+
t.setYAxisLabelDensity({
|
|
17
|
+
labelCount: n.labelCount ?? null,
|
|
18
|
+
minLabelSpacing: n.minLabelSpacing ?? null
|
|
18
19
|
});
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
-
let
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
}),
|
|
25
|
-
|
|
26
|
-
}), (
|
|
21
|
+
r(), c(() => [n.labelCount, n.minLabelSpacing], r), a(() => t.setYAxisLabelDensity({ labelCount: null, minLabelSpacing: null }));
|
|
22
|
+
let o = null;
|
|
23
|
+
return u(() => {
|
|
24
|
+
l.value !== null && (o = L({ chart: t, container: l.value, axis: "y" }));
|
|
25
|
+
}), a(() => {
|
|
26
|
+
o?.(), o = null;
|
|
27
|
+
}), (d, h) => (y(), b("div", {
|
|
27
28
|
ref_key: "containerRef",
|
|
28
|
-
ref:
|
|
29
|
-
style:
|
|
29
|
+
ref: l,
|
|
30
|
+
style: x({
|
|
30
31
|
position: "absolute",
|
|
31
|
-
|
|
32
|
+
[i(e).yAxisPosition]: "0",
|
|
32
33
|
top: "0",
|
|
33
|
-
bottom:
|
|
34
|
-
width:
|
|
34
|
+
bottom: i(e).xAxisHeight + "px",
|
|
35
|
+
width: i(e).yAxisWidth + "px",
|
|
35
36
|
pointerEvents: "none"
|
|
36
37
|
})
|
|
37
38
|
}, null, 4));
|
|
38
39
|
}
|
|
39
40
|
});
|
|
40
41
|
export {
|
|
41
|
-
|
|
42
|
+
F as default
|
|
42
43
|
};
|
package/dist/ui/YLabel.vue.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { defineComponent as q, useSlots as j, computed as n, ref as
|
|
2
|
-
import {
|
|
1
|
+
import { defineComponent as q, useSlots as j, computed as n, ref as w, onMounted as G, onUnmounted as W, openBlock as A, createElementBlock as B, Fragment as _, renderSlot as H, createElementVNode as T, normalizeStyle as z, unref as g, createVNode as J, createCommentVNode as K } from "vue";
|
|
2
|
+
import { prefersReducedMotion as Q } from "../core/dist/animation/reduced-motion.js";
|
|
3
|
+
import { resolveAnimate as X, firstVisibleValue as Z, BadgeAnimator as ee } from "../core/dist/chart/badge-animator.js";
|
|
3
4
|
import { useChartInstance as te } from "../context.js";
|
|
4
5
|
import ne from "./NumberFlow.vue.js";
|
|
5
|
-
const
|
|
6
|
+
const ie = /* @__PURE__ */ q({
|
|
6
7
|
__name: "YLabel",
|
|
7
8
|
props: {
|
|
8
9
|
seriesId: {},
|
|
@@ -10,56 +11,56 @@ const re = /* @__PURE__ */ q({
|
|
|
10
11
|
format: { type: Function },
|
|
11
12
|
animate: { type: [Boolean, Object] }
|
|
12
13
|
},
|
|
13
|
-
setup(
|
|
14
|
-
const f =
|
|
15
|
-
let m = null,
|
|
16
|
-
const
|
|
14
|
+
setup(Y) {
|
|
15
|
+
const f = Y, e = te(), E = j(), M = n(() => typeof E.default == "function"), i = w(0), o = w(null);
|
|
16
|
+
let m = null, x = null, u = null;
|
|
17
|
+
const N = n(() => X(f.animate));
|
|
17
18
|
function S() {
|
|
18
|
-
|
|
19
|
+
u = null;
|
|
19
20
|
const t = s.value, l = t !== null ? e.getStackedLastValue(t) : null;
|
|
20
21
|
if (t === null || !l) {
|
|
21
|
-
m = null,
|
|
22
|
+
m = null, x = null;
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
24
25
|
const v = l.value;
|
|
25
|
-
(m === null ||
|
|
26
|
-
const { glide: d, countUp: U, settleMs: $ } =
|
|
26
|
+
(m === null || x !== t) && (m = new ee(v), x = t);
|
|
27
|
+
const { glide: d, countUp: U, settleMs: $ } = N.value, I = e.getIntroFront(t), c = m.frame({
|
|
27
28
|
target: v,
|
|
28
29
|
// Only needed for the intro count-up; skip the visible-range binary search
|
|
29
30
|
// entirely once the reveal has settled (the hot streaming path).
|
|
30
|
-
firstVisible:
|
|
31
|
-
introFront:
|
|
31
|
+
firstVisible: I < 1 ? Z(e, t, v) : v,
|
|
32
|
+
introFront: I,
|
|
32
33
|
now: performance.now(),
|
|
33
34
|
glide: d,
|
|
34
35
|
countUp: U,
|
|
35
36
|
settleMs: $,
|
|
36
|
-
reducedMotion:
|
|
37
|
-
}), y =
|
|
38
|
-
(y === null || y.positionValue !== c.positionValue || y.textValue !== c.textValue || y.opacity !== c.opacity) && (
|
|
37
|
+
reducedMotion: Q()
|
|
38
|
+
}), y = o.value;
|
|
39
|
+
(y === null || y.positionValue !== c.positionValue || y.textValue !== c.textValue || y.opacity !== c.opacity) && (o.value = { positionValue: c.positionValue, textValue: c.textValue, opacity: c.opacity }), c.animating && u === null && (u = requestAnimationFrame(S));
|
|
39
40
|
}
|
|
40
|
-
function
|
|
41
|
-
|
|
41
|
+
function O() {
|
|
42
|
+
u === null && (u = requestAnimationFrame(S));
|
|
42
43
|
}
|
|
43
44
|
const p = () => {
|
|
44
|
-
i.value++,
|
|
45
|
+
i.value++, O();
|
|
45
46
|
};
|
|
46
47
|
G(() => {
|
|
47
48
|
e.setYLabel(!0), e.on("overlayChange", p), e.on("viewportChange", p), e.getSeriesIds().length > 0 && p();
|
|
48
|
-
}),
|
|
49
|
-
e.setYLabel(!1), e.off("overlayChange", p), e.off("viewportChange", p),
|
|
49
|
+
}), W(() => {
|
|
50
|
+
e.setYLabel(!1), e.off("overlayChange", p), e.off("viewportChange", p), u !== null && cancelAnimationFrame(u), u = null;
|
|
50
51
|
});
|
|
51
|
-
function
|
|
52
|
+
function R(t, l) {
|
|
52
53
|
if (l !== void 0) return l;
|
|
53
54
|
const v = t.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 });
|
|
54
55
|
if (v.length > 0) return v[0];
|
|
55
56
|
const d = t.getSeriesIdsByType("time", { visibleOnly: !0 });
|
|
56
57
|
return d.length > 0 ? d[0] : null;
|
|
57
58
|
}
|
|
58
|
-
const s = n(() => (i.value,
|
|
59
|
+
const s = n(() => (i.value, R(e, f.seriesId))), a = n(() => (i.value, s.value !== null ? e.getStackedLastValue(s.value) : null)), b = n(() => (i.value, s.value !== null ? e.getPreviousClose(s.value) : null)), r = n(() => (i.value, e.getTheme())), D = n(() => o.value !== null ? o.value.positionValue : a.value?.value ?? 0), P = n(() => o.value !== null ? o.value.textValue : a.value?.value ?? 0), h = n(() => o.value !== null ? o.value.opacity : s.value === null ? 1 : e.getIntroFront(s.value) < 1 ? 0 : 1), V = n(() => (i.value, a.value === null ? 0 : e.yScale.valueToY(D.value))), k = n(() => a.value === null || b.value === null ? "neutral" : a.value.value > b.value ? "up" : a.value.value < b.value ? "down" : "neutral"), L = n(() => {
|
|
59
60
|
if (a.value === null) return r.value.yLabel.neutralBackground;
|
|
60
61
|
if (!a.value.isLive) return r.value.axis.textColor;
|
|
61
62
|
if (f.color) return f.color;
|
|
62
|
-
switch (
|
|
63
|
+
switch (k.value) {
|
|
63
64
|
case "up":
|
|
64
65
|
return r.value.yLabel.upBackground;
|
|
65
66
|
case "down":
|
|
@@ -67,53 +68,53 @@ const re = /* @__PURE__ */ q({
|
|
|
67
68
|
default:
|
|
68
69
|
return r.value.yLabel.neutralBackground;
|
|
69
70
|
}
|
|
70
|
-
}),
|
|
71
|
+
}), C = n(() => {
|
|
71
72
|
i.value;
|
|
72
73
|
const t = e.yScale.getRange(), l = t.max - t.min;
|
|
73
74
|
return l < 0.1 ? 6 : l < 10 ? 4 : l < 1e3 ? 2 : 0;
|
|
74
|
-
}),
|
|
75
|
+
}), F = n(() => {
|
|
75
76
|
if (f.format) return f.format;
|
|
76
77
|
const t = new Intl.NumberFormat("en-US", {
|
|
77
|
-
minimumFractionDigits:
|
|
78
|
-
maximumFractionDigits:
|
|
78
|
+
minimumFractionDigits: C.value,
|
|
79
|
+
maximumFractionDigits: C.value,
|
|
79
80
|
useGrouping: !1
|
|
80
81
|
});
|
|
81
82
|
return (l) => t.format(l);
|
|
82
83
|
});
|
|
83
|
-
return (t, l) => a.value !== null ? (
|
|
84
|
-
|
|
84
|
+
return (t, l) => a.value !== null ? (A(), B(_, { key: 0 }, [
|
|
85
|
+
M.value ? H(t.$slots, "default", {
|
|
85
86
|
key: 0,
|
|
86
87
|
value: a.value.value,
|
|
87
|
-
y:
|
|
88
|
-
bgColor:
|
|
88
|
+
y: V.value,
|
|
89
|
+
bgColor: L.value,
|
|
89
90
|
isLive: a.value.isLive,
|
|
90
|
-
direction:
|
|
91
|
-
format:
|
|
91
|
+
direction: k.value,
|
|
92
|
+
format: F.value,
|
|
92
93
|
opacity: h.value
|
|
93
|
-
}) : (
|
|
94
|
-
|
|
95
|
-
style:
|
|
94
|
+
}) : (A(), B(_, { key: 1 }, [
|
|
95
|
+
T("div", {
|
|
96
|
+
style: z({
|
|
96
97
|
position: "absolute",
|
|
97
|
-
left: "
|
|
98
|
-
right:
|
|
99
|
-
top:
|
|
98
|
+
left: g(e).plotLeft + "px",
|
|
99
|
+
right: (g(e).yAxisPosition === "left" ? 0 : g(e).yAxisWidth) + "px",
|
|
100
|
+
top: V.value + "px",
|
|
100
101
|
height: "0",
|
|
101
|
-
borderTop: "1px dashed " +
|
|
102
|
+
borderTop: "1px dashed " + L.value,
|
|
102
103
|
opacity: h.value * 0.5,
|
|
103
104
|
pointerEvents: "none",
|
|
104
105
|
zIndex: 2
|
|
105
106
|
})
|
|
106
107
|
}, null, 4),
|
|
107
|
-
|
|
108
|
-
style:
|
|
108
|
+
T("div", {
|
|
109
|
+
style: z({
|
|
109
110
|
position: "absolute",
|
|
110
|
-
|
|
111
|
-
top:
|
|
111
|
+
[g(e).yAxisPosition]: "4px",
|
|
112
|
+
top: V.value + "px",
|
|
112
113
|
transform: "translateY(-50%)",
|
|
113
114
|
opacity: h.value,
|
|
114
115
|
pointerEvents: "auto",
|
|
115
116
|
zIndex: 3,
|
|
116
|
-
background:
|
|
117
|
+
background: L.value,
|
|
117
118
|
color: r.value.yLabel.textColor,
|
|
118
119
|
fontSize: r.value.yLabel.fontSize + "px",
|
|
119
120
|
fontFamily: r.value.typography.fontFamily,
|
|
@@ -124,8 +125,8 @@ const re = /* @__PURE__ */ q({
|
|
|
124
125
|
})
|
|
125
126
|
}, [
|
|
126
127
|
J(ne, {
|
|
127
|
-
value:
|
|
128
|
-
format:
|
|
128
|
+
value: P.value,
|
|
129
|
+
format: F.value,
|
|
129
130
|
"spin-duration": 350
|
|
130
131
|
}, null, 8, ["value", "format"])
|
|
131
132
|
], 4)
|
|
@@ -134,5 +135,5 @@ const re = /* @__PURE__ */ q({
|
|
|
134
135
|
}
|
|
135
136
|
});
|
|
136
137
|
export {
|
|
137
|
-
|
|
138
|
+
ie as default
|
|
138
139
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wick-charts/vue",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
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.6.
|
|
48
|
+
"@wick-charts/core": "^0.6.3"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "vite build"
|
package/src/ChartContainer.vue
CHANGED
|
@@ -81,7 +81,7 @@ const props = withDefaults(
|
|
|
81
81
|
* change, use `viewport.initialRange` instead.
|
|
82
82
|
*/
|
|
83
83
|
visibleRange?: VisibleRangeSpec;
|
|
84
|
-
/** Show the chart background gradient. Live. Defaults to
|
|
84
|
+
/** Show the chart background gradient. Live. Defaults to false. */
|
|
85
85
|
gradient?: boolean;
|
|
86
86
|
/**
|
|
87
87
|
* Enable zoom, pan, and crosshair interactions. Defaults to true.
|
|
@@ -96,16 +96,20 @@ const props = withDefaults(
|
|
|
96
96
|
* Soft fade-out at the top of the plot area, so series content dissolves
|
|
97
97
|
* under a floating `<Title>` / `<InfoBar>` instead of colliding with it.
|
|
98
98
|
* The mask erases canvas alpha rather than painting a cover color, so it
|
|
99
|
-
* stays correct over
|
|
99
|
+
* stays correct over a background gradient. Live.
|
|
100
100
|
*
|
|
101
101
|
* Two directions, independently controlled:
|
|
102
102
|
*
|
|
103
103
|
* **X (under the axis)** — panning content slides under the Y-axis
|
|
104
104
|
* column and dissolves instead of hard-clipping at the pane edge. **On
|
|
105
105
|
* by default** as a 60px ramp that finishes just inside the axis
|
|
106
|
-
* column, before the label glyphs
|
|
107
|
-
*
|
|
108
|
-
*
|
|
106
|
+
* column, before the label glyphs — on whichever side
|
|
107
|
+
* `axis.y.position` puts it (a left-axis ramp ends at the pane edge
|
|
108
|
+
* instead). The axis side's key (`{ right }` by default, `{ left }` for
|
|
109
|
+
* a left axis) overrides the total ramp width in CSS px (`0` disables);
|
|
110
|
+
* the other key adds a plain zone at the opposite pane edge (default
|
|
111
|
+
* off). A chart with only pie / heatmap series has no axis column, so
|
|
112
|
+
* both keys add plain zones there.
|
|
109
113
|
*
|
|
110
114
|
* **Top (under the header)** — opt-in. `true` enables the auto zone
|
|
111
115
|
* (measured header + 24px run-out, half the header fold-in released so
|
|
@@ -185,7 +189,7 @@ const props = withDefaults(
|
|
|
185
189
|
}>(),
|
|
186
190
|
{
|
|
187
191
|
theme: () => catppuccin.theme,
|
|
188
|
-
gradient:
|
|
192
|
+
gradient: false,
|
|
189
193
|
// Vue's type-based `defineProps` casts an absent Boolean-typed prop to
|
|
190
194
|
// `false` (not `undefined`) — without this explicit default, every chart
|
|
191
195
|
// that didn't pass `:interactive="true"` silently got zero pan/zoom/
|
|
@@ -229,6 +233,13 @@ provide(NavigatorAnchorKey, navigatorAnchor);
|
|
|
229
233
|
let resizeObserver: ResizeObserver | null = null;
|
|
230
234
|
const topOverlayHeight = ref(0);
|
|
231
235
|
|
|
236
|
+
// Watching `chart` hands a rebuilt instance the current height too.
|
|
237
|
+
watch(
|
|
238
|
+
() => [chart.value, props.headerLayout === 'overlay' ? topOverlayHeight.value : 0] as const,
|
|
239
|
+
([instance, height]) => instance?.setHeaderHeight(height),
|
|
240
|
+
{ immediate: true },
|
|
241
|
+
);
|
|
242
|
+
|
|
232
243
|
/** Run-out below the measured header (CSS px) for the auto `fade` zone —
|
|
233
244
|
* content starts dissolving this far before it slides under the header. */
|
|
234
245
|
const FADE_AUTO_BAND = 24;
|
|
@@ -310,8 +321,9 @@ function applyFade() {
|
|
|
310
321
|
return;
|
|
311
322
|
}
|
|
312
323
|
|
|
313
|
-
// Omitting
|
|
314
|
-
// explicit value (or the all-off branch
|
|
324
|
+
// Omitting a side keeps the core's auto default (the under-the-axis ramp
|
|
325
|
+
// on the Y-axis side); only an explicit value (or the all-off branch
|
|
326
|
+
// above) overrides it.
|
|
315
327
|
const next: FadeConfig = { top: resolvedFadeTop.value };
|
|
316
328
|
if (typeof props.fade === 'object') {
|
|
317
329
|
if (props.fade.right !== undefined) next.right = props.fade.right;
|
package/src/composables.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChartInstance, CrosshairPosition, VisibleRange, YRange } from '@wick-charts/core';
|
|
1
|
+
import type { ChartInstance, CrosshairPosition, VisibleRange, YAxisPosition, YRange } from '@wick-charts/core';
|
|
2
2
|
import { type Ref, onMounted, onUnmounted, ref } from 'vue';
|
|
3
3
|
|
|
4
4
|
export function useVisibleRange(chart: ChartInstance): Ref<VisibleRange> {
|
|
@@ -21,6 +21,40 @@ export function useYRange(chart: ChartInstance): Ref<YRange> {
|
|
|
21
21
|
return range;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export interface AxisLayout {
|
|
25
|
+
yAxisPosition: YAxisPosition;
|
|
26
|
+
yAxisWidth: number;
|
|
27
|
+
xAxisHeight: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readAxisLayout(chart: ChartInstance): AxisLayout {
|
|
31
|
+
return { yAxisPosition: chart.yAxisPosition, yAxisWidth: chart.yAxisWidth, xAxisHeight: chart.xAxisHeight };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The axis gutters overlays anchor to — refreshed on a Y-axis side or gutter size change. */
|
|
35
|
+
export function useAxisLayout(chart: ChartInstance): Ref<AxisLayout> {
|
|
36
|
+
const layout = ref<AxisLayout>(readAxisLayout(chart)) as Ref<AxisLayout>;
|
|
37
|
+
const handler = () => {
|
|
38
|
+
const next = readAxisLayout(chart);
|
|
39
|
+
const prev = layout.value;
|
|
40
|
+
const unchanged =
|
|
41
|
+
prev.yAxisPosition === next.yAxisPosition &&
|
|
42
|
+
prev.yAxisWidth === next.yAxisWidth &&
|
|
43
|
+
prev.xAxisHeight === next.xAxisHeight;
|
|
44
|
+
if (unchanged) return;
|
|
45
|
+
|
|
46
|
+
layout.value = next;
|
|
47
|
+
};
|
|
48
|
+
onMounted(() => {
|
|
49
|
+
// The axis watcher may have landed a new config between setup and mount.
|
|
50
|
+
handler();
|
|
51
|
+
chart.on('viewportChange', handler);
|
|
52
|
+
});
|
|
53
|
+
onUnmounted(() => chart.off('viewportChange', handler));
|
|
54
|
+
|
|
55
|
+
return layout;
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
export function useLastYValue(chart: ChartInstance, seriesId: string): Ref<{ value: number; isLive: boolean } | null> {
|
|
25
59
|
let snapshot = chart.getLastValue(seriesId);
|
|
26
60
|
// Track the pixel Y the snapshot maps to so a pan/zoom/resize that leaves the
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export type {
|
|
|
61
61
|
LineSeriesOptions,
|
|
62
62
|
LoadingIndicatorArgs,
|
|
63
63
|
LoadingIndicatorFn,
|
|
64
|
+
LogScaleOptions,
|
|
64
65
|
MarkerConfig,
|
|
65
66
|
MarkerShape,
|
|
66
67
|
MultiLayerData,
|
|
@@ -86,6 +87,7 @@ export type {
|
|
|
86
87
|
ReferenceLineStyle,
|
|
87
88
|
RenderPadding,
|
|
88
89
|
RoundedRectArgs,
|
|
90
|
+
ScaleTickArgs,
|
|
89
91
|
SeriesCreateEnv,
|
|
90
92
|
SeriesDefinition,
|
|
91
93
|
SeriesHoverInfo,
|
|
@@ -128,7 +130,9 @@ export type {
|
|
|
128
130
|
WaveIntroTransform,
|
|
129
131
|
XAxisConfig,
|
|
130
132
|
YAxisConfig,
|
|
133
|
+
YAxisPosition,
|
|
131
134
|
YRange,
|
|
135
|
+
YScaleTransform,
|
|
132
136
|
} from '@wick-charts/core';
|
|
133
137
|
// Painters — custom per-element drawing for bar / candle / line series.
|
|
134
138
|
export {
|
|
@@ -172,6 +176,7 @@ export {
|
|
|
172
176
|
isTimeSeriesRenderer,
|
|
173
177
|
lavenderMist,
|
|
174
178
|
lightPink,
|
|
179
|
+
logScale,
|
|
175
180
|
materialPalenight,
|
|
176
181
|
minimalLight,
|
|
177
182
|
mintBreeze,
|
package/src/ui/Crosshair.vue
CHANGED
|
@@ -56,11 +56,11 @@ const labelStyle = computed(() => ({
|
|
|
56
56
|
|
|
57
57
|
<template>
|
|
58
58
|
<template v-if="position">
|
|
59
|
-
<!-- Y label on
|
|
59
|
+
<!-- Y label on the value axis -->
|
|
60
60
|
<div
|
|
61
61
|
:style="{
|
|
62
62
|
position: 'absolute',
|
|
63
|
-
|
|
63
|
+
[chart.yAxisPosition]: '0',
|
|
64
64
|
top: position.mediaY + 'px',
|
|
65
65
|
transform: 'translateY(-50%)',
|
|
66
66
|
...labelStyle,
|
package/src/ui/TimeAxis.vue
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { mountAxisLabels } from '@wick-charts/core';
|
|
3
|
-
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
|
3
|
+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
4
4
|
|
|
5
|
+
import { useAxisLayout } from '../composables';
|
|
5
6
|
import { useChartInstance } from '../context';
|
|
6
7
|
|
|
7
8
|
const props = defineProps<{
|
|
@@ -12,6 +13,8 @@ const props = defineProps<{
|
|
|
12
13
|
}>();
|
|
13
14
|
|
|
14
15
|
const chart = useChartInstance();
|
|
16
|
+
const layout = useAxisLayout(chart);
|
|
17
|
+
const onLeft = computed(() => layout.value.yAxisPosition === 'left');
|
|
15
18
|
const containerRef = ref<HTMLDivElement | null>(null);
|
|
16
19
|
|
|
17
20
|
const applyDensity = () => {
|
|
@@ -42,10 +45,10 @@ onUnmounted(() => {
|
|
|
42
45
|
ref="containerRef"
|
|
43
46
|
:style="{
|
|
44
47
|
position: 'absolute',
|
|
45
|
-
left:
|
|
48
|
+
left: (onLeft ? layout.yAxisWidth : 0) + 'px',
|
|
46
49
|
bottom: '0',
|
|
47
|
-
right:
|
|
48
|
-
height:
|
|
50
|
+
right: (onLeft ? 0 : layout.yAxisWidth) + 'px',
|
|
51
|
+
height: layout.xAxisHeight + 'px',
|
|
49
52
|
pointerEvents: 'none',
|
|
50
53
|
display: 'flex',
|
|
51
54
|
alignItems: 'center',
|
package/src/ui/Tooltip.vue
CHANGED
|
@@ -104,16 +104,18 @@ watchEffect((onCleanup) => {
|
|
|
104
104
|
const floatingPos = computed<{ left: number; top: number } | null>(() => {
|
|
105
105
|
if (!crosshair.value || snapshots.value.length === 0) return null;
|
|
106
106
|
const mediaSize = chart.getMediaSize();
|
|
107
|
+
const chartLeft = chart.plotLeft;
|
|
107
108
|
const chartWidth = mediaSize.width - chart.yAxisWidth;
|
|
108
109
|
const chartHeight = mediaSize.height - chart.xAxisHeight;
|
|
109
110
|
|
|
110
111
|
if (hasCustomSlot.value) {
|
|
111
112
|
const size = measuredSize.value;
|
|
112
|
-
if (!size) return { left:
|
|
113
|
+
if (!size) return { left: chartLeft, top: 0 };
|
|
113
114
|
|
|
114
115
|
return computeTooltipPosition({
|
|
115
116
|
x: crosshair.value.mediaX,
|
|
116
117
|
y: crosshair.value.mediaY,
|
|
118
|
+
chartLeft,
|
|
117
119
|
chartWidth,
|
|
118
120
|
chartHeight,
|
|
119
121
|
tooltipWidth: size.width,
|
|
@@ -129,6 +131,7 @@ const floatingPos = computed<{ left: number; top: number } | null>(() => {
|
|
|
129
131
|
return computeTooltipPosition({
|
|
130
132
|
x: crosshair.value.mediaX,
|
|
131
133
|
y: crosshair.value.mediaY,
|
|
134
|
+
chartLeft,
|
|
132
135
|
chartWidth,
|
|
133
136
|
chartHeight,
|
|
134
137
|
tooltipWidth,
|
package/src/ui/YAxis.vue
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type ValueFormatter, mountAxisLabels } from '@wick-charts/core';
|
|
3
3
|
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
|
4
4
|
|
|
5
|
+
import { useAxisLayout } from '../composables';
|
|
5
6
|
import { useChartInstance } from '../context';
|
|
6
7
|
|
|
7
8
|
const props = defineProps<{
|
|
@@ -17,6 +18,7 @@ const props = defineProps<{
|
|
|
17
18
|
}>();
|
|
18
19
|
|
|
19
20
|
const chart = useChartInstance();
|
|
21
|
+
const layout = useAxisLayout(chart);
|
|
20
22
|
const containerRef = ref<HTMLDivElement | null>(null);
|
|
21
23
|
|
|
22
24
|
// Route the formatter through yScale so Crosshair / YLabel fallback use
|
|
@@ -52,10 +54,10 @@ onUnmounted(() => {
|
|
|
52
54
|
ref="containerRef"
|
|
53
55
|
:style="{
|
|
54
56
|
position: 'absolute',
|
|
55
|
-
|
|
57
|
+
[layout.yAxisPosition]: '0',
|
|
56
58
|
top: '0',
|
|
57
|
-
bottom:
|
|
58
|
-
width:
|
|
59
|
+
bottom: layout.xAxisHeight + 'px',
|
|
60
|
+
width: layout.yAxisWidth + 'px',
|
|
59
61
|
pointerEvents: 'none',
|
|
60
62
|
}"
|
|
61
63
|
/>
|
package/src/ui/YLabel.vue
CHANGED
|
@@ -273,8 +273,8 @@ const effectiveFormat = computed<ValueFormatter>(() => {
|
|
|
273
273
|
<div
|
|
274
274
|
:style="{
|
|
275
275
|
position: 'absolute',
|
|
276
|
-
left: '
|
|
277
|
-
right: chart.yAxisWidth + 'px',
|
|
276
|
+
left: chart.plotLeft + 'px',
|
|
277
|
+
right: (chart.yAxisPosition === 'left' ? 0 : chart.yAxisWidth) + 'px',
|
|
278
278
|
top: y + 'px',
|
|
279
279
|
height: '0',
|
|
280
280
|
borderTop: '1px dashed ' + bgColor,
|
|
@@ -286,7 +286,7 @@ const effectiveFormat = computed<ValueFormatter>(() => {
|
|
|
286
286
|
<div
|
|
287
287
|
:style="{
|
|
288
288
|
position: 'absolute',
|
|
289
|
-
|
|
289
|
+
[chart.yAxisPosition]: '4px',
|
|
290
290
|
top: y + 'px',
|
|
291
291
|
transform: 'translateY(-50%)',
|
|
292
292
|
opacity: opacity,
|