react-native-livechart 3.9.0 → 3.11.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/dist/components/AreaDotsOverlay.d.ts +24 -0
- package/dist/components/AreaDotsOverlay.d.ts.map +1 -0
- package/dist/components/AxisLabelOverlay.d.ts +19 -3
- package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +5 -1
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomTooltipOverlay.d.ts +8 -2
- package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
- package/dist/components/ExtremaConnectorOverlay.d.ts +32 -0
- package/dist/components/ExtremaConnectorOverlay.d.ts.map +1 -0
- package/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/core/liveChartEngineTick.d.ts +11 -0
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/liveChartSeriesEngineTick.d.ts +10 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +45 -2
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +18 -2
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts +4 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +12 -0
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/theme.d.ts +7 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/types.d.ts +103 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AreaDotsOverlay.tsx +62 -0
- package/src/components/AxisLabelOverlay.tsx +399 -46
- package/src/components/CrosshairOverlay.tsx +15 -5
- package/src/components/CustomTooltipOverlay.tsx +30 -5
- package/src/components/ExtremaConnectorOverlay.tsx +193 -0
- package/src/components/LiveChart.tsx +78 -8
- package/src/components/LiveChartSeries.tsx +18 -0
- package/src/core/liveChartEngineTick.ts +51 -6
- package/src/core/liveChartSeriesEngineTick.ts +34 -0
- package/src/core/resolveConfig.ts +100 -2
- package/src/core/useLiveChartEngine.ts +42 -2
- package/src/core/useLiveChartSeriesEngine.ts +26 -0
- package/src/hooks/crosshairShared.ts +20 -1
- package/src/hooks/useCrosshair.ts +6 -0
- package/src/index.ts +1 -0
- package/src/theme.ts +14 -0
- package/src/types.ts +105 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AreaDotsConfig,
|
|
2
3
|
AxisLabelConfig,
|
|
3
4
|
BadgeConfig,
|
|
4
5
|
BadgeVariant,
|
|
@@ -10,6 +11,7 @@ import type {
|
|
|
10
11
|
LeftEdgeFadeConfig,
|
|
11
12
|
LegendConfig,
|
|
12
13
|
LegendStyle,
|
|
14
|
+
LineStyleConfig,
|
|
13
15
|
LiveChartMetrics,
|
|
14
16
|
LiveChartMetricsOverride,
|
|
15
17
|
DotConfig,
|
|
@@ -61,12 +63,35 @@ export interface ResolvedYAxisConfig {
|
|
|
61
63
|
minGap: number;
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/** Resolved straight-line styling (connector, etc.). `color: undefined` → caller default. */
|
|
67
|
+
export interface ResolvedLineStyleConfig {
|
|
68
|
+
color: string | undefined;
|
|
69
|
+
strokeWidth: number;
|
|
70
|
+
/** undefined → solid (no dash). */
|
|
71
|
+
intervals: [number, number] | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
export interface ResolvedAxisLabelConfig {
|
|
65
75
|
/** undefined → use the chart's `formatValue` at render time. */
|
|
66
76
|
format?: (v: number) => string;
|
|
67
77
|
/** undefined → use the muted default label color at render time. */
|
|
68
78
|
color?: string;
|
|
69
|
-
|
|
79
|
+
/** `"left"`/`"right"` pin to that edge; `"extrema"`(-`edge`) tracks the data point. */
|
|
80
|
+
position: "left" | "right" | "extrema" | "extrema-edge";
|
|
81
|
+
/** undefined → the built-in default text size (11). */
|
|
82
|
+
fontSize?: number;
|
|
83
|
+
/** undefined → the platform `<Text>` default weight. */
|
|
84
|
+
fontWeight?: FontWeight;
|
|
85
|
+
/** undefined → the platform `<Text>` default family. */
|
|
86
|
+
fontFamily?: string;
|
|
87
|
+
/** Extrema dot color; undefined → use `color`. */
|
|
88
|
+
dotColor?: string;
|
|
89
|
+
/** Extrema dot diameter (px); undefined → the built-in default (7). */
|
|
90
|
+
dotSize?: number;
|
|
91
|
+
/** Extrema — whether to draw the marker dot. */
|
|
92
|
+
dot: boolean;
|
|
93
|
+
/** `"extrema-edge"` connector line (dot → edge label); null → none. */
|
|
94
|
+
connector: ResolvedLineStyleConfig | null;
|
|
70
95
|
/** When set, the built-in value label is replaced by this custom element. */
|
|
71
96
|
render?: () => ReactElement | null;
|
|
72
97
|
}
|
|
@@ -134,6 +159,14 @@ export interface ResolvedGradientConfig {
|
|
|
134
159
|
positions: number[] | undefined;
|
|
135
160
|
}
|
|
136
161
|
|
|
162
|
+
export interface ResolvedAreaDotsConfig {
|
|
163
|
+
spacing: number;
|
|
164
|
+
size: number;
|
|
165
|
+
/** undefined → derive a faint tint from the line/accent color at render time. */
|
|
166
|
+
color: string | undefined;
|
|
167
|
+
opacity: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
137
170
|
export interface ResolvedPulseConfig {
|
|
138
171
|
interval: number;
|
|
139
172
|
duration: number;
|
|
@@ -333,18 +366,65 @@ const AXIS_LABEL_DEFAULTS: ResolvedAxisLabelConfig = {
|
|
|
333
366
|
format: undefined,
|
|
334
367
|
color: undefined,
|
|
335
368
|
position: "right",
|
|
369
|
+
fontSize: undefined,
|
|
370
|
+
fontWeight: undefined,
|
|
371
|
+
fontFamily: undefined,
|
|
372
|
+
dotColor: undefined,
|
|
373
|
+
dotSize: undefined,
|
|
374
|
+
dot: true,
|
|
375
|
+
// Always overwritten by resolveAxisLabel (per-position default-on); placeholder.
|
|
376
|
+
connector: null,
|
|
336
377
|
render: undefined,
|
|
337
378
|
};
|
|
338
379
|
|
|
380
|
+
/** Dashed by default — a subtle guide tying the extrema dot to its edge label. */
|
|
381
|
+
const CONNECTOR_DEFAULTS: ResolvedLineStyleConfig = {
|
|
382
|
+
color: undefined,
|
|
383
|
+
strokeWidth: 1,
|
|
384
|
+
intervals: [2, 3],
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Resolves the extrema-label `connector` sub-prop to a line style or null.
|
|
389
|
+
* `defaultOn` (true in `"extrema-edge"` mode) means an unset connector draws the
|
|
390
|
+
* dashed default; `false` → null; an object → merged; a `LineStyleConfig` is
|
|
391
|
+
* normalized (its `intervals` may be omitted for a solid line).
|
|
392
|
+
*/
|
|
393
|
+
export function resolveConnector(
|
|
394
|
+
prop: boolean | LineStyleConfig | undefined,
|
|
395
|
+
defaultOn: boolean,
|
|
396
|
+
): ResolvedLineStyleConfig | null {
|
|
397
|
+
if (prop === false) return null;
|
|
398
|
+
if (prop == null) return defaultOn ? CONNECTOR_DEFAULTS : null;
|
|
399
|
+
if (prop === true) return CONNECTOR_DEFAULTS;
|
|
400
|
+
return {
|
|
401
|
+
color: prop.color,
|
|
402
|
+
strokeWidth: prop.strokeWidth ?? CONNECTOR_DEFAULTS.strokeWidth,
|
|
403
|
+
// An explicit object opts into its own dash (or solid when omitted).
|
|
404
|
+
intervals: prop.intervals,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
339
408
|
/**
|
|
340
409
|
* Resolves a `topLabel` / `bottomLabel` prop to a fully-typed config or null
|
|
341
410
|
* (no label). Opt-in, so `undefined`/`false` → null; `true` → the built-in
|
|
342
411
|
* value label with defaults; object → configured built-in (or a custom `render`).
|
|
412
|
+
* The `connector` defaults on (dashed) in `"extrema-edge"` mode, else off.
|
|
343
413
|
*/
|
|
344
414
|
export function resolveAxisLabel(
|
|
345
415
|
prop: boolean | AxisLabelConfig | undefined,
|
|
346
416
|
): ResolvedAxisLabelConfig | null {
|
|
347
|
-
|
|
417
|
+
const resolved = resolveToggle(prop, AXIS_LABEL_DEFAULTS, false);
|
|
418
|
+
if (!resolved) return null;
|
|
419
|
+
const connectorProp =
|
|
420
|
+
typeof prop === "object" ? prop.connector : undefined;
|
|
421
|
+
return {
|
|
422
|
+
...resolved,
|
|
423
|
+
connector: resolveConnector(
|
|
424
|
+
connectorProp,
|
|
425
|
+
resolved.position === "extrema-edge",
|
|
426
|
+
),
|
|
427
|
+
};
|
|
348
428
|
}
|
|
349
429
|
|
|
350
430
|
const X_AXIS_DEFAULTS: ResolvedXAxisConfig = {
|
|
@@ -433,6 +513,24 @@ export function resolveGradient(
|
|
|
433
513
|
return resolveToggle(prop, GRADIENT_DEFAULTS, false);
|
|
434
514
|
}
|
|
435
515
|
|
|
516
|
+
const AREA_DOTS_DEFAULTS: ResolvedAreaDotsConfig = {
|
|
517
|
+
spacing: 12,
|
|
518
|
+
size: 1.6,
|
|
519
|
+
color: undefined,
|
|
520
|
+
opacity: 1,
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Resolves `areaDots` prop to a fully-typed config or null (disabled).
|
|
525
|
+
* `true` → defaults (palette-derived color), object → merged with defaults,
|
|
526
|
+
* falsy/omitted → null. Default OFF (unlike `gradient`).
|
|
527
|
+
*/
|
|
528
|
+
export function resolveAreaDots(
|
|
529
|
+
prop: boolean | AreaDotsConfig | undefined,
|
|
530
|
+
): ResolvedAreaDotsConfig | null {
|
|
531
|
+
return resolveToggle(prop, AREA_DOTS_DEFAULTS, false);
|
|
532
|
+
}
|
|
533
|
+
|
|
436
534
|
/** Fallback when no theme background is passed (e.g. unit tests). */
|
|
437
535
|
const LEFT_EDGE_FADE_COLOR_FALLBACK: Pick<
|
|
438
536
|
ResolvedLeftEdgeFadeConfig,
|
|
@@ -63,13 +63,26 @@ export interface ChartEngineLayout {
|
|
|
63
63
|
timestamp: SharedValue<number>;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Live extrema (value + time of the lowest / highest data point in the visible
|
|
68
|
+
* window) — the raw data high/low, not the smoothed display bounds. Each field
|
|
69
|
+
* is `NaN` when the window holds no data. Provided by both engines so an
|
|
70
|
+
* `"extrema"`-positioned `topLabel` / `bottomLabel` can be pinned at the point.
|
|
71
|
+
*/
|
|
72
|
+
export interface ChartEngineExtrema {
|
|
73
|
+
extremaMinValue: SharedValue<number>;
|
|
74
|
+
extremaMaxValue: SharedValue<number>;
|
|
75
|
+
extremaMinTime: SharedValue<number>;
|
|
76
|
+
extremaMaxTime: SharedValue<number>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface SingleEngineState extends ChartEngineLayout, ChartEngineExtrema {
|
|
67
80
|
data: SharedValue<LiveChartPoint[]>;
|
|
68
81
|
value: SharedValue<number>;
|
|
69
82
|
displayValue: SharedValue<number>;
|
|
70
83
|
}
|
|
71
84
|
|
|
72
|
-
export interface MultiEngineState extends ChartEngineLayout {
|
|
85
|
+
export interface MultiEngineState extends ChartEngineLayout, ChartEngineExtrema {
|
|
73
86
|
data: SharedValue<LiveChartPoint[]>;
|
|
74
87
|
value: SharedValue<number>;
|
|
75
88
|
displayValue: SharedValue<number>;
|
|
@@ -109,6 +122,10 @@ export interface EngineFrameRefs {
|
|
|
109
122
|
modeSV: SharedValue<"line" | "candle">;
|
|
110
123
|
candles?: SharedValue<CandlePoint[]>;
|
|
111
124
|
liveCandle?: SharedValue<CandlePoint | null>;
|
|
125
|
+
extremaMinValue: SharedValue<number>;
|
|
126
|
+
extremaMaxValue: SharedValue<number>;
|
|
127
|
+
extremaMinTime: SharedValue<number>;
|
|
128
|
+
extremaMaxTime: SharedValue<number>;
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
/**
|
|
@@ -127,6 +144,10 @@ export function applyLiveChartEngineFrame(
|
|
|
127
144
|
displayMax: sv.displayMax.value,
|
|
128
145
|
displayWindow: sv.displayWindow.value,
|
|
129
146
|
timestamp: sv.timestamp.value,
|
|
147
|
+
extremaMinValue: sv.extremaMinValue.value,
|
|
148
|
+
extremaMaxValue: sv.extremaMaxValue.value,
|
|
149
|
+
extremaMinTime: sv.extremaMinTime.value,
|
|
150
|
+
extremaMaxTime: sv.extremaMaxTime.value,
|
|
130
151
|
};
|
|
131
152
|
tickLiveChartEngineFrame(state, {
|
|
132
153
|
dt,
|
|
@@ -155,6 +176,10 @@ export function applyLiveChartEngineFrame(
|
|
|
155
176
|
sv.displayMax.value = state.displayMax;
|
|
156
177
|
sv.displayWindow.value = state.displayWindow;
|
|
157
178
|
sv.timestamp.value = state.timestamp;
|
|
179
|
+
sv.extremaMinValue.value = state.extremaMinValue;
|
|
180
|
+
sv.extremaMaxValue.value = state.extremaMaxValue;
|
|
181
|
+
sv.extremaMinTime.value = state.extremaMinTime;
|
|
182
|
+
sv.extremaMaxTime.value = state.extremaMaxTime;
|
|
158
183
|
}
|
|
159
184
|
|
|
160
185
|
export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
|
|
@@ -187,6 +212,13 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
|
|
|
187
212
|
const [initialTimestamp] = useState(() => Date.now() / 1000);
|
|
188
213
|
const timestamp = useSharedValue(initialTimestamp);
|
|
189
214
|
|
|
215
|
+
// Live data extrema (value + time of the visible high / low). NaN until the
|
|
216
|
+
// first tick finds data — the extrema label stays hidden until then.
|
|
217
|
+
const extremaMinValue = useSharedValue(NaN);
|
|
218
|
+
const extremaMaxValue = useSharedValue(NaN);
|
|
219
|
+
const extremaMinTime = useSharedValue(NaN);
|
|
220
|
+
const extremaMaxTime = useSharedValue(NaN);
|
|
221
|
+
|
|
190
222
|
// High-frequency data reads directly from the caller's shared values —
|
|
191
223
|
// no useDerivedValue bridging, no closure serialization per tick.
|
|
192
224
|
const { data, value, candles, liveCandle } = config;
|
|
@@ -221,6 +253,10 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
|
|
|
221
253
|
modeSV,
|
|
222
254
|
candles,
|
|
223
255
|
liveCandle,
|
|
256
|
+
extremaMinValue,
|
|
257
|
+
extremaMaxValue,
|
|
258
|
+
extremaMinTime,
|
|
259
|
+
extremaMaxTime,
|
|
224
260
|
};
|
|
225
261
|
|
|
226
262
|
// `autostart=false` registers the frame callback without running it — the live
|
|
@@ -271,5 +307,9 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
|
|
|
271
307
|
canvasWidth,
|
|
272
308
|
canvasHeight,
|
|
273
309
|
timestamp,
|
|
310
|
+
extremaMinValue,
|
|
311
|
+
extremaMaxValue,
|
|
312
|
+
extremaMinTime,
|
|
313
|
+
extremaMaxTime,
|
|
274
314
|
};
|
|
275
315
|
}
|
|
@@ -47,6 +47,10 @@ export interface MultiEngineFrameRefs {
|
|
|
47
47
|
nowOverrideSV?: SharedValue<number | undefined>;
|
|
48
48
|
windowBufferSV?: SharedValue<number>;
|
|
49
49
|
pausedSV: SharedValue<boolean>;
|
|
50
|
+
extremaMinValue: SharedValue<number>;
|
|
51
|
+
extremaMaxValue: SharedValue<number>;
|
|
52
|
+
extremaMinTime: SharedValue<number>;
|
|
53
|
+
extremaMaxTime: SharedValue<number>;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/**
|
|
@@ -103,6 +107,10 @@ export function applyLiveChartSeriesEngineFrame(
|
|
|
103
107
|
timestamp: sv.timestamp.value,
|
|
104
108
|
displayValues,
|
|
105
109
|
opacities,
|
|
110
|
+
extremaMinValue: sv.extremaMinValue.value,
|
|
111
|
+
extremaMaxValue: sv.extremaMaxValue.value,
|
|
112
|
+
extremaMinTime: sv.extremaMinTime.value,
|
|
113
|
+
extremaMaxTime: sv.extremaMaxTime.value,
|
|
106
114
|
};
|
|
107
115
|
tickLiveChartSeriesEngineFrame(state, {
|
|
108
116
|
dt,
|
|
@@ -128,6 +136,10 @@ export function applyLiveChartSeriesEngineFrame(
|
|
|
128
136
|
sv.timestamp.value = state.timestamp;
|
|
129
137
|
sv.displaySeriesValues.value = state.displayValues;
|
|
130
138
|
sv.seriesOpacities.value = state.opacities;
|
|
139
|
+
sv.extremaMinValue.value = state.extremaMinValue;
|
|
140
|
+
sv.extremaMaxValue.value = state.extremaMaxValue;
|
|
141
|
+
sv.extremaMinTime.value = state.extremaMinTime;
|
|
142
|
+
sv.extremaMaxTime.value = state.extremaMaxTime;
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
/**
|
|
@@ -161,6 +173,12 @@ export function useLiveChartSeriesEngine(
|
|
|
161
173
|
const displaySeriesValues = useSharedValue<number[]>([]);
|
|
162
174
|
const seriesOpacities = useSharedValue<number[]>([]);
|
|
163
175
|
|
|
176
|
+
// Live data extrema (value + time of the visible high / low across series).
|
|
177
|
+
const extremaMinValue = useSharedValue(NaN);
|
|
178
|
+
const extremaMaxValue = useSharedValue(NaN);
|
|
179
|
+
const extremaMinTime = useSharedValue(NaN);
|
|
180
|
+
const extremaMaxTime = useSharedValue(NaN);
|
|
181
|
+
|
|
164
182
|
const data = useSharedValue<LiveChartPoint[]>([]);
|
|
165
183
|
const value = useSharedValue(0);
|
|
166
184
|
const displayValue = useSharedValue(0);
|
|
@@ -199,6 +217,10 @@ export function useLiveChartSeriesEngine(
|
|
|
199
217
|
nowOverrideSV,
|
|
200
218
|
windowBufferSV,
|
|
201
219
|
pausedSV,
|
|
220
|
+
extremaMinValue,
|
|
221
|
+
extremaMaxValue,
|
|
222
|
+
extremaMinTime,
|
|
223
|
+
extremaMaxTime,
|
|
202
224
|
},
|
|
203
225
|
scratch,
|
|
204
226
|
);
|
|
@@ -218,5 +240,9 @@ export function useLiveChartSeriesEngine(
|
|
|
218
240
|
series,
|
|
219
241
|
displaySeriesValues,
|
|
220
242
|
seriesOpacities,
|
|
243
|
+
extremaMinValue,
|
|
244
|
+
extremaMaxValue,
|
|
245
|
+
extremaMinTime,
|
|
246
|
+
extremaMaxTime,
|
|
221
247
|
};
|
|
222
248
|
}
|
|
@@ -4,6 +4,7 @@ import type { SharedValue } from "react-native-reanimated";
|
|
|
4
4
|
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
5
5
|
import { type ChartPadding } from "../draw/line";
|
|
6
6
|
import { interpolateAtTime } from "../math/interpolate";
|
|
7
|
+
import type { CandlePoint } from "../types";
|
|
7
8
|
|
|
8
9
|
const TOOLTIP_PAD_X = 8;
|
|
9
10
|
const TOOLTIP_PAD_Y = 6;
|
|
@@ -57,6 +58,17 @@ export interface CrosshairState {
|
|
|
57
58
|
/** Scrub intersection Y in canvas px; -1 when there's no dot to draw
|
|
58
59
|
* (inactive / no value / degenerate range). See {@link computeScrubDotY}. */
|
|
59
60
|
scrubDotY: SharedValue<number>;
|
|
61
|
+
/** OHLC candle under the crosshair in candle mode (`null` in line mode or when
|
|
62
|
+
* inactive) — single-series `useCrosshair` only; undefined on the multi-series
|
|
63
|
+
* crosshair. Surfaced for a custom candle `renderTooltip`. */
|
|
64
|
+
scrubCandle?: SharedValue<CandlePoint | null>;
|
|
65
|
+
/** Canvas-Y where the crosshair line should start so it stops at a top-pinned
|
|
66
|
+
* custom tooltip's bottom edge instead of running up through it. Written by
|
|
67
|
+
* {@link CustomTooltipOverlay} (the label's measured bottom) when
|
|
68
|
+
* `tooltipPlacement: "top"` is active, read by {@link CrosshairOverlay}; -1
|
|
69
|
+
* means "no top tooltip" → the line starts at `padding.top` as before.
|
|
70
|
+
* Single-series `useCrosshair` only; undefined on the multi-series crosshair. */
|
|
71
|
+
tooltipLineTop?: SharedValue<number>;
|
|
60
72
|
gesture: ReturnType<typeof Gesture.Pan>;
|
|
61
73
|
|
|
62
74
|
// ── Scrub-action ("order ticket") lock state — single-series `useCrosshair`
|
|
@@ -383,7 +395,7 @@ export function computeCandleTooltipLayout(
|
|
|
383
395
|
{ text: `C ${formatValue(candle.close)}`, dim: false },
|
|
384
396
|
{ text: formatTime(scrubTime), dim: true },
|
|
385
397
|
];
|
|
386
|
-
|
|
398
|
+
const layout = computeTooltipLayoutMulti(
|
|
387
399
|
scrubActive,
|
|
388
400
|
scrubX,
|
|
389
401
|
lines,
|
|
@@ -392,6 +404,13 @@ export function computeCandleTooltipLayout(
|
|
|
392
404
|
font,
|
|
393
405
|
monoCharWidth,
|
|
394
406
|
);
|
|
407
|
+
// Surface the close + time as the single-value strings too. The built-in
|
|
408
|
+
// OHLC stack renders `stackedLines`, but a custom `renderTooltip` reads
|
|
409
|
+
// `valueStr` / `timeStr` off the layout (same as line mode) — so a custom
|
|
410
|
+
// candle tooltip gets a ready-made close + time without its own formatter.
|
|
411
|
+
layout.valueStr = formatValue(candle.close);
|
|
412
|
+
layout.timeStr = formatTime(scrubTime);
|
|
413
|
+
return layout;
|
|
395
414
|
}
|
|
396
415
|
|
|
397
416
|
/** Single-series scrub value at window time — extracted for tests. */
|
|
@@ -143,6 +143,10 @@ export function useCrosshair(
|
|
|
143
143
|
// Tracks whether the active scrub phase actually began, so a tap that never
|
|
144
144
|
// activates doesn't emit a spurious onGestureEnd.
|
|
145
145
|
const gestureStarted = useSharedValue(false);
|
|
146
|
+
// Where the crosshair line should start (canvas Y) so it stops at a top-pinned
|
|
147
|
+
// custom tooltip instead of running through it. -1 = no top tooltip → the line
|
|
148
|
+
// starts at padding.top. Written by CustomTooltipOverlay, read by CrosshairOverlay.
|
|
149
|
+
const tooltipLineTop = useSharedValue(-1);
|
|
146
150
|
|
|
147
151
|
// Scrub-action lock state. Created unconditionally (hooks must be), but the
|
|
148
152
|
// lock gestures are only wired by the controller when `scrubAction` is set.
|
|
@@ -634,9 +638,11 @@ export function useCrosshair(
|
|
|
634
638
|
scrubActive,
|
|
635
639
|
scrubTime,
|
|
636
640
|
scrubValue,
|
|
641
|
+
scrubCandle,
|
|
637
642
|
crosshairOpacity,
|
|
638
643
|
tooltipLayout,
|
|
639
644
|
scrubDotY,
|
|
645
|
+
tooltipLineTop,
|
|
640
646
|
gesture,
|
|
641
647
|
lockActive,
|
|
642
648
|
lockX,
|
package/src/index.ts
CHANGED
package/src/theme.ts
CHANGED
|
@@ -98,6 +98,20 @@ function rgba(r: number, g: number, b: number, a: number): string {
|
|
|
98
98
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Parse a color into `[r, g, b, a]` — RGB channels 0–255, alpha 0–1. Extends
|
|
103
|
+
* {@link parseColorRgb} with the alpha channel of an `rgba(...)` string;
|
|
104
|
+
* `rgb(...)` and `#hex` (which carry no alpha) default to `a = 1`. Used to feed
|
|
105
|
+
* the area-dots shader a vec4 color uniform.
|
|
106
|
+
*/
|
|
107
|
+
export function parseColorRgba(
|
|
108
|
+
color: string,
|
|
109
|
+
): [number, number, number, number] {
|
|
110
|
+
const [r, g, b] = parseColorRgb(color);
|
|
111
|
+
const rgba = color.match(/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/);
|
|
112
|
+
return [r, g, b, rgba ? +rgba[1] : 1];
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
/**
|
|
102
116
|
* Default left-edge fade gradient stops for `dstOut` blending: same RGB as the chart
|
|
103
117
|
* background (`palette.bgRgb`), opacity 1 → 0. Matches the container `backgroundColor`.
|
package/src/types.ts
CHANGED
|
@@ -228,6 +228,20 @@ export interface ValueLineConfig {
|
|
|
228
228
|
color?: string;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Shared styling for a simple straight line — color, thickness, and an optional
|
|
233
|
+
* dash. The common shape behind the chart's secondary lines (the `valueLine`,
|
|
234
|
+
* the extrema-label `connector`, etc.), so they configure the same way.
|
|
235
|
+
*/
|
|
236
|
+
export interface LineStyleConfig {
|
|
237
|
+
/** Line color. Defaults per usage (e.g. the connector uses the label color). */
|
|
238
|
+
color?: string;
|
|
239
|
+
/** Line thickness in pixels. Default `1`. */
|
|
240
|
+
strokeWidth?: number;
|
|
241
|
+
/** Dash pattern as `[dashLength, gapLength]` in pixels. Omit for a solid line. */
|
|
242
|
+
intervals?: [number, number];
|
|
243
|
+
}
|
|
244
|
+
|
|
231
245
|
/** Main chart line styling. */
|
|
232
246
|
export interface LineConfig {
|
|
233
247
|
/** Stroke width of the main line in pixels. Default `2`. */
|
|
@@ -326,6 +340,22 @@ export interface GradientConfig {
|
|
|
326
340
|
positions?: number[];
|
|
327
341
|
}
|
|
328
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Dot-lattice fill of the area beneath the line — a screen-fixed grid of dots
|
|
345
|
+
* clipped to the region between the line and the baseline. Composes with
|
|
346
|
+
* `gradient` (both paint), or use it alone with `gradient={false}`.
|
|
347
|
+
*/
|
|
348
|
+
export interface AreaDotsConfig {
|
|
349
|
+
/** Lattice pitch (px) between dots, both axes. Default `12`. */
|
|
350
|
+
spacing?: number;
|
|
351
|
+
/** Dot diameter (px). Default `1.6`. */
|
|
352
|
+
size?: number;
|
|
353
|
+
/** Dot color. Omit to derive a faint tint from the line/accent color. */
|
|
354
|
+
color?: string;
|
|
355
|
+
/** Overall opacity (0..1) applied to the whole field. Default `1`. */
|
|
356
|
+
opacity?: number;
|
|
357
|
+
}
|
|
358
|
+
|
|
329
359
|
/** Value badge pill configuration. */
|
|
330
360
|
export interface BadgeConfig {
|
|
331
361
|
/** Visual style of the badge pill. Default `"default"`. */
|
|
@@ -356,9 +386,52 @@ export interface AxisLabelConfig {
|
|
|
356
386
|
format?: (v: number) => string;
|
|
357
387
|
/** Text color. Defaults to a muted label color (`palette.gridLabel`). */
|
|
358
388
|
color?: string;
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
|
|
389
|
+
/**
|
|
390
|
+
* Where the label sits.
|
|
391
|
+
* - `"left"` / `"right"` (default `"right"`) — pin to that edge of the plot,
|
|
392
|
+
* horizontally aligned.
|
|
393
|
+
* - `"extrema"` — float at the **actual data point** where the value occurs
|
|
394
|
+
* (`topLabel` tracks the highest point, `bottomLabel` the lowest), anchored
|
|
395
|
+
* over the point with a marker dot, so you can see *when* the high / low
|
|
396
|
+
* happened. The dot and label track the point on the UI thread.
|
|
397
|
+
* - `"extrema-edge"` — like `"extrema"`, but the value label is pinned to the
|
|
398
|
+
* **top / bottom edge**, horizontally aligned with the extremum (not floated
|
|
399
|
+
* over the point). The marker dot still sits on the data point, joined to the
|
|
400
|
+
* edge label by a {@link AxisLabelConfig.connector} line. Keeps the readout on
|
|
401
|
+
* a clean rail while still showing where the extremum is.
|
|
402
|
+
*/
|
|
403
|
+
position?: "left" | "right" | "extrema" | "extrema-edge";
|
|
404
|
+
/** Built-in value text size in px. Default `11`. */
|
|
405
|
+
fontSize?: number;
|
|
406
|
+
/** Built-in value text weight. Default the platform `<Text>` default. */
|
|
407
|
+
fontWeight?: FontWeight;
|
|
408
|
+
/** Built-in value text font family (e.g. a loaded monospace face). */
|
|
409
|
+
fontFamily?: string;
|
|
410
|
+
/**
|
|
411
|
+
* Extrema modes only — color of the marker dot at the data point. Defaults to
|
|
412
|
+
* `color`. Lets the dot and the value text differ.
|
|
413
|
+
*/
|
|
414
|
+
dotColor?: string;
|
|
415
|
+
/** Extrema modes only — marker dot diameter in px. Default `7`. */
|
|
416
|
+
dotSize?: number;
|
|
417
|
+
/**
|
|
418
|
+
* Extrema modes only — draw the marker dot at the data point. Default `true`.
|
|
419
|
+
* Set `false` for a value label with no dot.
|
|
420
|
+
*/
|
|
421
|
+
dot?: boolean;
|
|
422
|
+
/**
|
|
423
|
+
* `"extrema-edge"` only — the line joining the marker dot (on the data point)
|
|
424
|
+
* to the edge value label. `true` = a dashed default, `false` = none, or pass a
|
|
425
|
+
* {@link LineStyleConfig} to style it (`color` defaults to the label `color`).
|
|
426
|
+
* Default on (dashed) in `"extrema-edge"` mode.
|
|
427
|
+
*/
|
|
428
|
+
connector?: boolean | LineStyleConfig;
|
|
429
|
+
/**
|
|
430
|
+
* Full custom element, floated at the edge (or, in an extrema mode, centered
|
|
431
|
+
* over the extremum point). Overrides the built-in value label (and the
|
|
432
|
+
* `fontSize` / `fontWeight` / `fontFamily` / `dot*` knobs above — you own the
|
|
433
|
+
* styling).
|
|
434
|
+
*/
|
|
362
435
|
render?: () => ReactElement | null;
|
|
363
436
|
}
|
|
364
437
|
|
|
@@ -527,16 +600,30 @@ export interface SelectionDotProps {
|
|
|
527
600
|
* for the value/date to update on the UI thread too.
|
|
528
601
|
*/
|
|
529
602
|
export interface TooltipRenderProps {
|
|
530
|
-
/**
|
|
603
|
+
/**
|
|
604
|
+
* Value under the crosshair; `null` when none. In line mode this is the
|
|
605
|
+
* interpolated value at the scrub time; in candle mode it's the scrubbed
|
|
606
|
+
* candle's close (use {@link TooltipRenderProps.candle} for full OHLC).
|
|
607
|
+
*/
|
|
531
608
|
value: SharedValue<number | null>;
|
|
532
609
|
/** Window time (unix seconds) under the crosshair. */
|
|
533
610
|
time: SharedValue<number>;
|
|
534
|
-
/**
|
|
611
|
+
/**
|
|
612
|
+
* Value formatted with the chart's `formatValue` (computed UI-side). In candle
|
|
613
|
+
* mode this is the formatted close.
|
|
614
|
+
*/
|
|
535
615
|
valueStr: SharedValue<string>;
|
|
536
616
|
/** Time formatted with the chart's `formatTime` (computed UI-side). */
|
|
537
617
|
timeStr: SharedValue<string>;
|
|
538
618
|
/** Whether scrubbing is currently active. */
|
|
539
619
|
active: SharedValue<boolean>;
|
|
620
|
+
/**
|
|
621
|
+
* In candle mode, the OHLC candle under the crosshair (`null` when none or
|
|
622
|
+
* while inactive). Always `null` in line mode — bind it to render OHLC in a
|
|
623
|
+
* custom candlestick tooltip. Format the individual prices with your own
|
|
624
|
+
* worklet-safe formatter (e.g. the chart's `formatValue`).
|
|
625
|
+
*/
|
|
626
|
+
candle: SharedValue<CandlePoint | null>;
|
|
540
627
|
}
|
|
541
628
|
|
|
542
629
|
/** Outer ring drawn around the built-in selection dot (the subtle halo). */
|
|
@@ -1100,9 +1187,13 @@ export interface LiveChartCoreProps {
|
|
|
1100
1187
|
* background are plain RN styles) and content — bind the {@link
|
|
1101
1188
|
* TooltipRenderProps} SharedValues to animated text for the value/date.
|
|
1102
1189
|
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1190
|
+
* Supplying `renderTooltip` replaces the built-in tooltip entirely while
|
|
1191
|
+
* scrubbing (the line pill in line mode, the OHLC stack in candle mode).
|
|
1192
|
+
* Returning `null`/`undefined` from a frame renders nothing for that frame
|
|
1193
|
+
* (e.g. to hide the tooltip in certain states) — it does *not* restore the
|
|
1194
|
+
* built-in pill. Works in **both line and candle mode**: in candle mode the
|
|
1195
|
+
* scrubbed candle is available as {@link TooltipRenderProps.candle} for
|
|
1196
|
+
* rendering your own OHLC readout.
|
|
1106
1197
|
*/
|
|
1107
1198
|
renderTooltip?: (ctx: TooltipRenderProps) => ReactElement | null | undefined;
|
|
1108
1199
|
/**
|
|
@@ -1157,6 +1248,12 @@ export interface LiveChartCoreProps {
|
|
|
1157
1248
|
export interface LiveChartProps extends LiveChartCoreProps {
|
|
1158
1249
|
/** Area gradient fill under the line. `true` = defaults, or pass `GradientConfig`. Default `true`. */
|
|
1159
1250
|
gradient?: boolean | GradientConfig;
|
|
1251
|
+
/**
|
|
1252
|
+
* Dot-lattice fill of the area beneath the line (clipped to the under-line
|
|
1253
|
+
* region). `true` = defaults, or pass `AreaDotsConfig`. Composes with
|
|
1254
|
+
* `gradient`. Default `false` (off). Inert in candle mode.
|
|
1255
|
+
*/
|
|
1256
|
+
areaDots?: boolean | AreaDotsConfig;
|
|
1160
1257
|
/** Value badge pill at the chart tip. `true` = defaults, or pass `BadgeConfig`. Default `true`. */
|
|
1161
1258
|
badge?: boolean | BadgeConfig;
|
|
1162
1259
|
/**
|