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