react-native-livechart 3.8.2 → 3.10.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/AxisLabelOverlay.d.ts +19 -3
- package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
- package/dist/components/CrosshairLine.d.ts +3 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +7 -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 +34 -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/types.d.ts +88 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AxisLabelOverlay.tsx +399 -46
- package/src/components/CrosshairLine.tsx +7 -2
- package/src/components/CrosshairOverlay.tsx +22 -6
- package/src/components/CustomTooltipOverlay.tsx +30 -5
- package/src/components/ExtremaConnectorOverlay.tsx +193 -0
- package/src/components/LiveChart.tsx +38 -8
- package/src/components/LiveChartSeries.tsx +19 -0
- package/src/core/liveChartEngineTick.ts +51 -6
- package/src/core/liveChartSeriesEngineTick.ts +34 -0
- package/src/core/resolveConfig.ts +84 -3
- 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/types.ts +89 -8
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
LeftEdgeFadeConfig,
|
|
11
11
|
LegendConfig,
|
|
12
12
|
LegendStyle,
|
|
13
|
+
LineStyleConfig,
|
|
13
14
|
LiveChartMetrics,
|
|
14
15
|
LiveChartMetricsOverride,
|
|
15
16
|
DotConfig,
|
|
@@ -61,12 +62,35 @@ export interface ResolvedYAxisConfig {
|
|
|
61
62
|
minGap: number;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/** Resolved straight-line styling (connector, etc.). `color: undefined` → caller default. */
|
|
66
|
+
export interface ResolvedLineStyleConfig {
|
|
67
|
+
color: string | undefined;
|
|
68
|
+
strokeWidth: number;
|
|
69
|
+
/** undefined → solid (no dash). */
|
|
70
|
+
intervals: [number, number] | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
export interface ResolvedAxisLabelConfig {
|
|
65
74
|
/** undefined → use the chart's `formatValue` at render time. */
|
|
66
75
|
format?: (v: number) => string;
|
|
67
76
|
/** undefined → use the muted default label color at render time. */
|
|
68
77
|
color?: string;
|
|
69
|
-
|
|
78
|
+
/** `"left"`/`"right"` pin to that edge; `"extrema"`(-`edge`) tracks the data point. */
|
|
79
|
+
position: "left" | "right" | "extrema" | "extrema-edge";
|
|
80
|
+
/** undefined → the built-in default text size (11). */
|
|
81
|
+
fontSize?: number;
|
|
82
|
+
/** undefined → the platform `<Text>` default weight. */
|
|
83
|
+
fontWeight?: FontWeight;
|
|
84
|
+
/** undefined → the platform `<Text>` default family. */
|
|
85
|
+
fontFamily?: string;
|
|
86
|
+
/** Extrema dot color; undefined → use `color`. */
|
|
87
|
+
dotColor?: string;
|
|
88
|
+
/** Extrema dot diameter (px); undefined → the built-in default (7). */
|
|
89
|
+
dotSize?: number;
|
|
90
|
+
/** Extrema — whether to draw the marker dot. */
|
|
91
|
+
dot: boolean;
|
|
92
|
+
/** `"extrema-edge"` connector line (dot → edge label); null → none. */
|
|
93
|
+
connector: ResolvedLineStyleConfig | null;
|
|
70
94
|
/** When set, the built-in value label is replaced by this custom element. */
|
|
71
95
|
render?: () => ReactElement | null;
|
|
72
96
|
}
|
|
@@ -81,6 +105,8 @@ export interface ResolvedScrubConfig {
|
|
|
81
105
|
dimOpacity: number;
|
|
82
106
|
/** undefined → palette.crosshairLine */
|
|
83
107
|
crosshairLineColor: string | undefined;
|
|
108
|
+
/** Dash intervals `[on, off, …]` for the crosshair line; undefined → solid. */
|
|
109
|
+
crosshairDash: number[] | undefined;
|
|
84
110
|
/** undefined → palette.crosshairDim */
|
|
85
111
|
crosshairDimColor: string | undefined;
|
|
86
112
|
/** undefined → palette.tooltipBg */
|
|
@@ -331,18 +357,65 @@ const AXIS_LABEL_DEFAULTS: ResolvedAxisLabelConfig = {
|
|
|
331
357
|
format: undefined,
|
|
332
358
|
color: undefined,
|
|
333
359
|
position: "right",
|
|
360
|
+
fontSize: undefined,
|
|
361
|
+
fontWeight: undefined,
|
|
362
|
+
fontFamily: undefined,
|
|
363
|
+
dotColor: undefined,
|
|
364
|
+
dotSize: undefined,
|
|
365
|
+
dot: true,
|
|
366
|
+
// Always overwritten by resolveAxisLabel (per-position default-on); placeholder.
|
|
367
|
+
connector: null,
|
|
334
368
|
render: undefined,
|
|
335
369
|
};
|
|
336
370
|
|
|
371
|
+
/** Dashed by default — a subtle guide tying the extrema dot to its edge label. */
|
|
372
|
+
const CONNECTOR_DEFAULTS: ResolvedLineStyleConfig = {
|
|
373
|
+
color: undefined,
|
|
374
|
+
strokeWidth: 1,
|
|
375
|
+
intervals: [2, 3],
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Resolves the extrema-label `connector` sub-prop to a line style or null.
|
|
380
|
+
* `defaultOn` (true in `"extrema-edge"` mode) means an unset connector draws the
|
|
381
|
+
* dashed default; `false` → null; an object → merged; a `LineStyleConfig` is
|
|
382
|
+
* normalized (its `intervals` may be omitted for a solid line).
|
|
383
|
+
*/
|
|
384
|
+
export function resolveConnector(
|
|
385
|
+
prop: boolean | LineStyleConfig | undefined,
|
|
386
|
+
defaultOn: boolean,
|
|
387
|
+
): ResolvedLineStyleConfig | null {
|
|
388
|
+
if (prop === false) return null;
|
|
389
|
+
if (prop == null) return defaultOn ? CONNECTOR_DEFAULTS : null;
|
|
390
|
+
if (prop === true) return CONNECTOR_DEFAULTS;
|
|
391
|
+
return {
|
|
392
|
+
color: prop.color,
|
|
393
|
+
strokeWidth: prop.strokeWidth ?? CONNECTOR_DEFAULTS.strokeWidth,
|
|
394
|
+
// An explicit object opts into its own dash (or solid when omitted).
|
|
395
|
+
intervals: prop.intervals,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
337
399
|
/**
|
|
338
400
|
* Resolves a `topLabel` / `bottomLabel` prop to a fully-typed config or null
|
|
339
401
|
* (no label). Opt-in, so `undefined`/`false` → null; `true` → the built-in
|
|
340
402
|
* value label with defaults; object → configured built-in (or a custom `render`).
|
|
403
|
+
* The `connector` defaults on (dashed) in `"extrema-edge"` mode, else off.
|
|
341
404
|
*/
|
|
342
405
|
export function resolveAxisLabel(
|
|
343
406
|
prop: boolean | AxisLabelConfig | undefined,
|
|
344
407
|
): ResolvedAxisLabelConfig | null {
|
|
345
|
-
|
|
408
|
+
const resolved = resolveToggle(prop, AXIS_LABEL_DEFAULTS, false);
|
|
409
|
+
if (!resolved) return null;
|
|
410
|
+
const connectorProp =
|
|
411
|
+
typeof prop === "object" ? prop.connector : undefined;
|
|
412
|
+
return {
|
|
413
|
+
...resolved,
|
|
414
|
+
connector: resolveConnector(
|
|
415
|
+
connectorProp,
|
|
416
|
+
resolved.position === "extrema-edge",
|
|
417
|
+
),
|
|
418
|
+
};
|
|
346
419
|
}
|
|
347
420
|
|
|
348
421
|
const X_AXIS_DEFAULTS: ResolvedXAxisConfig = {
|
|
@@ -363,6 +436,7 @@ const SCRUB_DEFAULTS: ResolvedScrubConfig = {
|
|
|
363
436
|
tooltip: true,
|
|
364
437
|
dimOpacity: 0.3,
|
|
365
438
|
crosshairLineColor: undefined,
|
|
439
|
+
crosshairDash: undefined,
|
|
366
440
|
crosshairDimColor: undefined,
|
|
367
441
|
tooltipBackground: undefined,
|
|
368
442
|
tooltipColor: undefined,
|
|
@@ -382,7 +456,14 @@ const SCRUB_DEFAULTS: ResolvedScrubConfig = {
|
|
|
382
456
|
export function resolveScrub(
|
|
383
457
|
prop: boolean | ScrubConfig | undefined,
|
|
384
458
|
): ResolvedScrubConfig | null {
|
|
385
|
-
|
|
459
|
+
const resolved = resolveToggle(prop, SCRUB_DEFAULTS, false);
|
|
460
|
+
if (resolved) {
|
|
461
|
+
// Normalize the dash shorthand: `true` → a default dash, an array passes
|
|
462
|
+
// through, anything falsy → solid (undefined).
|
|
463
|
+
const dash = typeof prop === "object" ? prop.crosshairDash : undefined;
|
|
464
|
+
resolved.crosshairDash = dash === true ? [4, 4] : dash || undefined;
|
|
465
|
+
}
|
|
466
|
+
return resolved;
|
|
386
467
|
}
|
|
387
468
|
|
|
388
469
|
const SCRUB_ACTION_DEFAULTS: ResolvedScrubActionConfig = {
|
|
@@ -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/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`. */
|
|
@@ -356,9 +370,52 @@ export interface AxisLabelConfig {
|
|
|
356
370
|
format?: (v: number) => string;
|
|
357
371
|
/** Text color. Defaults to a muted label color (`palette.gridLabel`). */
|
|
358
372
|
color?: string;
|
|
359
|
-
/**
|
|
360
|
-
|
|
361
|
-
|
|
373
|
+
/**
|
|
374
|
+
* Where the label sits.
|
|
375
|
+
* - `"left"` / `"right"` (default `"right"`) — pin to that edge of the plot,
|
|
376
|
+
* horizontally aligned.
|
|
377
|
+
* - `"extrema"` — float at the **actual data point** where the value occurs
|
|
378
|
+
* (`topLabel` tracks the highest point, `bottomLabel` the lowest), anchored
|
|
379
|
+
* over the point with a marker dot, so you can see *when* the high / low
|
|
380
|
+
* happened. The dot and label track the point on the UI thread.
|
|
381
|
+
* - `"extrema-edge"` — like `"extrema"`, but the value label is pinned to the
|
|
382
|
+
* **top / bottom edge**, horizontally aligned with the extremum (not floated
|
|
383
|
+
* over the point). The marker dot still sits on the data point, joined to the
|
|
384
|
+
* edge label by a {@link AxisLabelConfig.connector} line. Keeps the readout on
|
|
385
|
+
* a clean rail while still showing where the extremum is.
|
|
386
|
+
*/
|
|
387
|
+
position?: "left" | "right" | "extrema" | "extrema-edge";
|
|
388
|
+
/** Built-in value text size in px. Default `11`. */
|
|
389
|
+
fontSize?: number;
|
|
390
|
+
/** Built-in value text weight. Default the platform `<Text>` default. */
|
|
391
|
+
fontWeight?: FontWeight;
|
|
392
|
+
/** Built-in value text font family (e.g. a loaded monospace face). */
|
|
393
|
+
fontFamily?: string;
|
|
394
|
+
/**
|
|
395
|
+
* Extrema modes only — color of the marker dot at the data point. Defaults to
|
|
396
|
+
* `color`. Lets the dot and the value text differ.
|
|
397
|
+
*/
|
|
398
|
+
dotColor?: string;
|
|
399
|
+
/** Extrema modes only — marker dot diameter in px. Default `7`. */
|
|
400
|
+
dotSize?: number;
|
|
401
|
+
/**
|
|
402
|
+
* Extrema modes only — draw the marker dot at the data point. Default `true`.
|
|
403
|
+
* Set `false` for a value label with no dot.
|
|
404
|
+
*/
|
|
405
|
+
dot?: boolean;
|
|
406
|
+
/**
|
|
407
|
+
* `"extrema-edge"` only — the line joining the marker dot (on the data point)
|
|
408
|
+
* to the edge value label. `true` = a dashed default, `false` = none, or pass a
|
|
409
|
+
* {@link LineStyleConfig} to style it (`color` defaults to the label `color`).
|
|
410
|
+
* Default on (dashed) in `"extrema-edge"` mode.
|
|
411
|
+
*/
|
|
412
|
+
connector?: boolean | LineStyleConfig;
|
|
413
|
+
/**
|
|
414
|
+
* Full custom element, floated at the edge (or, in an extrema mode, centered
|
|
415
|
+
* over the extremum point). Overrides the built-in value label (and the
|
|
416
|
+
* `fontSize` / `fontWeight` / `fontFamily` / `dot*` knobs above — you own the
|
|
417
|
+
* styling).
|
|
418
|
+
*/
|
|
362
419
|
render?: () => ReactElement | null;
|
|
363
420
|
}
|
|
364
421
|
|
|
@@ -382,6 +439,12 @@ export interface ScrubConfig {
|
|
|
382
439
|
dimOpacity?: number;
|
|
383
440
|
/** Vertical crosshair line stroke. Omit to use theme `crosshairLine`. */
|
|
384
441
|
crosshairLineColor?: string;
|
|
442
|
+
/**
|
|
443
|
+
* Dash the vertical crosshair line. `true` → a default `[4, 4]` dash; an array
|
|
444
|
+
* sets explicit Skia dash intervals `[on, off, …]` in px. Omit / `false` → a
|
|
445
|
+
* solid line.
|
|
446
|
+
*/
|
|
447
|
+
crosshairDash?: number[] | boolean;
|
|
385
448
|
/**
|
|
386
449
|
* Legacy: fill the region right of the crosshair with this solid (usually
|
|
387
450
|
* semi-transparent) color — a mask painted *over* the chart, so it only looks
|
|
@@ -521,16 +584,30 @@ export interface SelectionDotProps {
|
|
|
521
584
|
* for the value/date to update on the UI thread too.
|
|
522
585
|
*/
|
|
523
586
|
export interface TooltipRenderProps {
|
|
524
|
-
/**
|
|
587
|
+
/**
|
|
588
|
+
* Value under the crosshair; `null` when none. In line mode this is the
|
|
589
|
+
* interpolated value at the scrub time; in candle mode it's the scrubbed
|
|
590
|
+
* candle's close (use {@link TooltipRenderProps.candle} for full OHLC).
|
|
591
|
+
*/
|
|
525
592
|
value: SharedValue<number | null>;
|
|
526
593
|
/** Window time (unix seconds) under the crosshair. */
|
|
527
594
|
time: SharedValue<number>;
|
|
528
|
-
/**
|
|
595
|
+
/**
|
|
596
|
+
* Value formatted with the chart's `formatValue` (computed UI-side). In candle
|
|
597
|
+
* mode this is the formatted close.
|
|
598
|
+
*/
|
|
529
599
|
valueStr: SharedValue<string>;
|
|
530
600
|
/** Time formatted with the chart's `formatTime` (computed UI-side). */
|
|
531
601
|
timeStr: SharedValue<string>;
|
|
532
602
|
/** Whether scrubbing is currently active. */
|
|
533
603
|
active: SharedValue<boolean>;
|
|
604
|
+
/**
|
|
605
|
+
* In candle mode, the OHLC candle under the crosshair (`null` when none or
|
|
606
|
+
* while inactive). Always `null` in line mode — bind it to render OHLC in a
|
|
607
|
+
* custom candlestick tooltip. Format the individual prices with your own
|
|
608
|
+
* worklet-safe formatter (e.g. the chart's `formatValue`).
|
|
609
|
+
*/
|
|
610
|
+
candle: SharedValue<CandlePoint | null>;
|
|
534
611
|
}
|
|
535
612
|
|
|
536
613
|
/** Outer ring drawn around the built-in selection dot (the subtle halo). */
|
|
@@ -1094,9 +1171,13 @@ export interface LiveChartCoreProps {
|
|
|
1094
1171
|
* background are plain RN styles) and content — bind the {@link
|
|
1095
1172
|
* TooltipRenderProps} SharedValues to animated text for the value/date.
|
|
1096
1173
|
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1174
|
+
* Supplying `renderTooltip` replaces the built-in tooltip entirely while
|
|
1175
|
+
* scrubbing (the line pill in line mode, the OHLC stack in candle mode).
|
|
1176
|
+
* Returning `null`/`undefined` from a frame renders nothing for that frame
|
|
1177
|
+
* (e.g. to hide the tooltip in certain states) — it does *not* restore the
|
|
1178
|
+
* built-in pill. Works in **both line and candle mode**: in candle mode the
|
|
1179
|
+
* scrubbed candle is available as {@link TooltipRenderProps.candle} for
|
|
1180
|
+
* rendering your own OHLC readout.
|
|
1100
1181
|
*/
|
|
1101
1182
|
renderTooltip?: (ctx: TooltipRenderProps) => ReactElement | null | undefined;
|
|
1102
1183
|
/**
|