@wick-charts/vue 0.6.2 → 0.6.4

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 (61) hide show
  1. package/README.md +2 -2
  2. package/dist/BandSeries.vue.js +50 -0
  3. package/dist/BandSeries.vue2.js +4 -0
  4. package/dist/ChartContainer.vue.js +45 -39
  5. package/dist/composables.js +39 -26
  6. package/dist/core/dist/animation/config.js +92 -75
  7. package/dist/core/dist/axis/dom-labels.js +56 -43
  8. package/dist/core/dist/chart/badge-animator.js +30 -22
  9. package/dist/core/dist/chart/edge-indicators.js +22 -21
  10. package/dist/core/dist/chart/last-value.js +33 -26
  11. package/dist/core/dist/chart/options.js +5 -5
  12. package/dist/core/dist/chart/scale-guards.js +26 -0
  13. package/dist/core/dist/chart/y-target.js +46 -19
  14. package/dist/core/dist/chart.js +670 -518
  15. package/dist/core/dist/components/crosshair.js +8 -8
  16. package/dist/core/dist/components/grid.js +26 -17
  17. package/dist/core/dist/components/marker.js +28 -28
  18. package/dist/core/dist/components/reference-line.js +16 -16
  19. package/dist/core/dist/data/decimation.js +58 -39
  20. package/dist/core/dist/interactions/handler.js +3 -2
  21. package/dist/core/dist/interactions/zoom.js +6 -5
  22. package/dist/core/dist/navigator/controller.js +47 -44
  23. package/dist/core/dist/perf/perf-hud.js +3 -3
  24. package/dist/core/dist/scales/log-scale.js +76 -0
  25. package/dist/core/dist/scales/y-scale.js +134 -35
  26. package/dist/core/dist/series/band.js +670 -0
  27. package/dist/core/dist/series/bar-intro.js +11 -11
  28. package/dist/core/dist/series/bar.js +301 -251
  29. package/dist/core/dist/series/base-multi-layer.js +100 -76
  30. package/dist/core/dist/series/candlestick.js +238 -235
  31. package/dist/core/dist/series/heatmap.js +5 -5
  32. package/dist/core/dist/series/line-intro.js +50 -40
  33. package/dist/core/dist/series/line.js +561 -483
  34. package/dist/core/dist/series/painters/builtins.js +4 -4
  35. package/dist/core/dist/series/painters/canvas-path.js +64 -52
  36. package/dist/core/dist/series/register-builtins.js +9 -8
  37. package/dist/core/dist/series/scale-stack.js +25 -0
  38. package/dist/core/dist/snapshots.js +36 -35
  39. package/dist/core/dist/tooltip-position.js +5 -5
  40. package/dist/core/dist/utils/point.js +10 -0
  41. package/dist/index.d.ts +501 -128
  42. package/dist/index.js +195 -189
  43. package/dist/ui/Crosshair.vue.js +14 -14
  44. package/dist/ui/HeatmapTooltip.vue.js +6 -6
  45. package/dist/ui/InfoBar.vue.js +100 -81
  46. package/dist/ui/PieTooltip.vue.js +4 -4
  47. package/dist/ui/TimeAxis.vue.js +23 -22
  48. package/dist/ui/Tooltip.vue.js +140 -111
  49. package/dist/ui/YAxis.vue.js +25 -24
  50. package/dist/ui/YLabel.vue.js +63 -70
  51. package/package.json +2 -2
  52. package/src/BandSeries.vue +87 -0
  53. package/src/ChartContainer.vue +20 -8
  54. package/src/composables.ts +35 -1
  55. package/src/index.ts +13 -0
  56. package/src/ui/Crosshair.vue +2 -2
  57. package/src/ui/InfoBar.vue +20 -1
  58. package/src/ui/TimeAxis.vue +7 -4
  59. package/src/ui/Tooltip.vue +40 -3
  60. package/src/ui/YAxis.vue +5 -3
  61. package/src/ui/YLabel.vue +5 -16
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import type { BandData, BandSeriesOptions, SeriesSyncState } from '@wick-charts/core';
3
+ import { BandSeriesDef, EMPTY_SYNC_STATE, syncLayers, toLayers } from '@wick-charts/core';
4
+ import { onMounted, onUnmounted, ref, watch } from 'vue';
5
+
6
+ import { useChartInstance } from './context';
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ /**
11
+ * Band data — one band per layer. A single band passes a flat
12
+ * `{ time, low, high }[]`; several bands pass `BandPoint[][]`. To name/color
13
+ * a band, pass `{ label, color?, data }` (or an array of them). Time accepts
14
+ * ms or `Date`.
15
+ */
16
+ data: BandData;
17
+ /** Visual options override — fill, edge stroke, curve, entrance / intro / smoothing. */
18
+ options?: Partial<BandSeriesOptions>;
19
+ /** Stable series ID — same value across remounts. */
20
+ id?: string;
21
+ /** Show/hide the series without unmounting it — excludes it from the Y-range fit and tooltip/legend. Default `true`. Live. */
22
+ visible?: boolean;
23
+ }>(),
24
+ // Vue's type-based `defineProps` casts an absent Boolean-typed prop to
25
+ // `false` (not `undefined`) without an explicit default — see the
26
+ // `interactive` fix in ChartContainer.vue for the full explanation.
27
+ { visible: true },
28
+ );
29
+
30
+ const chart = useChartInstance();
31
+ const seriesId = ref<string | null>(null);
32
+ // Per-layer sync state — drives the shared append/keepLast/update/replace
33
+ // reconciliation (plus per-layer labels/colors) so streaming eases instead of
34
+ // snapping Y every tick.
35
+ let prevSync: SeriesSyncState[] = [];
36
+
37
+ function applyData(id: string, data: BandData): void {
38
+ prevSync = syncLayers({ chart, id, data, prev: prevSync });
39
+ }
40
+
41
+ onMounted(() => {
42
+ const layerCount = toLayers(props.data).length;
43
+ const id = chart.addSeries(BandSeriesDef, {
44
+ ...props.options,
45
+ layers: layerCount,
46
+ id: props.id,
47
+ });
48
+ seriesId.value = id;
49
+ prevSync = new Array(layerCount).fill(EMPTY_SYNC_STATE);
50
+ // Lazy watcher — apply initial data here so static-data mounts render without a no-op first frame.
51
+ applyData(id, props.data);
52
+ chart.setSeriesVisible(id, props.visible);
53
+ });
54
+
55
+ onUnmounted(() => {
56
+ if (seriesId.value) chart.removeSeries(seriesId.value);
57
+ });
58
+
59
+ watch(
60
+ () => props.data,
61
+ (data) => {
62
+ const id = seriesId.value;
63
+ if (!id) return;
64
+
65
+ applyData(id, data);
66
+ },
67
+ );
68
+
69
+ watch(
70
+ () => props.options,
71
+ (options) => {
72
+ if (seriesId.value && options) {
73
+ chart.updateSeriesOptions(seriesId.value, options);
74
+ }
75
+ },
76
+ { deep: true },
77
+ );
78
+
79
+ watch(
80
+ () => props.visible,
81
+ (visible) => {
82
+ if (seriesId.value) chart.setSeriesVisible(seriesId.value, visible);
83
+ },
84
+ );
85
+ </script>
86
+
87
+ <template><span v-if="false" /></template>
@@ -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
@@ -13,6 +13,11 @@ export type {
13
13
  AnimationsConfig,
14
14
  AxisBound,
15
15
  AxisConfig,
16
+ BandData,
17
+ BandEntryAnimation,
18
+ BandPoint,
19
+ BandPointInput,
20
+ BandSeriesOptions,
16
21
  BarElementTransform,
17
22
  BarEntryAnimation,
18
23
  BarIntroDirectives,
@@ -51,6 +56,7 @@ export type {
51
56
  HeatmapSeriesOptions,
52
57
  HeatmapSeriesRenderer,
53
58
  HoverInfo,
59
+ LayerSnapshot,
54
60
  LegendItem,
55
61
  LineIntroDirectives,
56
62
  LineIntroFn,
@@ -61,6 +67,7 @@ export type {
61
67
  LineSeriesOptions,
62
68
  LoadingIndicatorArgs,
63
69
  LoadingIndicatorFn,
70
+ LogScaleOptions,
64
71
  MarkerConfig,
65
72
  MarkerShape,
66
73
  MultiLayerData,
@@ -86,6 +93,7 @@ export type {
86
93
  ReferenceLineStyle,
87
94
  RenderPadding,
88
95
  RoundedRectArgs,
96
+ ScaleTickArgs,
89
97
  SeriesCreateEnv,
90
98
  SeriesDefinition,
91
99
  SeriesHoverInfo,
@@ -128,10 +136,13 @@ export type {
128
136
  WaveIntroTransform,
129
137
  XAxisConfig,
130
138
  YAxisConfig,
139
+ YAxisPosition,
131
140
  YRange,
141
+ YScaleTransform,
132
142
  } from '@wick-charts/core';
133
143
  // Painters — custom per-element drawing for bar / candle / line series.
134
144
  export {
145
+ BandSeriesDef,
135
146
  BarSeriesDef,
136
147
  CandlestickSeriesDef,
137
148
  ChartInstance,
@@ -172,6 +183,7 @@ export {
172
183
  isTimeSeriesRenderer,
173
184
  lavenderMist,
174
185
  lightPink,
186
+ logScale,
175
187
  materialPalenight,
176
188
  minimalLight,
177
189
  mintBreeze,
@@ -214,6 +226,7 @@ export {
214
226
  wipeIntro,
215
227
  } from '@wick-charts/core';
216
228
 
229
+ export { default as BandSeries } from './BandSeries.vue';
217
230
  export { default as BarSeries } from './BarSeries.vue';
218
231
  export { default as CandlestickSeries } from './CandlestickSeries.vue';
219
232
  // Vue components
@@ -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,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import {
3
+ type BandPoint,
3
4
  type OHLCData,
4
5
  type SeriesSnapshot,
5
6
  type SnapshotSort,
@@ -98,9 +99,13 @@ const displayTime = computed(() => {
98
99
  const slots = useSlots();
99
100
  const hasCustomSlot = computed(() => typeof slots.default === 'function');
100
101
 
101
- function isOHLC(data: OHLCData | TimePoint): data is OHLCData {
102
+ function isOHLC(data: SeriesSnapshot['data']): data is OHLCData {
102
103
  return 'open' in data;
103
104
  }
105
+
106
+ function isBand(data: SeriesSnapshot['data']): data is BandPoint {
107
+ return 'high' in data && !isOHLC(data);
108
+ }
104
109
  </script>
105
110
 
106
111
  <template>
@@ -174,6 +179,20 @@ function isOHLC(data: OHLCData | TimePoint): data is OHLCData {
174
179
  </span>
175
180
  </template>
176
181
  </span>
182
+ <span v-else-if="isBand(s.data)" :style="{ display: 'inline-flex', alignItems: 'center', gap: '3px' }">
183
+ <span
184
+ :style="{
185
+ width: '6px',
186
+ height: '6px',
187
+ borderRadius: '1px',
188
+ background: s.color,
189
+ flexShrink: 0,
190
+ }"
191
+ />
192
+ <span :style="{ color: s.color, fontWeight: 500 }">
193
+ {{ effectiveFormat((s.data as BandPoint).low, 'low') }} – {{ effectiveFormat((s.data as BandPoint).high, 'high') }}
194
+ </span>
195
+ </span>
177
196
  <span v-else :style="{ display: 'inline-flex', alignItems: 'center', gap: '3px' }">
178
197
  <span
179
198
  :style="{
@@ -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',
@@ -1,5 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import {
3
+ type BandPoint,
3
4
  type OHLCData,
4
5
  type SeriesSnapshot,
5
6
  type SnapshotSort,
@@ -104,16 +105,18 @@ watchEffect((onCleanup) => {
104
105
  const floatingPos = computed<{ left: number; top: number } | null>(() => {
105
106
  if (!crosshair.value || snapshots.value.length === 0) return null;
106
107
  const mediaSize = chart.getMediaSize();
108
+ const chartLeft = chart.plotLeft;
107
109
  const chartWidth = mediaSize.width - chart.yAxisWidth;
108
110
  const chartHeight = mediaSize.height - chart.xAxisHeight;
109
111
 
110
112
  if (hasCustomSlot.value) {
111
113
  const size = measuredSize.value;
112
- if (!size) return { left: 0, top: 0 };
114
+ if (!size) return { left: chartLeft, top: 0 };
113
115
 
114
116
  return computeTooltipPosition({
115
117
  x: crosshair.value.mediaX,
116
118
  y: crosshair.value.mediaY,
119
+ chartLeft,
117
120
  chartWidth,
118
121
  chartHeight,
119
122
  tooltipWidth: size.width,
@@ -123,12 +126,15 @@ const floatingPos = computed<{ left: number; top: number } | null>(() => {
123
126
 
124
127
  const hasOHLC = snapshots.value.some((s) => 'open' in s.data);
125
128
  const lineCount = snapshots.value.filter((s) => !('open' in s.data)).length;
126
- const tooltipWidth = 160;
129
+ // A band row carries two numbers, so it needs a wider box than a line row.
130
+ const hasBand = snapshots.value.some((s) => 'high' in s.data && !('open' in s.data));
131
+ const tooltipWidth = hasBand ? 220 : 160;
127
132
  const tooltipHeight = hasOHLC ? 140 : 40 + lineCount * 22;
128
133
 
129
134
  return computeTooltipPosition({
130
135
  x: crosshair.value.mediaX,
131
136
  y: crosshair.value.mediaY,
137
+ chartLeft,
132
138
  chartWidth,
133
139
  chartHeight,
134
140
  tooltipWidth,
@@ -138,9 +144,13 @@ const floatingPos = computed<{ left: number; top: number } | null>(() => {
138
144
 
139
145
  const measured = computed(() => !hasCustomSlot.value || measuredSize.value !== null);
140
146
 
141
- function isOHLC(data: OHLCData | TimePoint): data is OHLCData {
147
+ function isOHLC(data: SeriesSnapshot['data']): data is OHLCData {
142
148
  return 'open' in data;
143
149
  }
150
+
151
+ function isBand(data: SeriesSnapshot['data']): data is BandPoint {
152
+ return 'high' in data && !isOHLC(data);
153
+ }
144
154
  </script>
145
155
 
146
156
  <template>
@@ -212,6 +222,33 @@ function isOHLC(data: OHLCData | TimePoint): data is OHLCData {
212
222
  </span>
213
223
  </template>
214
224
  </div>
225
+ <div
226
+ v-else-if="isBand(s.data)"
227
+ :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '2px 0' }"
228
+ >
229
+ <span
230
+ :style="{
231
+ width: '8px',
232
+ height: '8px',
233
+ borderRadius: '2px',
234
+ background: s.color,
235
+ flexShrink: 0,
236
+ }"
237
+ />
238
+ <span
239
+ :style="{
240
+ opacity: 0.6,
241
+ flex: '1',
242
+ minWidth: 0,
243
+ overflow: 'hidden',
244
+ textOverflow: 'ellipsis',
245
+ whiteSpace: 'nowrap',
246
+ }"
247
+ >{{ s.label ?? 'Range' }}</span>
248
+ <span :style="{ fontWeight: 600, color: s.color, whiteSpace: 'nowrap' }">
249
+ {{ effectiveFormat((s.data as BandPoint).low, 'low') }} – {{ effectiveFormat((s.data as BandPoint).high, 'high') }}
250
+ </span>
251
+ </div>
215
252
  <div v-else :style="{ display: 'flex', alignItems: 'center', gap: '8px', padding: '2px 0' }">
216
253
  <span
217
254
  :style="{
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
@@ -1,12 +1,12 @@
1
1
  <script setup lang="ts">
2
2
  import {
3
3
  BadgeAnimator,
4
- type ChartInstance,
5
4
  type ValueFormatter,
6
5
  type YLabelAnimate,
7
6
  firstVisibleValue,
8
7
  prefersReducedMotion,
9
8
  resolveAnimate,
9
+ resolveYLabelSeriesId,
10
10
  } from '@wick-charts/core';
11
11
  import { computed, onMounted, onUnmounted, ref, useSlots } from 'vue';
12
12
 
@@ -145,21 +145,10 @@ onUnmounted(() => {
145
145
  rafId = null;
146
146
  });
147
147
 
148
- function resolveSeriesId(c: ChartInstance, explicit: string | undefined): string | null {
149
- if (explicit !== undefined) return explicit;
150
-
151
- const singleLayer = c.getSeriesIdsByType('time', { visibleOnly: true, singleLayerOnly: true });
152
- if (singleLayer.length > 0) return singleLayer[0];
153
-
154
- const anyTime = c.getSeriesIdsByType('time', { visibleOnly: true });
155
-
156
- return anyTime.length > 0 ? anyTime[0] : null;
157
- }
158
-
159
148
  const resolvedId = computed<string | null>(() => {
160
149
  void bump.value;
161
150
 
162
- return resolveSeriesId(chart, props.seriesId);
151
+ return resolveYLabelSeriesId(chart, props.seriesId);
163
152
  });
164
153
 
165
154
  const last = computed<{ value: number; isLive: boolean } | null>(() => {
@@ -273,8 +262,8 @@ const effectiveFormat = computed<ValueFormatter>(() => {
273
262
  <div
274
263
  :style="{
275
264
  position: 'absolute',
276
- left: '0',
277
- right: chart.yAxisWidth + 'px',
265
+ left: chart.plotLeft + 'px',
266
+ right: (chart.yAxisPosition === 'left' ? 0 : chart.yAxisWidth) + 'px',
278
267
  top: y + 'px',
279
268
  height: '0',
280
269
  borderTop: '1px dashed ' + bgColor,
@@ -286,7 +275,7 @@ const effectiveFormat = computed<ValueFormatter>(() => {
286
275
  <div
287
276
  :style="{
288
277
  position: 'absolute',
289
- right: '4px',
278
+ [chart.yAxisPosition]: '4px',
290
279
  top: y + 'px',
291
280
  transform: 'translateY(-50%)',
292
281
  opacity: opacity,