@wick-charts/vue 0.6.2 → 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.
Files changed (44) hide show
  1. package/dist/ChartContainer.vue.js +43 -37
  2. package/dist/LineSeries.vue.js +2 -2
  3. package/dist/composables.js +39 -26
  4. package/dist/core/dist/axis/dom-labels.js +56 -43
  5. package/dist/core/dist/chart/options.js +5 -5
  6. package/dist/core/dist/chart/scale-guards.js +26 -0
  7. package/dist/core/dist/chart/y-target.js +46 -19
  8. package/dist/core/dist/chart.js +670 -519
  9. package/dist/core/dist/components/crosshair.js +8 -8
  10. package/dist/core/dist/components/grid.js +26 -17
  11. package/dist/core/dist/components/marker.js +28 -28
  12. package/dist/core/dist/components/reference-line.js +16 -16
  13. package/dist/core/dist/interactions/handler.js +3 -2
  14. package/dist/core/dist/interactions/zoom.js +6 -5
  15. package/dist/core/dist/navigator/controller.js +38 -35
  16. package/dist/core/dist/perf/perf-hud.js +3 -3
  17. package/dist/core/dist/scales/log-scale.js +76 -0
  18. package/dist/core/dist/scales/y-scale.js +134 -35
  19. package/dist/core/dist/series/bar-intro.js +11 -11
  20. package/dist/core/dist/series/bar.js +301 -251
  21. package/dist/core/dist/series/base-multi-layer.js +100 -76
  22. package/dist/core/dist/series/candlestick.js +244 -241
  23. package/dist/core/dist/series/line-intro.js +18 -18
  24. package/dist/core/dist/series/line.js +428 -390
  25. package/dist/core/dist/series/scale-stack.js +25 -0
  26. package/dist/core/dist/tooltip-position.js +5 -5
  27. package/dist/index.d.ts +246 -31
  28. package/dist/index.js +59 -57
  29. package/dist/ui/Crosshair.vue.js +14 -14
  30. package/dist/ui/HeatmapTooltip.vue.js +6 -6
  31. package/dist/ui/PieTooltip.vue.js +4 -4
  32. package/dist/ui/TimeAxis.vue.js +23 -22
  33. package/dist/ui/Tooltip.vue.js +71 -69
  34. package/dist/ui/YAxis.vue.js +25 -24
  35. package/dist/ui/YLabel.vue.js +48 -48
  36. package/package.json +2 -2
  37. package/src/ChartContainer.vue +20 -8
  38. package/src/composables.ts +35 -1
  39. package/src/index.ts +5 -0
  40. package/src/ui/Crosshair.vue +2 -2
  41. package/src/ui/TimeAxis.vue +7 -4
  42. package/src/ui/Tooltip.vue +4 -1
  43. package/src/ui/YAxis.vue +5 -3
  44. package/src/ui/YLabel.vue +3 -3
