react-native-livechart 4.11.0 → 4.12.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/CrosshairLine.d.ts +3 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CustomReferenceLineOverlay.d.ts +15 -10
- package/dist/components/CustomReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/DotOverlay.d.ts +3 -5
- package/dist/components/DotOverlay.d.ts.map +1 -1
- package/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/components/PerSeriesTooltipOverlay.d.ts +19 -0
- package/dist/components/PerSeriesTooltipOverlay.d.ts.map +1 -0
- package/dist/components/ReferenceLineOverlay.d.ts +20 -6
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/YAxisOverlay.d.ts +3 -1
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- package/dist/core/liveIndicatorVisibility.d.ts +9 -0
- package/dist/core/liveIndicatorVisibility.d.ts.map +1 -0
- package/dist/core/resolveConfig.d.ts +29 -0
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/hooks/crosshairSeries.d.ts +10 -2
- package/dist/hooks/crosshairSeries.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +33 -2
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/delayedPanGuard.d.ts +66 -8
- package/dist/hooks/delayedPanGuard.d.ts.map +1 -1
- package/dist/hooks/useChartPaths.d.ts +5 -9
- package/dist/hooks/useChartPaths.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts +7 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts +22 -3
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
- package/dist/hooks/useMomentum.d.ts +1 -1
- package/dist/hooks/useMomentum.d.ts.map +1 -1
- package/dist/hooks/usePanScroll.d.ts +15 -1
- package/dist/hooks/usePanScroll.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/lerp.d.ts.map +1 -1
- package/dist/math/momentum.d.ts +1 -1
- package/dist/math/momentum.d.ts.map +1 -1
- package/dist/math/referenceLines.d.ts +7 -0
- package/dist/math/referenceLines.d.ts.map +1 -1
- package/dist/types.d.ts +160 -18
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CrosshairLine.tsx +4 -1
- package/src/components/CustomReferenceLineOverlay.tsx +42 -13
- package/src/components/DotOverlay.tsx +24 -8
- package/src/components/LiveChart.tsx +157 -37
- package/src/components/LiveChartSeries.tsx +128 -16
- package/src/components/PerSeriesTooltipOverlay.tsx +287 -0
- package/src/components/ReferenceLineOverlay.tsx +274 -141
- package/src/components/YAxisOverlay.tsx +5 -1
- package/src/core/liveIndicatorVisibility.ts +25 -0
- package/src/core/resolveConfig.ts +86 -0
- package/src/hooks/crosshairSeries.ts +262 -33
- package/src/hooks/crosshairShared.ts +36 -2
- package/src/hooks/delayedPanGuard.ts +111 -6
- package/src/hooks/useChartPaths.ts +25 -15
- package/src/hooks/useCrosshair.ts +57 -4
- package/src/hooks/useCrosshairSeries.ts +95 -7
- package/src/hooks/useMomentum.ts +8 -1
- package/src/hooks/usePanScroll.ts +46 -5
- package/src/index.ts +1 -0
- package/src/math/lerp.ts +7 -0
- package/src/math/momentum.ts +22 -5
- package/src/math/referenceLines.ts +38 -0
- package/src/types.ts +168 -18
|
@@ -12,7 +12,10 @@ import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
|
12
12
|
import type { ChartPadding } from "../draw/line";
|
|
13
13
|
import { useChartSkiaFont } from "../hooks/useChartSkiaFont";
|
|
14
14
|
import { usePathBuilder } from "../hooks/usePathBuilder";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useReferenceLine,
|
|
17
|
+
type ReferenceLineLayout,
|
|
18
|
+
} from "../hooks/useReferenceLine";
|
|
16
19
|
import { MONO_FONT_FAMILY } from "../lib/monoFontFamily";
|
|
17
20
|
import { referenceLineForm, resolveReferenceBadge } from "../math/referenceLines";
|
|
18
21
|
import type { FontConfig, LiveChartPalette, ReferenceLine } from "../types";
|
|
@@ -23,6 +26,10 @@ const BAND_FILL_OPACITY = 0.16;
|
|
|
23
26
|
/** Vertical padding inside the badge pill, in px (kept in sync with the layout). */
|
|
24
27
|
const BADGE_PILL_PAD_Y = 3;
|
|
25
28
|
const BADGE_PILL_RADIUS = 5;
|
|
29
|
+
/** Gap between a badge's outer edge and its dashed connector, in px. */
|
|
30
|
+
const CONNECTOR_GAP = 4;
|
|
31
|
+
/** Badge inset from its plot edge, in px (matches useReferenceLine). */
|
|
32
|
+
const BADGE_EDGE_INSET = 2;
|
|
26
33
|
|
|
27
34
|
/**
|
|
28
35
|
* Renders one reference line or band into the chart canvas. Handles all three
|
|
@@ -30,20 +37,7 @@ const BADGE_PILL_RADIUS = 5;
|
|
|
30
37
|
* band) plus the Form-A pill badge (in-range tag + off-screen chevron pin).
|
|
31
38
|
* Self-contained so callers can `.map()` over a variable-length array.
|
|
32
39
|
*/
|
|
33
|
-
|
|
34
|
-
engine,
|
|
35
|
-
padding,
|
|
36
|
-
line,
|
|
37
|
-
palette,
|
|
38
|
-
formatValue,
|
|
39
|
-
font,
|
|
40
|
-
fontProp,
|
|
41
|
-
badgeLayer = false,
|
|
42
|
-
suppressTag = false,
|
|
43
|
-
groupHidden,
|
|
44
|
-
dragValues,
|
|
45
|
-
index = 0,
|
|
46
|
-
}: {
|
|
40
|
+
type ReferenceLineOverlayProps = {
|
|
47
41
|
engine: ChartEngineLayout;
|
|
48
42
|
padding: ChartPadding;
|
|
49
43
|
line: ReferenceLine;
|
|
@@ -64,11 +58,23 @@ export function ReferenceLineOverlay({
|
|
|
64
58
|
*/
|
|
65
59
|
badgeLayer?: boolean;
|
|
66
60
|
/**
|
|
67
|
-
* Suppress the built-in tag (badge pill +
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
|
|
61
|
+
* Suppress the built-in tag (badge pill + chevron + icon + gutter label) for
|
|
62
|
+
* this line — used when a custom `renderReferenceLine` element owns the tag.
|
|
63
|
+
* The line / band stroke and a badged line's dashed connector still draw. No
|
|
64
|
+
* effect on the base pass.
|
|
65
|
+
*/
|
|
71
66
|
suppressTag?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Suppress the built-in tag only while the line is off-axis. Used by
|
|
69
|
+
* `renderOffAxisReferenceLine`, so the normal in-range tag remains intact.
|
|
70
|
+
*/
|
|
71
|
+
suppressTagWhenOffAxis?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Measured widths of custom reference-line tags, index-aligned with their
|
|
74
|
+
* `referenceLines`. When present for a suppressed badged tag, the built-in
|
|
75
|
+
* connector begins after the custom element instead of the hidden Skia pill.
|
|
76
|
+
*/
|
|
77
|
+
customTagWidths?: SharedValue<number[]>;
|
|
72
78
|
/**
|
|
73
79
|
* Per-frame grouping flags (index-aligned): when this line's slot is `true` it's
|
|
74
80
|
* collapsed into a group count handle, so its tag is suppressed (the line / band
|
|
@@ -79,7 +85,24 @@ export function ReferenceLineOverlay({
|
|
|
79
85
|
dragValues?: SharedValue<number[]>;
|
|
80
86
|
/** This line's index into {@link dragValues}. */
|
|
81
87
|
index?: number;
|
|
82
|
-
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export function ReferenceLineOverlay({
|
|
91
|
+
engine,
|
|
92
|
+
padding,
|
|
93
|
+
line,
|
|
94
|
+
palette,
|
|
95
|
+
formatValue,
|
|
96
|
+
font,
|
|
97
|
+
fontProp,
|
|
98
|
+
badgeLayer = false,
|
|
99
|
+
suppressTag = false,
|
|
100
|
+
suppressTagWhenOffAxis = false,
|
|
101
|
+
customTagWidths,
|
|
102
|
+
groupHidden,
|
|
103
|
+
dragValues,
|
|
104
|
+
index = 0,
|
|
105
|
+
}: ReferenceLineOverlayProps) {
|
|
83
106
|
const form = referenceLineForm(line);
|
|
84
107
|
const isBand = form === "value-band" || form === "time-band";
|
|
85
108
|
|
|
@@ -141,24 +164,78 @@ export function ReferenceLineOverlay({
|
|
|
141
164
|
? [{ translateX: badgeOffsetX }, { translateY: badgeOffsetY }]
|
|
142
165
|
: undefined;
|
|
143
166
|
|
|
167
|
+
if (!badgeLayer) {
|
|
168
|
+
return (
|
|
169
|
+
<ReferenceLineBasePass
|
|
170
|
+
layout={layout}
|
|
171
|
+
color={color}
|
|
172
|
+
strokeWidth={strokeWidth}
|
|
173
|
+
intervals={intervals}
|
|
174
|
+
isBand={isBand}
|
|
175
|
+
isTimeBand={form === "time-band"}
|
|
176
|
+
bandFillOpacity={bandFillOpacity}
|
|
177
|
+
hasBandBorder={hasBandBorder}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<ReferenceLineBadgePass
|
|
184
|
+
layout={layout}
|
|
185
|
+
color={color}
|
|
186
|
+
strokeWidth={strokeWidth}
|
|
187
|
+
intervals={intervals}
|
|
188
|
+
badgeFont={badgeFont}
|
|
189
|
+
badgePosition={badge?.position}
|
|
190
|
+
labelColor={labelColor}
|
|
191
|
+
badgeBackground={badgeBackground}
|
|
192
|
+
badgeBorderColor={badgeBorderColor}
|
|
193
|
+
badgeBorderWidth={badgeBorderWidth}
|
|
194
|
+
badgeRadius={badgeRadius}
|
|
195
|
+
badgeOffsetX={badgeOffsetX}
|
|
196
|
+
badgeOffsetY={badgeOffsetY}
|
|
197
|
+
suppressTag={suppressTag}
|
|
198
|
+
suppressTagWhenOffAxis={suppressTagWhenOffAxis}
|
|
199
|
+
customTagWidths={customTagWidths}
|
|
200
|
+
groupHidden={groupHidden}
|
|
201
|
+
index={index}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Base stroke / band pass, kept behind the chart content. */
|
|
207
|
+
function ReferenceLineBasePass({
|
|
208
|
+
layout,
|
|
209
|
+
color,
|
|
210
|
+
strokeWidth,
|
|
211
|
+
intervals,
|
|
212
|
+
isBand,
|
|
213
|
+
isTimeBand,
|
|
214
|
+
bandFillOpacity,
|
|
215
|
+
hasBandBorder,
|
|
216
|
+
}: {
|
|
217
|
+
layout: SharedValue<ReferenceLineLayout>;
|
|
218
|
+
color: string;
|
|
219
|
+
strokeWidth: number;
|
|
220
|
+
intervals: [number, number];
|
|
221
|
+
isBand: boolean;
|
|
222
|
+
isTimeBand: boolean;
|
|
223
|
+
bandFillOpacity: number;
|
|
224
|
+
hasBandBorder: boolean;
|
|
225
|
+
}) {
|
|
144
226
|
const lineBuilder = usePathBuilder();
|
|
145
227
|
const bandBuilder = usePathBuilder();
|
|
146
228
|
const borderBuilder = usePathBuilder();
|
|
147
|
-
const connBuilder = usePathBuilder();
|
|
148
|
-
const chevBuilder = usePathBuilder();
|
|
149
229
|
|
|
150
230
|
const linePath = useDerivedValue(() => {
|
|
151
231
|
const b = lineBuilder.value;
|
|
152
232
|
const l = layout.get();
|
|
153
|
-
// A plain horizontal line, drawn at the line extent (full-width when opted in).
|
|
154
|
-
// A non-full-width badge instead draws a pill + connector to the edge, below.
|
|
155
233
|
if (l.visible && l.drawLine && !isBand) {
|
|
156
234
|
b.moveTo(l.lineX1, l.y);
|
|
157
235
|
b.lineTo(l.lineX2, l.y);
|
|
158
236
|
}
|
|
159
237
|
return b.detach();
|
|
160
238
|
});
|
|
161
|
-
|
|
162
239
|
const bandPath = useDerivedValue(() => {
|
|
163
240
|
const b = bandBuilder.value;
|
|
164
241
|
const l = layout.get();
|
|
@@ -171,19 +248,16 @@ export function ReferenceLineOverlay({
|
|
|
171
248
|
}
|
|
172
249
|
return b.detach();
|
|
173
250
|
});
|
|
174
|
-
|
|
175
251
|
const bandBorderPath = useDerivedValue(() => {
|
|
176
252
|
const b = borderBuilder.value;
|
|
177
253
|
const l = layout.get();
|
|
178
254
|
if (l.visible && hasBandBorder) {
|
|
179
|
-
if (
|
|
180
|
-
// Vertical edges at the band's left / right.
|
|
255
|
+
if (isTimeBand) {
|
|
181
256
|
b.moveTo(l.lineX1, l.y);
|
|
182
257
|
b.lineTo(l.lineX1, l.yBottom);
|
|
183
258
|
b.moveTo(l.lineX2, l.y);
|
|
184
259
|
b.lineTo(l.lineX2, l.yBottom);
|
|
185
260
|
} else {
|
|
186
|
-
// Horizontal edges at the band's top / bottom.
|
|
187
261
|
b.moveTo(l.lineX1, l.y);
|
|
188
262
|
b.lineTo(l.lineX2, l.y);
|
|
189
263
|
b.moveTo(l.lineX1, l.yBottom);
|
|
@@ -192,18 +266,125 @@ export function ReferenceLineOverlay({
|
|
|
192
266
|
}
|
|
193
267
|
return b.detach();
|
|
194
268
|
});
|
|
269
|
+
const lineOpacity = useDerivedValue(() => {
|
|
270
|
+
const l = layout.get();
|
|
271
|
+
return l.visible && l.drawLine && !isBand ? 1 : 0;
|
|
272
|
+
});
|
|
273
|
+
const bandOpacity = useDerivedValue(() =>
|
|
274
|
+
layout.get().visible && isBand ? bandFillOpacity : 0,
|
|
275
|
+
);
|
|
276
|
+
const bandBorderOpacity = useDerivedValue(() =>
|
|
277
|
+
layout.get().visible && hasBandBorder ? 1 : 0,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<Group>
|
|
282
|
+
{isBand && (
|
|
283
|
+
<Group opacity={bandOpacity}>
|
|
284
|
+
<Path path={bandPath} style="fill" color={color} />
|
|
285
|
+
</Group>
|
|
286
|
+
)}
|
|
287
|
+
{hasBandBorder && (
|
|
288
|
+
<Group opacity={bandBorderOpacity}>
|
|
289
|
+
<Path
|
|
290
|
+
path={bandBorderPath}
|
|
291
|
+
style="stroke"
|
|
292
|
+
strokeWidth={strokeWidth}
|
|
293
|
+
color={color}
|
|
294
|
+
>
|
|
295
|
+
<DashPathEffect intervals={intervals} />
|
|
296
|
+
</Path>
|
|
297
|
+
</Group>
|
|
298
|
+
)}
|
|
299
|
+
{!isBand && (
|
|
300
|
+
<Group opacity={lineOpacity}>
|
|
301
|
+
<Path
|
|
302
|
+
path={linePath}
|
|
303
|
+
style="stroke"
|
|
304
|
+
strokeWidth={strokeWidth}
|
|
305
|
+
color={color}
|
|
306
|
+
>
|
|
307
|
+
<DashPathEffect intervals={intervals} />
|
|
308
|
+
</Path>
|
|
309
|
+
</Group>
|
|
310
|
+
)}
|
|
311
|
+
</Group>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
195
314
|
|
|
196
|
-
|
|
315
|
+
/** Badge, connector, and label pass, painted above the chart's left-edge fade. */
|
|
316
|
+
function ReferenceLineBadgePass({
|
|
317
|
+
layout,
|
|
318
|
+
color,
|
|
319
|
+
strokeWidth,
|
|
320
|
+
intervals,
|
|
321
|
+
badgeFont,
|
|
322
|
+
badgePosition,
|
|
323
|
+
labelColor,
|
|
324
|
+
badgeBackground,
|
|
325
|
+
badgeBorderColor,
|
|
326
|
+
badgeBorderWidth,
|
|
327
|
+
badgeRadius,
|
|
328
|
+
badgeOffsetX,
|
|
329
|
+
badgeOffsetY,
|
|
330
|
+
suppressTag,
|
|
331
|
+
suppressTagWhenOffAxis,
|
|
332
|
+
customTagWidths,
|
|
333
|
+
groupHidden,
|
|
334
|
+
index,
|
|
335
|
+
}: {
|
|
336
|
+
layout: SharedValue<ReferenceLineLayout>;
|
|
337
|
+
color: string;
|
|
338
|
+
strokeWidth: number;
|
|
339
|
+
intervals: [number, number];
|
|
340
|
+
badgeFont: SkFont;
|
|
341
|
+
badgePosition: "left" | "center" | "right" | undefined;
|
|
342
|
+
labelColor: string;
|
|
343
|
+
badgeBackground: string;
|
|
344
|
+
badgeBorderColor: string;
|
|
345
|
+
badgeBorderWidth: number;
|
|
346
|
+
badgeRadius: number;
|
|
347
|
+
badgeOffsetX: number;
|
|
348
|
+
badgeOffsetY: number;
|
|
349
|
+
suppressTag: boolean;
|
|
350
|
+
suppressTagWhenOffAxis: boolean;
|
|
351
|
+
customTagWidths?: SharedValue<number[]>;
|
|
352
|
+
groupHidden?: SharedValue<boolean[]>;
|
|
353
|
+
index: number;
|
|
354
|
+
}) {
|
|
355
|
+
const connBuilder = usePathBuilder();
|
|
356
|
+
const chevBuilder = usePathBuilder();
|
|
357
|
+
const badgeTransform =
|
|
358
|
+
badgeOffsetX !== 0 || badgeOffsetY !== 0
|
|
359
|
+
? [{ translateX: badgeOffsetX }, { translateY: badgeOffsetY }]
|
|
360
|
+
: undefined;
|
|
197
361
|
const connPath = useDerivedValue(() => {
|
|
198
362
|
const b = connBuilder.value;
|
|
199
363
|
const l = layout.get();
|
|
200
364
|
if (l.visible && l.badge && l.connStart >= 0) {
|
|
201
|
-
|
|
202
|
-
|
|
365
|
+
let start = l.connStart;
|
|
366
|
+
let end = l.connEnd;
|
|
367
|
+
const customTagActive =
|
|
368
|
+
suppressTag || (suppressTagWhenOffAxis && l.offAxis);
|
|
369
|
+
const customWidth = customTagActive
|
|
370
|
+
? customTagWidths?.get()[index] ?? 0
|
|
371
|
+
: 0;
|
|
372
|
+
if (customWidth > 0) {
|
|
373
|
+
if (badgePosition === "left") {
|
|
374
|
+
start = l.x1 + BADGE_EDGE_INSET + customWidth + CONNECTOR_GAP;
|
|
375
|
+
end = l.x2;
|
|
376
|
+
} else if (badgePosition === "right") {
|
|
377
|
+
start = l.x1;
|
|
378
|
+
end = l.x2 - BADGE_EDGE_INSET - customWidth - CONNECTOR_GAP;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (end > start) {
|
|
382
|
+
b.moveTo(start, l.y);
|
|
383
|
+
b.lineTo(end, l.y);
|
|
384
|
+
}
|
|
203
385
|
}
|
|
204
386
|
return b.detach();
|
|
205
387
|
});
|
|
206
|
-
|
|
207
388
|
const chevronPath = useDerivedValue(() => {
|
|
208
389
|
const b = chevBuilder.value;
|
|
209
390
|
const l = layout.get();
|
|
@@ -223,41 +404,34 @@ export function ReferenceLineOverlay({
|
|
|
223
404
|
}
|
|
224
405
|
return b.detach();
|
|
225
406
|
});
|
|
226
|
-
|
|
227
|
-
const lineOpacity = useDerivedValue(() => {
|
|
407
|
+
const badgeOpacity = useDerivedValue(() => {
|
|
228
408
|
const l = layout.get();
|
|
229
|
-
|
|
409
|
+
const grouped = groupHidden ? groupHidden.get()[index] === true : false;
|
|
410
|
+
const customTagActive =
|
|
411
|
+
suppressTag || (suppressTagWhenOffAxis && l.offAxis);
|
|
412
|
+
return !customTagActive && !grouped && l.visible && l.badge ? 1 : 0;
|
|
230
413
|
});
|
|
231
|
-
const
|
|
232
|
-
layout.get().visible && isBand ? bandFillOpacity : 0,
|
|
233
|
-
);
|
|
234
|
-
const bandBorderOpacity = useDerivedValue(() =>
|
|
235
|
-
layout.get().visible && hasBandBorder ? 1 : 0,
|
|
236
|
-
);
|
|
237
|
-
const badgeOpacity = useDerivedValue(() => {
|
|
414
|
+
const connectorOpacity = useDerivedValue(() => {
|
|
238
415
|
const l = layout.get();
|
|
239
416
|
const grouped = groupHidden ? groupHidden.get()[index] === true : false;
|
|
240
|
-
return !
|
|
417
|
+
return !grouped && l.visible && l.badge && l.connStart >= 0 ? 1 : 0;
|
|
241
418
|
});
|
|
242
|
-
// Text + icon ride in the badge pass too, so they stay crisp above the fade.
|
|
243
419
|
const labelOpacity = useDerivedValue(() => {
|
|
244
420
|
const l = layout.get();
|
|
245
421
|
const grouped = groupHidden ? groupHidden.get()[index] === true : false;
|
|
246
|
-
|
|
422
|
+
const customTagActive =
|
|
423
|
+
suppressTag || (suppressTagWhenOffAxis && l.offAxis);
|
|
424
|
+
return !customTagActive && !grouped && l.visible && l.label.length > 0 ? 1 : 0;
|
|
247
425
|
});
|
|
248
426
|
const iconOpacity = useDerivedValue(() => {
|
|
249
427
|
const l = layout.get();
|
|
250
428
|
return l.visible && l.icon.length > 0 ? 1 : 0;
|
|
251
429
|
});
|
|
252
|
-
|
|
253
430
|
const labelX = useDerivedValue(() => layout.get().labelX);
|
|
254
431
|
const labelY = useDerivedValue(() => layout.get().labelY);
|
|
255
432
|
const labelText = useDerivedValue(() => layout.get().label);
|
|
256
433
|
const iconX = useDerivedValue(() => layout.get().iconX);
|
|
257
434
|
const iconText = useDerivedValue(() => layout.get().icon);
|
|
258
|
-
|
|
259
|
-
// Font metrics depend only on the (stable) font, so read them once instead of
|
|
260
|
-
// on every frame inside the pill worklet (`getMetrics` allocates + crosses JSI).
|
|
261
435
|
const { ascent: fontAscent, height: pillH } = (() => {
|
|
262
436
|
const fm = badgeFont.getMetrics();
|
|
263
437
|
return {
|
|
@@ -265,8 +439,6 @@ export function ReferenceLineOverlay({
|
|
|
265
439
|
height: fm.descent - fm.ascent + BADGE_PILL_PAD_Y * 2,
|
|
266
440
|
};
|
|
267
441
|
})();
|
|
268
|
-
|
|
269
|
-
// Pill rect — position/size from the layout; vertically centered on the line.
|
|
270
442
|
const pillX = useDerivedValue(() => layout.get().pillX);
|
|
271
443
|
const pillW = useDerivedValue(() => layout.get().pillW);
|
|
272
444
|
const pillY = useDerivedValue(
|
|
@@ -275,101 +447,62 @@ export function ReferenceLineOverlay({
|
|
|
275
447
|
|
|
276
448
|
return (
|
|
277
449
|
<Group>
|
|
278
|
-
{
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
path={connPath}
|
|
317
|
-
style="stroke"
|
|
318
|
-
strokeWidth={strokeWidth}
|
|
319
|
-
color={color}
|
|
320
|
-
>
|
|
321
|
-
<DashPathEffect intervals={intervals} />
|
|
322
|
-
</Path>
|
|
323
|
-
<RoundedRect
|
|
324
|
-
x={pillX}
|
|
325
|
-
y={pillY}
|
|
326
|
-
width={pillW}
|
|
327
|
-
height={pillH}
|
|
328
|
-
r={badgeRadius}
|
|
329
|
-
color={badgeBackground}
|
|
330
|
-
/>
|
|
331
|
-
<RoundedRect
|
|
332
|
-
x={pillX}
|
|
333
|
-
y={pillY}
|
|
334
|
-
width={pillW}
|
|
335
|
-
height={pillH}
|
|
336
|
-
r={badgeRadius}
|
|
337
|
-
color={badgeBorderColor}
|
|
338
|
-
style="stroke"
|
|
339
|
-
strokeWidth={badgeBorderWidth}
|
|
340
|
-
/>
|
|
341
|
-
<Path
|
|
342
|
-
path={chevronPath}
|
|
343
|
-
style="stroke"
|
|
344
|
-
strokeWidth={1.5}
|
|
345
|
-
color={color}
|
|
346
|
-
strokeCap="round"
|
|
347
|
-
strokeJoin="round"
|
|
348
|
-
/>
|
|
349
|
-
<Group opacity={iconOpacity}>
|
|
350
|
-
<SkiaText
|
|
351
|
-
x={iconX}
|
|
352
|
-
y={labelY}
|
|
353
|
-
text={iconText}
|
|
354
|
-
font={badgeFont}
|
|
355
|
-
color={labelColor}
|
|
356
|
-
/>
|
|
357
|
-
</Group>
|
|
358
|
-
</Group>
|
|
359
|
-
)}
|
|
360
|
-
|
|
361
|
-
{/* Labels ride in the badge pass too, so they stay crisp above the fade. */}
|
|
362
|
-
{badgeLayer && (
|
|
363
|
-
<Group opacity={labelOpacity} transform={badgeTransform}>
|
|
450
|
+
<Group opacity={connectorOpacity} transform={badgeTransform}>
|
|
451
|
+
<Path
|
|
452
|
+
path={connPath}
|
|
453
|
+
style="stroke"
|
|
454
|
+
strokeWidth={strokeWidth}
|
|
455
|
+
color={color}
|
|
456
|
+
>
|
|
457
|
+
<DashPathEffect intervals={intervals} />
|
|
458
|
+
</Path>
|
|
459
|
+
</Group>
|
|
460
|
+
<Group opacity={badgeOpacity} transform={badgeTransform}>
|
|
461
|
+
<RoundedRect
|
|
462
|
+
x={pillX}
|
|
463
|
+
y={pillY}
|
|
464
|
+
width={pillW}
|
|
465
|
+
height={pillH}
|
|
466
|
+
r={badgeRadius}
|
|
467
|
+
color={badgeBackground}
|
|
468
|
+
/>
|
|
469
|
+
<RoundedRect
|
|
470
|
+
x={pillX}
|
|
471
|
+
y={pillY}
|
|
472
|
+
width={pillW}
|
|
473
|
+
height={pillH}
|
|
474
|
+
r={badgeRadius}
|
|
475
|
+
color={badgeBorderColor}
|
|
476
|
+
style="stroke"
|
|
477
|
+
strokeWidth={badgeBorderWidth}
|
|
478
|
+
/>
|
|
479
|
+
<Path
|
|
480
|
+
path={chevronPath}
|
|
481
|
+
style="stroke"
|
|
482
|
+
strokeWidth={1.5}
|
|
483
|
+
color={color}
|
|
484
|
+
strokeCap="round"
|
|
485
|
+
strokeJoin="round"
|
|
486
|
+
/>
|
|
487
|
+
<Group opacity={iconOpacity}>
|
|
364
488
|
<SkiaText
|
|
365
|
-
x={
|
|
489
|
+
x={iconX}
|
|
366
490
|
y={labelY}
|
|
367
|
-
text={
|
|
491
|
+
text={iconText}
|
|
368
492
|
font={badgeFont}
|
|
369
493
|
color={labelColor}
|
|
370
494
|
/>
|
|
371
495
|
</Group>
|
|
372
|
-
|
|
496
|
+
</Group>
|
|
497
|
+
<Group opacity={labelOpacity} transform={badgeTransform}>
|
|
498
|
+
<SkiaText
|
|
499
|
+
x={labelX}
|
|
500
|
+
y={labelY}
|
|
501
|
+
text={labelText}
|
|
502
|
+
font={badgeFont}
|
|
503
|
+
color={labelColor}
|
|
504
|
+
/>
|
|
505
|
+
</Group>
|
|
373
506
|
</Group>
|
|
374
507
|
);
|
|
375
508
|
}
|
|
@@ -52,6 +52,7 @@ export function YAxisOverlay({
|
|
|
52
52
|
badgeCenterY,
|
|
53
53
|
badgeFontSize,
|
|
54
54
|
badgeOffsetY = 0,
|
|
55
|
+
badgeOpacity,
|
|
55
56
|
seriesLabelInset = 0,
|
|
56
57
|
gridStyle,
|
|
57
58
|
variant = "all",
|
|
@@ -74,6 +75,8 @@ export function YAxisOverlay({
|
|
|
74
75
|
badgeFontSize?: number;
|
|
75
76
|
/** Configured live badge Y offset. */
|
|
76
77
|
badgeOffsetY?: number;
|
|
78
|
+
/** Live badge opacity. A fully hidden badge does not suppress an axis label. */
|
|
79
|
+
badgeOpacity?: SharedValue<number>;
|
|
77
80
|
/** When > 0, series labels occupy the left portion of the gutter; Y-axis labels right-align. */
|
|
78
81
|
seriesLabelInset?: number;
|
|
79
82
|
/** Grid-line styling overrides. Omit for the legacy solid 1px line. */
|
|
@@ -118,7 +121,8 @@ export function YAxisOverlay({
|
|
|
118
121
|
const fm = font.getMetrics();
|
|
119
122
|
const baselineOffset = (fm.ascent + fm.descent) / 2;
|
|
120
123
|
const labelHeight = fm.descent - fm.ascent;
|
|
121
|
-
const
|
|
124
|
+
const badgeIsVisible = badgeOpacity === undefined || badgeOpacity.get() > 0;
|
|
125
|
+
const resolvedBadgeCenterY = badgeCenterY && badgeIsVisible
|
|
122
126
|
? badgeCenterY.get() + badgeOffsetY
|
|
123
127
|
: null;
|
|
124
128
|
const badgeHeight =
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TimeScrollConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve whether historical time-scroll views should suppress indicators that
|
|
5
|
+
* still point at the live price.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveHideLiveOnScrollBack(
|
|
8
|
+
timeScroll: boolean | TimeScrollConfig | undefined,
|
|
9
|
+
followViewEdge: boolean,
|
|
10
|
+
): boolean {
|
|
11
|
+
const hide =
|
|
12
|
+
typeof timeScroll === "object"
|
|
13
|
+
? (timeScroll.hideLiveOnScrollBack ?? true)
|
|
14
|
+
: true;
|
|
15
|
+
return hide && !followViewEdge;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Worklet-safe opacity gate shared by the live badge, dot, and dashed value line. */
|
|
19
|
+
export function liveIndicatorScrollOpacity(
|
|
20
|
+
hideLiveOnScrollBack: boolean,
|
|
21
|
+
viewEnd: number | null,
|
|
22
|
+
): 0 | 1 {
|
|
23
|
+
"worklet";
|
|
24
|
+
return hideLiveOnScrollBack && viewEnd != null ? 0 : 1;
|
|
25
|
+
}
|