@wick-charts/react 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/src/ui/YAxis.tsx CHANGED
@@ -1,16 +1,9 @@
1
1
  import { useLayoutEffect, useRef } from 'react';
2
2
 
3
- import { resolveAxisFontSize, resolveAxisTextColor, type ValueFormatter } from '@wick-charts/core';
3
+ import { type ValueFormatter, mountAxisLabels } from '@wick-charts/core';
4
4
 
5
5
  import { useChartInstance } from '../context';
6
6
  import { useYRange } from '../store-bridge';
7
- import { AXIS_LABEL_CLEANUP_MS, AXIS_LABEL_FADE_CSS } from './axisFade';
8
-
9
- interface TrackedTick {
10
- opacity: number;
11
- addedAt: number;
12
- fadedAt?: number;
13
- }
14
7
 
15
8
  export interface YAxisProps {
16
9
  /**
@@ -29,7 +22,9 @@ export interface YAxisProps {
29
22
 
30
23
  export function YAxis({ format, labelCount, minLabelSpacing }: YAxisProps = {}) {
31
24
  const chart = useChartInstance();
32
- useYRange(chart); // subscribe to viewport changes so ticks re-render
25
+ useYRange(chart);
26
+
27
+ const containerRef = useRef<HTMLDivElement | null>(null);
33
28
 
34
29
  // Route the prop through yScale so the *same* formatter drives every
35
30
  // surface that reads `yScale.formatY()` (Crosshair, YLabel fallback).
@@ -50,42 +45,16 @@ export function YAxis({ format, labelCount, minLabelSpacing }: YAxisProps = {})
50
45
  };
51
46
  }, [chart, labelCount, minLabelSpacing]);
52
47
 
53
- const theme = chart.getTheme();
54
- const currentTicks = chart.yScale.niceTickValues();
55
- const currentSet = new Set(currentTicks);
56
-
57
- const mapRef = useRef<Map<number, TrackedTick>>(new Map());
58
- const map = mapRef.current;
59
- const now = performance.now();
60
-
61
- for (const p of currentTicks) {
62
- if (!map.has(p)) {
63
- map.set(p, { opacity: 1, addedAt: now });
64
- } else {
65
- map.get(p)!.opacity = 1;
66
- }
67
- }
68
-
69
- for (const [p, entry] of map) {
70
- if (!currentSet.has(p)) {
71
- if (entry.opacity !== 0) {
72
- entry.opacity = 0;
73
- entry.fadedAt = now;
74
- }
75
- }
76
- }
77
-
78
- // Cleanup buffer matches the shared AXIS_LABEL_CLEANUP_MS — see axisFade.ts.
79
- for (const [p, entry] of map) {
80
- if (entry.opacity === 0 && entry.fadedAt !== undefined && now - entry.fadedAt > AXIS_LABEL_CLEANUP_MS) {
81
- map.delete(p);
82
- }
83
- }
48
+ useLayoutEffect(() => {
49
+ const container = containerRef.current;
50
+ if (container === null) return;
84
51
 
85
- const allTicks = Array.from(map.entries());
52
+ return mountAxisLabels({ chart, container, axis: 'y' });
53
+ }, [chart]);
86
54
 
87
55
  return (
88
56
  <div
57
+ ref={containerRef}
89
58
  style={{
90
59
  position: 'absolute',
91
60
  right: 0,
@@ -94,31 +63,6 @@ export function YAxis({ format, labelCount, minLabelSpacing }: YAxisProps = {})
94
63
  width: chart.yAxisWidth,
95
64
  pointerEvents: 'none',
96
65
  }}
97
- >
98
- {allTicks.map(([price, entry]) => {
99
- const y = chart.yScale.valueToY(price);
100
- return (
101
- <span
102
- key={price}
103
- style={{
104
- position: 'absolute',
105
- right: 8,
106
- top: y,
107
- transform: 'translateY(-50%)',
108
- color: resolveAxisTextColor(theme, 'y'),
109
- fontSize: resolveAxisFontSize(theme, 'y'),
110
- fontFamily: theme.typography.fontFamily,
111
- fontVariantNumeric: 'tabular-nums',
112
- userSelect: 'none',
113
- opacity: entry.opacity,
114
- transition: AXIS_LABEL_FADE_CSS,
115
- willChange: 'opacity',
116
- }}
117
- >
118
- {chart.yScale.formatY(price)}
119
- </span>
120
- );
121
- })}
122
- </div>
66
+ />
123
67
  );
124
68
  }
@@ -1,23 +0,0 @@
1
- /**
2
- * Axis-label fade timing — shared between {@link TimeAxis} and {@link YAxis}.
3
- *
4
- * The fade is a pure CSS opacity transition, not Animator-driven, because the
5
- * label set itself is rebuilt on every render: a tick that "leaves" the
6
- * range becomes a separate DOM node fading out while a new node fades in,
7
- * and inline `transition` is the cheapest way to crossfade them without a
8
- * per-tick Animator instance.
9
- *
10
- * The duration matches the chart-level `DEFAULT_ENTER_MS` / `streamTick` so
11
- * label transitions land in lockstep with the X re-fit, Y range chase, and
12
- * series live-track. Cleanup buffer leaves the node mounted past the
13
- * visible fade so React doesn't unmount it mid-transition.
14
- */
15
-
16
- const AXIS_LABEL_FADE_MS = 250;
17
-
18
- /** Inline `style.transition` value the axis label spans use. */
19
- export const AXIS_LABEL_FADE_CSS = `opacity ${AXIS_LABEL_FADE_MS / 1000}s ease`;
20
-
21
- /** Time after which a faded-out tick can be dropped from the persistent map.
22
- * `2 * AXIS_LABEL_FADE_MS` — one transition plus a frame margin. */
23
- export const AXIS_LABEL_CLEANUP_MS = AXIS_LABEL_FADE_MS * 2;