@@ -1,42 +1,43 @@
1
- import { defineComponent as m, ref as p, onMounted as r, watch as s, onUnmounted as o, openBlock as b, createElementBlock as f, 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({
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(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
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
- 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) => (b(), f("div", {
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: t,
29
- style: y({
29
+ ref: l,
30
+ style: x({
30
31
  position: "absolute",
31
- right: "0",
32
+ [i(e).yAxisPosition]: "0",
32
33
  top: "0",
33
- bottom: u(n).xAxisHeight + "px",
34
- width: u(n).yAxisWidth + "px",
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
- _ as default
42
+ F as default
42
43
  };
@@ -1,4 +1,4 @@
1
- import { defineComponent as q, useSlots as j, computed as n, ref as I, onMounted as G, onUnmounted as P, openBlock as w, createElementBlock as B, Fragment as A, renderSlot as W, createElementVNode as _, normalizeStyle as T, unref as H, createVNode as J, createCommentVNode as K } from "vue";
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
2
  import { prefersReducedMotion as Q } from "../core/dist/animation/reduced-motion.js";
3
3
  import { resolveAnimate as X, firstVisibleValue as Z, BadgeAnimator as ee } from "../core/dist/chart/badge-animator.js";
4
4
  import { useChartInstance as te } from "../context.js";
@@ -11,56 +11,56 @@ const ie = /* @__PURE__ */ q({
11
11
  format: { type: Function },
12
12
  animate: { type: [Boolean, Object] }
13
13
  },
14
- setup(z) {
15
- const f = z, e = te(), Y = j(), E = n(() => typeof Y.default == "function"), i = I(0), u = I(null);
16
- let p = null, g = null, o = null;
17
- const M = n(() => X(f.animate));
18
- function L() {
19
- o = null;
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));
18
+ function S() {
19
+ u = null;
20
20
  const t = s.value, l = t !== null ? e.getStackedLastValue(t) : null;
21
21
  if (t === null || !l) {
22
- p = null, g = null;
22
+ m = null, x = null;
23
23
  return;
24
24
  }
25
25
  const v = l.value;
26
- (p === null || g !== t) && (p = new ee(v), g = t);
27
- const { glide: d, countUp: U, settleMs: $ } = M.value, F = e.getIntroFront(t), c = p.frame({
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({
28
28
  target: v,
29
29
  // Only needed for the intro count-up; skip the visible-range binary search
30
30
  // entirely once the reveal has settled (the hot streaming path).
31
- firstVisible: F < 1 ? Z(e, t, v) : v,
32
- introFront: F,
31
+ firstVisible: I < 1 ? Z(e, t, v) : v,
32
+ introFront: I,
33
33
  now: performance.now(),
34
34
  glide: d,
35
35
  countUp: U,
36
36
  settleMs: $,
37
37
  reducedMotion: Q()
38
- }), y = u.value;
39
- (y === null || y.positionValue !== c.positionValue || y.textValue !== c.textValue || y.opacity !== c.opacity) && (u.value = { positionValue: c.positionValue, textValue: c.textValue, opacity: c.opacity }), c.animating && o === null && (o = requestAnimationFrame(L));
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));
40
40
  }
41
- function N() {
42
- o === null && (o = requestAnimationFrame(L));
41
+ function O() {
42
+ u === null && (u = requestAnimationFrame(S));
43
43
  }
44
- const m = () => {
45
- i.value++, N();
44
+ const p = () => {
45
+ i.value++, O();
46
46
  };
47
47
  G(() => {
48
- e.setYLabel(!0), e.on("overlayChange", m), e.on("viewportChange", m), e.getSeriesIds().length > 0 && m();
49
- }), P(() => {
50
- e.setYLabel(!1), e.off("overlayChange", m), e.off("viewportChange", m), o !== null && cancelAnimationFrame(o), o = null;
48
+ e.setYLabel(!0), e.on("overlayChange", p), e.on("viewportChange", p), e.getSeriesIds().length > 0 && p();
49
+ }), W(() => {
50
+ e.setYLabel(!1), e.off("overlayChange", p), e.off("viewportChange", p), u !== null && cancelAnimationFrame(u), u = null;
51
51
  });
52
- function O(t, l) {
52
+ function R(t, l) {
53
53
  if (l !== void 0) return l;
54
54
  const v = t.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 });
55
55
  if (v.length > 0) return v[0];
56
56
  const d = t.getSeriesIdsByType("time", { visibleOnly: !0 });
57
57
  return d.length > 0 ? d[0] : null;
58
58
  }
59
- const s = n(() => (i.value, O(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())), R = n(() => u.value !== null ? u.value.positionValue : a.value?.value ?? 0), D = n(() => u.value !== null ? u.value.textValue : a.value?.value ?? 0), h = n(() => u.value !== null ? u.value.opacity : s.value === null ? 1 : e.getIntroFront(s.value) < 1 ? 0 : 1), x = n(() => (i.value, a.value === null ? 0 : e.yScale.valueToY(R.value))), S = n(() => a.value === null || b.value === null ? "neutral" : a.value.value > b.value ? "up" : a.value.value < b.value ? "down" : "neutral"), V = n(() => {
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(() => {
60
60
  if (a.value === null) return r.value.yLabel.neutralBackground;
61
61
  if (!a.value.isLive) return r.value.axis.textColor;
62
62
  if (f.color) return f.color;
63
- switch (S.value) {
63
+ switch (k.value) {
64
64
  case "up":
65
65
  return r.value.yLabel.upBackground;
66
66
  case "down":
@@ -68,53 +68,53 @@ const ie = /* @__PURE__ */ q({
68
68
  default:
69
69
  return r.value.yLabel.neutralBackground;
70
70
  }
71
- }), k = n(() => {
71
+ }), C = n(() => {
72
72
  i.value;
73
73
  const t = e.yScale.getRange(), l = t.max - t.min;
74
74
  return l < 0.1 ? 6 : l < 10 ? 4 : l < 1e3 ? 2 : 0;
75
- }), C = n(() => {
75
+ }), F = n(() => {
76
76
  if (f.format) return f.format;
77
77
  const t = new Intl.NumberFormat("en-US", {
78
- minimumFractionDigits: k.value,
79
- maximumFractionDigits: k.value,
78
+ minimumFractionDigits: C.value,
79
+ maximumFractionDigits: C.value,
80
80
  useGrouping: !1
81
81
  });
82
82
  return (l) => t.format(l);
83
83
  });
84
- return (t, l) => a.value !== null ? (w(), B(A, { key: 0 }, [
85
- E.value ? W(t.$slots, "default", {
84
+ return (t, l) => a.value !== null ? (A(), B(_, { key: 0 }, [
85
+ M.value ? H(t.$slots, "default", {
86
86
  key: 0,
87
87
  value: a.value.value,
88
- y: x.value,
89
- bgColor: V.value,
88
+ y: V.value,
89
+ bgColor: L.value,
90
90
  isLive: a.value.isLive,
91
- direction: S.value,
92
- format: C.value,
91
+ direction: k.value,
92
+ format: F.value,
93
93
  opacity: h.value
94
- }) : (w(), B(A, { key: 1 }, [
95
- _("div", {
96
- style: T({
94
+ }) : (A(), B(_, { key: 1 }, [
95
+ T("div", {
96
+ style: z({
97
97
  position: "absolute",
98
- left: "0",
99
- right: H(e).yAxisWidth + "px",
100
- top: x.value + "px",
98
+ left: g(e).plotLeft + "px",
99
+ right: (g(e).yAxisPosition === "left" ? 0 : g(e).yAxisWidth) + "px",
100
+ top: V.value + "px",
101
101
  height: "0",
102
- borderTop: "1px dashed " + V.value,
102
+ borderTop: "1px dashed " + L.value,
103
103
  opacity: h.value * 0.5,
104
104
  pointerEvents: "none",
105
105
  zIndex: 2
106
106
  })
107
107
  }, null, 4),
108
- _("div", {
109
- style: T({
108
+ T("div", {
109
+ style: z({
110
110
  position: "absolute",
111
- right: "4px",
112
- top: x.value + "px",
111
+ [g(e).yAxisPosition]: "4px",
112
+ top: V.value + "px",
113
113
  transform: "translateY(-50%)",
114
114
  opacity: h.value,
115
115
  pointerEvents: "auto",
116
116
  zIndex: 3,
117
- background: V.value,
117
+ background: L.value,
118
118
  color: r.value.yLabel.textColor,
119
119
  fontSize: r.value.yLabel.fontSize + "px",
120
120
  fontFamily: r.value.typography.fontFamily,
@@ -125,8 +125,8 @@ const ie = /* @__PURE__ */ q({
125
125
  })
126
126
  }, [
127
127
  J(ne, {
128
- value: D.value,
129
- format: C.value,
128
+ value: P.value,
129
+ format: F.value,
130
130
  "spin-duration": 350
131
131
  }, null, 8, ["value", "format"])
132
132
  ], 4)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wick-charts/vue",
3
- "version": "0.6.2",
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.2"
48
+ "@wick-charts/core": "^0.6.3"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "vite build"
@@ -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 true. */
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 the default background gradient. Live.
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; `{ right }` overrides the total
107
- * ramp width in CSS px (`0` disables). `{ left }` adds the mirror zone
108
- * at the left pane edge (default off).
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: true,
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 `right` keeps the core's under-the-axis default armed; only an
314
- // explicit value (or the all-off branch above) overrides it.
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;
@@ -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,
@@ -56,11 +56,11 @@ const labelStyle = computed(() => ({
56
56
 
57
57
  <template>
58
58
  <template v-if="position">
59
- <!-- Y label on right axis -->
59
+ <!-- Y label on the value axis -->
60
60
  <div
61
61
  :style="{
62
62
  position: 'absolute',
63
- right: '0',
63
+ [chart.yAxisPosition]: '0',
64
64
  top: position.mediaY + 'px',
65
65
  transform: 'translateY(-50%)',
66
66
  ...labelStyle,
@@ -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: '0',
48
+ left: (onLeft ? layout.yAxisWidth : 0) + 'px',
46
49
  bottom: '0',
47
- right: chart.yAxisWidth + 'px',
48
- height: chart.xAxisHeight + 'px',
50
+ right: (onLeft ? 0 : layout.yAxisWidth) + 'px',
51
+ height: layout.xAxisHeight + 'px',
49
52
  pointerEvents: 'none',
50
53
  display: 'flex',
51
54
  alignItems: 'center',
@@ -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: 0, top: 0 };
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
- right: '0',
57
+ [layout.yAxisPosition]: '0',
56
58
  top: '0',
57
- bottom: chart.xAxisHeight + 'px',
58
- width: chart.yAxisWidth + 'px',
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: '0',
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
- right: '4px',
289
+ [chart.yAxisPosition]: '4px',
290
290
  top: y + 'px',
291
291
  transform: 'translateY(-50%)',
292
292
  opacity: opacity,