react-native-livechart 4.21.0 → 4.22.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 +5 -1
- package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +4 -2
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
- package/dist/components/CustomThresholdBadgeOverlay.d.ts +3 -1
- package/dist/components/CustomThresholdBadgeOverlay.d.ts.map +1 -1
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
- package/dist/components/ExtremaConnectorOverlay.d.ts +5 -1
- package/dist/components/ExtremaConnectorOverlay.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/MarkerOverlay.d.ts +4 -2
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineSeriesOverlay.d.ts.map +1 -1
- package/dist/components/ThresholdLineOverlay.d.ts +3 -1
- package/dist/components/ThresholdLineOverlay.d.ts.map +1 -1
- package/dist/components/thresholdSplitShaderSource.d.ts.map +1 -1
- package/dist/core/chartDataPresence.d.ts +8 -0
- package/dist/core/chartDataPresence.d.ts.map +1 -0
- package/dist/core/liveChartEngineTick.d.ts +3 -0
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +2 -0
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +4 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/markerAtlas.d.ts +3 -2
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/hooks/useChartReveal.d.ts +1 -1
- package/dist/hooks/useChartReveal.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useDegen.d.ts.map +1 -1
- package/dist/hooks/useLiveChartHasData.d.ts +2 -2
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
- package/dist/hooks/useReferenceLineSeries.d.ts +1 -1
- package/dist/hooks/useReferenceLineSeries.d.ts.map +1 -1
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
- package/dist/hooks/useThreshold.d.ts +10 -5
- package/dist/hooks/useThreshold.d.ts.map +1 -1
- package/dist/math/referenceLineSeries.d.ts +1 -1
- package/dist/math/referenceLineSeries.d.ts.map +1 -1
- package/dist/math/threshold.d.ts +4 -3
- package/dist/math/threshold.d.ts.map +1 -1
- package/dist/types.d.ts +23 -8
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AxisLabelOverlay.tsx +162 -81
- package/src/components/CrosshairOverlay.tsx +394 -147
- package/src/components/CustomMarkerOverlay.tsx +9 -6
- package/src/components/CustomThresholdBadgeOverlay.tsx +11 -4
- package/src/components/DegenParticlesOverlay.tsx +34 -23
- package/src/components/ExtremaConnectorOverlay.tsx +43 -3
- package/src/components/LiveChart.tsx +1709 -1025
- package/src/components/LiveChartSeries.tsx +445 -296
- package/src/components/MarkerOverlay.tsx +215 -156
- package/src/components/ReferenceLineOverlay.tsx +77 -50
- package/src/components/ReferenceLineSeriesOverlay.tsx +2 -0
- package/src/components/ThresholdLineOverlay.tsx +16 -6
- package/src/components/thresholdSplitShaderSource.ts +4 -1
- package/src/core/chartDataPresence.ts +24 -0
- package/src/core/liveChartEngineTick.ts +4 -0
- package/src/core/resolveConfig.ts +3 -0
- package/src/core/useLiveChartEngine.ts +16 -3
- package/src/core/useLiveChartSeriesEngine.ts +2 -0
- package/src/draw/markerAtlas.ts +8 -3
- package/src/hooks/useChartReveal.ts +1 -1
- package/src/hooks/useCrosshair.ts +69 -46
- package/src/hooks/useDegen.ts +82 -53
- package/src/hooks/useLiveChartHasData.ts +8 -4
- package/src/hooks/useReferenceLineSeries.ts +5 -0
- package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
- package/src/hooks/useThreshold.ts +41 -15
- package/src/math/referenceLineSeries.ts +9 -4
- package/src/math/threshold.ts +10 -3
- package/src/types.ts +23 -8
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
isConnectorMarker,
|
|
31
31
|
markerAppearanceSig,
|
|
32
32
|
type AtlasCell,
|
|
33
|
+
type MarkerAtlasPalette,
|
|
33
34
|
} from "../draw/markerAtlas";
|
|
34
35
|
import {
|
|
35
36
|
markersSignature,
|
|
@@ -45,6 +46,7 @@ import type {
|
|
|
45
46
|
LiveChartPalette,
|
|
46
47
|
LiveChartPoint,
|
|
47
48
|
Marker,
|
|
49
|
+
MarkerGroupBadge,
|
|
48
50
|
MarkerRenderContext,
|
|
49
51
|
SeriesConfig,
|
|
50
52
|
} from "../types";
|
|
@@ -85,7 +87,12 @@ function pushGroupCountBadge(
|
|
|
85
87
|
const bg = cells[groupBgSig(repColor)];
|
|
86
88
|
if (bg) {
|
|
87
89
|
transforms.push(
|
|
88
|
-
Skia.RSXform(
|
|
90
|
+
Skia.RSXform(
|
|
91
|
+
invScale * mul,
|
|
92
|
+
0,
|
|
93
|
+
cx - (bg.w * mul) / 2,
|
|
94
|
+
cy - (bg.h * mul) / 2,
|
|
95
|
+
),
|
|
89
96
|
);
|
|
90
97
|
sprites.push(bg.rect);
|
|
91
98
|
}
|
|
@@ -219,18 +226,7 @@ function ConnectorGlyph({
|
|
|
219
226
|
*
|
|
220
227
|
* Glyph precedence per marker: `image` → `icon` (text) → built-in `kind` shape.
|
|
221
228
|
*/
|
|
222
|
-
|
|
223
|
-
markers,
|
|
224
|
-
engine,
|
|
225
|
-
padding,
|
|
226
|
-
palette,
|
|
227
|
-
font,
|
|
228
|
-
series,
|
|
229
|
-
lineData,
|
|
230
|
-
lineLinear,
|
|
231
|
-
renderMarker,
|
|
232
|
-
cluster,
|
|
233
|
-
}: {
|
|
229
|
+
type MarkerOverlayProps = {
|
|
234
230
|
markers: SharedValue<Marker[]>;
|
|
235
231
|
engine: ChartEngineLayout;
|
|
236
232
|
padding: ChartPadding;
|
|
@@ -250,9 +246,24 @@ export function MarkerOverlay({
|
|
|
250
246
|
renderMarker?: (marker: Marker, ctx: MarkerRenderContext) => unknown;
|
|
251
247
|
/** Collision config; `"stacked"` fans/collapses co-located markers. */
|
|
252
248
|
cluster: ResolvedMarkerCluster;
|
|
253
|
-
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
function useMarkerOverlayModel({
|
|
252
|
+
markers,
|
|
253
|
+
engine,
|
|
254
|
+
padding,
|
|
255
|
+
palette,
|
|
256
|
+
font,
|
|
257
|
+
series,
|
|
258
|
+
lineData,
|
|
259
|
+
lineLinear,
|
|
260
|
+
renderMarker,
|
|
261
|
+
cluster,
|
|
262
|
+
}: MarkerOverlayProps) {
|
|
254
263
|
// Seed from the current markers at mount; the reaction below keeps it in sync.
|
|
255
|
-
const [snapshot, setSnapshot] = useState<Marker[]>(() =>
|
|
264
|
+
const [snapshot, setSnapshot] = useState<Marker[]>(() =>
|
|
265
|
+
markers.get().slice(),
|
|
266
|
+
);
|
|
256
267
|
|
|
257
268
|
// Read the `markers` prop from closure rather than a SharedValue passed
|
|
258
269
|
// through `scheduleOnRN` (which loses `.get()`); mirrors the data model.
|
|
@@ -295,44 +306,85 @@ export function MarkerOverlay({
|
|
|
295
306
|
return o;
|
|
296
307
|
}, [customKey]);
|
|
297
308
|
|
|
298
|
-
// Rebuild the atlas image only when the set of distinct appearances or the
|
|
299
|
-
// theme/font changes — never per frame. `markersSignature` already drives the
|
|
300
|
-
// snapshot, so this useMemo re-runs at most at the snapshot cadence.
|
|
301
|
-
const appearanceKey = useMemo(() => {
|
|
302
|
-
const sigs = new Set<string>();
|
|
303
|
-
for (let i = 0; i < snapshot.length; i++) {
|
|
304
|
-
const m = snapshot[i];
|
|
305
|
-
if (!isConnectorMarker(m) && !customIds[m.id])
|
|
306
|
-
sigs.add(markerAppearanceSig(m));
|
|
307
|
-
}
|
|
308
|
-
return Array.from(sigs).sort().join("\x1e");
|
|
309
|
-
}, [snapshot, customIds]);
|
|
310
|
-
// Cells bake in resolved colors; include the palette fields they depend on.
|
|
311
|
-
const paletteKey = `${palette.bgRgb.join(",")}|${palette.line}|${palette.refLine}|${palette.dotUp}|${palette.refLabel}`;
|
|
312
309
|
// Rasterize at the screen's device-pixel ratio so sprites stay crisp on
|
|
313
310
|
// retina canvases instead of being upscaled from a logical-sized texture.
|
|
314
311
|
const dpr = PixelRatio.get();
|
|
315
312
|
const clusterStacked = cluster.mode === "stacked";
|
|
313
|
+
const [paletteBgR, paletteBgG, paletteBgB] = palette.bgRgb;
|
|
314
|
+
const paletteLine = palette.line;
|
|
315
|
+
const paletteRefLine = palette.refLine;
|
|
316
|
+
const paletteDotUp = palette.dotUp;
|
|
317
|
+
const paletteRefLabel = palette.refLabel;
|
|
318
|
+
const atlasPalette = useMemo<MarkerAtlasPalette>(
|
|
319
|
+
() => ({
|
|
320
|
+
bgRgb: [paletteBgR, paletteBgG, paletteBgB],
|
|
321
|
+
line: paletteLine,
|
|
322
|
+
refLine: paletteRefLine,
|
|
323
|
+
dotUp: paletteDotUp,
|
|
324
|
+
refLabel: paletteRefLabel,
|
|
325
|
+
}),
|
|
326
|
+
[
|
|
327
|
+
paletteBgR,
|
|
328
|
+
paletteBgG,
|
|
329
|
+
paletteBgB,
|
|
330
|
+
paletteLine,
|
|
331
|
+
paletteRefLine,
|
|
332
|
+
paletteDotUp,
|
|
333
|
+
paletteRefLabel,
|
|
334
|
+
],
|
|
335
|
+
);
|
|
316
336
|
// A dedicated group badge (object form) bakes one extra atlas cell; its image
|
|
317
337
|
// identity + styling drive the rebuild key alongside the marker appearances.
|
|
318
338
|
const groupBadgeCfg =
|
|
319
339
|
typeof cluster.groupBadge === "object" ? cluster.groupBadge : undefined;
|
|
340
|
+
const hasGroupBadge = groupBadgeCfg !== undefined;
|
|
320
341
|
const groupBadgeImage = groupBadgeCfg?.image;
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
342
|
+
const groupBadgeIcon = groupBadgeCfg?.icon;
|
|
343
|
+
const groupBadgeColor = groupBadgeCfg?.color;
|
|
344
|
+
const groupBadgePill = groupBadgeCfg?.pill;
|
|
345
|
+
const groupBadgeSize = groupBadgeCfg?.size;
|
|
346
|
+
const groupBadgeLetterSpacing = groupBadgeCfg?.letterSpacing;
|
|
347
|
+
const atlasGroupBadge = useMemo<MarkerGroupBadge | undefined>(
|
|
348
|
+
() =>
|
|
349
|
+
hasGroupBadge
|
|
350
|
+
? {
|
|
351
|
+
image: groupBadgeImage,
|
|
352
|
+
icon: groupBadgeIcon,
|
|
353
|
+
color: groupBadgeColor,
|
|
354
|
+
pill: groupBadgePill,
|
|
355
|
+
size: groupBadgeSize,
|
|
356
|
+
letterSpacing: groupBadgeLetterSpacing,
|
|
357
|
+
}
|
|
358
|
+
: undefined,
|
|
359
|
+
[
|
|
360
|
+
hasGroupBadge,
|
|
361
|
+
groupBadgeImage,
|
|
362
|
+
groupBadgeIcon,
|
|
363
|
+
groupBadgeColor,
|
|
364
|
+
groupBadgePill,
|
|
365
|
+
groupBadgeSize,
|
|
366
|
+
groupBadgeLetterSpacing,
|
|
367
|
+
],
|
|
368
|
+
);
|
|
324
369
|
const atlas = useMemo(
|
|
325
370
|
() =>
|
|
326
371
|
buildMarkerAtlas(
|
|
327
372
|
snapshot.filter((m) => !customIds[m.id]),
|
|
328
|
-
|
|
373
|
+
atlasPalette,
|
|
329
374
|
font,
|
|
330
375
|
dpr,
|
|
331
376
|
clusterStacked,
|
|
332
|
-
|
|
377
|
+
atlasGroupBadge,
|
|
333
378
|
),
|
|
334
|
-
|
|
335
|
-
|
|
379
|
+
[
|
|
380
|
+
snapshot,
|
|
381
|
+
customIds,
|
|
382
|
+
atlasPalette,
|
|
383
|
+
font,
|
|
384
|
+
dpr,
|
|
385
|
+
clusterStacked,
|
|
386
|
+
atlasGroupBadge,
|
|
387
|
+
],
|
|
336
388
|
);
|
|
337
389
|
const cells: Record<string, AtlasCell> = atlas.cells;
|
|
338
390
|
const atlasImage = atlas.image;
|
|
@@ -367,133 +419,133 @@ export function MarkerOverlay({
|
|
|
367
419
|
// Single per-frame worklet: project all markers, then emit a transform +
|
|
368
420
|
// source rect for each visible atlas marker. Three mappers total regardless
|
|
369
421
|
// of marker count (this + the two thin readers below).
|
|
370
|
-
const atlasData = useDerivedValue(
|
|
371
|
-
()
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
422
|
+
const atlasData = useDerivedValue(() => {
|
|
423
|
+
const ms = markers.get();
|
|
424
|
+
const proj = projRef.current!;
|
|
425
|
+
proj.tick = !proj.tick;
|
|
426
|
+
const buf = proj.tick ? proj.a : proj.b;
|
|
427
|
+
projectMarkers(ms, buf, {
|
|
428
|
+
canvasWidth: engine.canvasWidth.get(),
|
|
429
|
+
canvasHeight: engine.canvasHeight.get(),
|
|
430
|
+
padTop: padding.top,
|
|
431
|
+
padBottom: padding.bottom,
|
|
432
|
+
padLeft: padding.left,
|
|
433
|
+
padRight: padding.right,
|
|
434
|
+
timestamp: engine.timestamp.get(),
|
|
435
|
+
displayWindow: engine.displayWindow.get(),
|
|
436
|
+
displayMin: engine.displayMin.get(),
|
|
437
|
+
displayMax: engine.displayMax.get(),
|
|
438
|
+
series: series?.get(),
|
|
439
|
+
lineData: lineData?.get(),
|
|
440
|
+
lineLinear,
|
|
441
|
+
});
|
|
442
|
+
clusterMarkers(ms, buf, {
|
|
443
|
+
config: cluster,
|
|
444
|
+
minY: 0,
|
|
445
|
+
maxY: engine.canvasHeight.get(),
|
|
446
|
+
});
|
|
447
|
+
const atlasFrames = atlasFrameRef.current!;
|
|
448
|
+
atlasFrames.tick = !atlasFrames.tick;
|
|
449
|
+
const frame = atlasFrames.tick ? atlasFrames.a : atlasFrames.b;
|
|
450
|
+
const transforms = frame.transforms;
|
|
451
|
+
const sprites = frame.sprites;
|
|
452
|
+
transforms.length = 0;
|
|
453
|
+
sprites.length = 0;
|
|
454
|
+
for (let i = 0; i < ms.length; i++) {
|
|
455
|
+
const pt = buf[i];
|
|
456
|
+
// Collapsed-cluster members fold into their representative's badge.
|
|
457
|
+
if (!pt.visible || pt.hidden) continue;
|
|
458
|
+
const m = ms[i];
|
|
459
|
+
if (isConnectorMarker(m)) continue;
|
|
460
|
+
// Custom-rendered markers are floated as RN views, not drawn here.
|
|
461
|
+
if (customIds[m.id]) continue;
|
|
462
|
+
// Collapsed cluster. By default a round count badge; with
|
|
463
|
+
// `groupBadge: "marker"` the representative marker's own glyph, or with a
|
|
464
|
+
// `MarkerGroupBadge` object a dedicated baked badge — either optionally
|
|
465
|
+
// carrying a corner count, all within this one `drawAtlas`.
|
|
466
|
+
if (pt.isGrouped) {
|
|
467
|
+
const repColor = m.color ?? defaultMarkerColor(m.kind, palette);
|
|
468
|
+
const gb = cluster.groupBadge;
|
|
469
|
+
// Pick the non-count glyph cell: a dedicated group badge (object form)
|
|
470
|
+
// or the representative marker's own appearance.
|
|
471
|
+
const glyphCell =
|
|
472
|
+
typeof gb === "object" && gb !== null
|
|
473
|
+
? cells[GROUP_BADGE_SIG]
|
|
474
|
+
: gb === "marker"
|
|
475
|
+
? cells[markerAppearanceSig(m)]
|
|
476
|
+
: undefined;
|
|
477
|
+
if (glyphCell) {
|
|
478
|
+
transforms.push(
|
|
479
|
+
Skia.RSXform(
|
|
480
|
+
invScale,
|
|
481
|
+
0,
|
|
482
|
+
pt.x - glyphCell.w / 2,
|
|
483
|
+
pt.y - glyphCell.h / 2,
|
|
484
|
+
),
|
|
485
|
+
);
|
|
486
|
+
sprites.push(glyphCell.rect);
|
|
487
|
+
if (cluster.showGroupCount) {
|
|
488
|
+
pushGroupCountBadge(
|
|
489
|
+
transforms,
|
|
490
|
+
sprites,
|
|
491
|
+
cells,
|
|
492
|
+
digitWidths,
|
|
493
|
+
invScale,
|
|
494
|
+
pt.x + (glyphCell.w / 2) * GROUP_CORNER_INSET,
|
|
495
|
+
pt.y - (glyphCell.h / 2) * GROUP_CORNER_INSET,
|
|
496
|
+
repColor,
|
|
497
|
+
pt.groupCount,
|
|
498
|
+
GROUP_CORNER_SCALE,
|
|
499
|
+
groupBadgeCfg?.letterSpacing ?? 0,
|
|
434
500
|
);
|
|
435
|
-
sprites.push(glyphCell.rect);
|
|
436
|
-
if (cluster.showGroupCount) {
|
|
437
|
-
pushGroupCountBadge(
|
|
438
|
-
transforms,
|
|
439
|
-
sprites,
|
|
440
|
-
cells,
|
|
441
|
-
digitWidths,
|
|
442
|
-
invScale,
|
|
443
|
-
pt.x + (glyphCell.w / 2) * GROUP_CORNER_INSET,
|
|
444
|
-
pt.y - (glyphCell.h / 2) * GROUP_CORNER_INSET,
|
|
445
|
-
repColor,
|
|
446
|
-
pt.groupCount,
|
|
447
|
-
GROUP_CORNER_SCALE,
|
|
448
|
-
groupBadgeCfg?.letterSpacing ?? 0,
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
continue;
|
|
452
501
|
}
|
|
453
|
-
// Default — or fall back here when a non-count badge has no baked cell
|
|
454
|
-
// (e.g. an empty group-badge config): the round count badge.
|
|
455
|
-
pushGroupCountBadge(
|
|
456
|
-
transforms,
|
|
457
|
-
sprites,
|
|
458
|
-
cells,
|
|
459
|
-
digitWidths,
|
|
460
|
-
invScale,
|
|
461
|
-
pt.x,
|
|
462
|
-
pt.y,
|
|
463
|
-
repColor,
|
|
464
|
-
pt.groupCount,
|
|
465
|
-
1,
|
|
466
|
-
groupBadgeCfg?.letterSpacing ?? 0,
|
|
467
|
-
);
|
|
468
502
|
continue;
|
|
469
503
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
504
|
+
// Default — or fall back here when a non-count badge has no baked cell
|
|
505
|
+
// (e.g. an empty group-badge config): the round count badge.
|
|
506
|
+
pushGroupCountBadge(
|
|
507
|
+
transforms,
|
|
508
|
+
sprites,
|
|
509
|
+
cells,
|
|
510
|
+
digitWidths,
|
|
511
|
+
invScale,
|
|
512
|
+
pt.x,
|
|
513
|
+
pt.y,
|
|
514
|
+
repColor,
|
|
515
|
+
pt.groupCount,
|
|
516
|
+
1,
|
|
517
|
+
groupBadgeCfg?.letterSpacing ?? 0,
|
|
476
518
|
);
|
|
477
|
-
|
|
519
|
+
continue;
|
|
478
520
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
521
|
+
const cell = cells[markerAppearanceSig(m)];
|
|
522
|
+
if (!cell) continue;
|
|
523
|
+
// Center the cell on the projected point. The cell's source rect is in
|
|
524
|
+
// device pixels, so scale by 1/dpr to land it at its logical size.
|
|
525
|
+
transforms.push(
|
|
526
|
+
Skia.RSXform(invScale, 0, pt.x - cell.w / 2, pt.y - cell.h / 2),
|
|
527
|
+
);
|
|
528
|
+
sprites.push(cell.rect);
|
|
529
|
+
}
|
|
530
|
+
return frame;
|
|
531
|
+
}, [
|
|
532
|
+
cells,
|
|
533
|
+
customIds,
|
|
534
|
+
invScale,
|
|
535
|
+
digitWidths,
|
|
536
|
+
markers,
|
|
537
|
+
engine,
|
|
538
|
+
padding,
|
|
539
|
+
palette,
|
|
540
|
+
series,
|
|
541
|
+
lineData,
|
|
542
|
+
lineLinear,
|
|
543
|
+
cluster,
|
|
544
|
+
]);
|
|
545
|
+
const transforms = useDerivedValue(
|
|
546
|
+
() => atlasData.get().transforms,
|
|
547
|
+
[atlasData],
|
|
495
548
|
);
|
|
496
|
-
const transforms = useDerivedValue(() => atlasData.get().transforms, [atlasData]);
|
|
497
549
|
const sprites = useDerivedValue(() => atlasData.get().sprites, [atlasData]);
|
|
498
550
|
|
|
499
551
|
const axisY = useDerivedValue(
|
|
@@ -502,6 +554,13 @@ export function MarkerOverlay({
|
|
|
502
554
|
|
|
503
555
|
const connectors = snapshot.filter(isConnectorMarker);
|
|
504
556
|
|
|
557
|
+
return { atlasImage, sprites, transforms, connectors, axisY };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export function MarkerOverlay(props: MarkerOverlayProps) {
|
|
561
|
+
const { engine, padding, palette, series, lineData, lineLinear } = props;
|
|
562
|
+
const { atlasImage, sprites, transforms, connectors, axisY } =
|
|
563
|
+
useMarkerOverlayModel(props);
|
|
505
564
|
return (
|
|
506
565
|
<Group>
|
|
507
566
|
{atlasImage && (
|
|
@@ -18,7 +18,10 @@ import {
|
|
|
18
18
|
type ReferenceLineLayout,
|
|
19
19
|
} from "../hooks/useReferenceLine";
|
|
20
20
|
import { MONO_FONT_FAMILY } from "../lib/monoFontFamily";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
referenceLineForm,
|
|
23
|
+
resolveReferenceBadge,
|
|
24
|
+
} from "../math/referenceLines";
|
|
22
25
|
import type { FontConfig, LiveChartPalette, ReferenceLine } from "../types";
|
|
23
26
|
import { ReferenceLineSeriesOverlay } from "./ReferenceLineSeriesOverlay";
|
|
24
27
|
|
|
@@ -65,7 +68,7 @@ type ReferenceLineOverlayProps = {
|
|
|
65
68
|
* this line — used when a custom `renderReferenceLine` element owns the tag.
|
|
66
69
|
* The line / band stroke and a badged line's dashed connector still draw. No
|
|
67
70
|
* effect on the base pass.
|
|
68
|
-
|
|
71
|
+
*/
|
|
69
72
|
suppressTag?: boolean;
|
|
70
73
|
/**
|
|
71
74
|
* Suppress the built-in tag only while the line is off-axis. Used by
|
|
@@ -117,6 +120,52 @@ export function ReferenceLineOverlay(props: ReferenceLineOverlayProps) {
|
|
|
117
120
|
return <ReferenceLineStaticOverlay {...props} />;
|
|
118
121
|
}
|
|
119
122
|
|
|
123
|
+
function resolveStaticReferenceLineAppearance(
|
|
124
|
+
line: ReferenceLine,
|
|
125
|
+
palette: LiveChartPalette,
|
|
126
|
+
fontProp: FontConfig | undefined,
|
|
127
|
+
) {
|
|
128
|
+
const form = referenceLineForm(line);
|
|
129
|
+
const isBand = form === "value-band" || form === "time-band";
|
|
130
|
+
const color = line.color ?? palette.refLine;
|
|
131
|
+
const badge = resolveReferenceBadge(line);
|
|
132
|
+
const badgeHasFontOverride =
|
|
133
|
+
badge != null &&
|
|
134
|
+
(badge.fontSize != null ||
|
|
135
|
+
badge.fontFamily != null ||
|
|
136
|
+
badge.fontWeight != null);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
form,
|
|
140
|
+
isBand,
|
|
141
|
+
color,
|
|
142
|
+
fillColor: line.fillColor ?? color,
|
|
143
|
+
strokeOpacity: Math.max(0, Math.min(1, line.strokeOpacity ?? 1)),
|
|
144
|
+
badge,
|
|
145
|
+
badgeHasFontOverride,
|
|
146
|
+
badgeFontConfig: badgeHasFontOverride
|
|
147
|
+
? {
|
|
148
|
+
...fontProp,
|
|
149
|
+
fontFamily: badge?.fontFamily ?? fontProp?.fontFamily,
|
|
150
|
+
fontSize: badge?.fontSize ?? fontProp?.fontSize,
|
|
151
|
+
fontWeight: badge?.fontWeight ?? fontProp?.fontWeight,
|
|
152
|
+
}
|
|
153
|
+
: fontProp,
|
|
154
|
+
labelColor:
|
|
155
|
+
badge?.textColor ?? line.labelColor ?? line.color ?? palette.refLabel,
|
|
156
|
+
strokeWidth: line.strokeWidth ?? 1,
|
|
157
|
+
intervals: line.intervals ?? [4, 4],
|
|
158
|
+
bandFillOpacity: line.fillOpacity ?? BAND_FILL_OPACITY,
|
|
159
|
+
hasBandBorder: isBand && line.strokeWidth !== undefined,
|
|
160
|
+
badgeBackground: badge?.background ?? palette.tooltipBg,
|
|
161
|
+
badgeBorderColor: badge?.borderColor ?? color,
|
|
162
|
+
badgeBorderWidth: badge?.borderWidth ?? 1,
|
|
163
|
+
badgeRadius: badge?.radius ?? BADGE_PILL_RADIUS,
|
|
164
|
+
badgeOffsetX: badge?.offsetX ?? 0,
|
|
165
|
+
badgeOffsetY: badge?.offsetY ?? 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
120
169
|
function ReferenceLineStaticOverlay({
|
|
121
170
|
engine,
|
|
122
171
|
padding,
|
|
@@ -136,33 +185,30 @@ function ReferenceLineStaticOverlay({
|
|
|
136
185
|
labelRightMargin,
|
|
137
186
|
gridEndGap,
|
|
138
187
|
}: ReferenceLineOverlayProps) {
|
|
139
|
-
const
|
|
140
|
-
|
|
188
|
+
const {
|
|
189
|
+
form,
|
|
190
|
+
isBand,
|
|
191
|
+
color,
|
|
192
|
+
fillColor,
|
|
193
|
+
strokeOpacity,
|
|
194
|
+
badge,
|
|
195
|
+
badgeHasFontOverride,
|
|
196
|
+
badgeFontConfig,
|
|
197
|
+
labelColor,
|
|
198
|
+
strokeWidth,
|
|
199
|
+
intervals,
|
|
200
|
+
bandFillOpacity,
|
|
201
|
+
hasBandBorder,
|
|
202
|
+
badgeBackground,
|
|
203
|
+
badgeBorderColor,
|
|
204
|
+
badgeBorderWidth,
|
|
205
|
+
badgeRadius,
|
|
206
|
+
badgeOffsetX,
|
|
207
|
+
badgeOffsetY,
|
|
208
|
+
} = resolveStaticReferenceLineAppearance(line, palette, fontProp);
|
|
141
209
|
|
|
142
|
-
const color = line.color ?? palette.refLine;
|
|
143
|
-
const fillColor = line.fillColor ?? color;
|
|
144
|
-
const strokeOpacity = Math.max(0, Math.min(1, line.strokeOpacity ?? 1));
|
|
145
|
-
|
|
146
|
-
// Resolved badge appearance (badge config → fallback flat fields → theme).
|
|
147
|
-
const badge = resolveReferenceBadge(line);
|
|
148
|
-
|
|
149
|
-
// Per-badge font (size/family/weight) override; reuses the chart `font` when no
|
|
150
|
-
// badge font knob is set, so plain lines and gutter labels are unchanged. The
|
|
151
|
-
// built font drives pill measurement (via useReferenceLine) and the badge text.
|
|
152
|
-
const badgeHasFontOverride =
|
|
153
|
-
badge != null &&
|
|
154
|
-
(badge.fontSize != null ||
|
|
155
|
-
badge.fontFamily != null ||
|
|
156
|
-
badge.fontWeight != null);
|
|
157
210
|
const badgeFontOverride = useChartSkiaFont(
|
|
158
|
-
|
|
159
|
-
? {
|
|
160
|
-
...fontProp,
|
|
161
|
-
fontFamily: badge?.fontFamily ?? fontProp?.fontFamily,
|
|
162
|
-
fontSize: badge?.fontSize ?? fontProp?.fontSize,
|
|
163
|
-
fontWeight: badge?.fontWeight ?? fontProp?.fontWeight,
|
|
164
|
-
}
|
|
165
|
-
: fontProp,
|
|
211
|
+
badgeFontConfig,
|
|
166
212
|
MONO_FONT_FAMILY,
|
|
167
213
|
palette.labelFontSize,
|
|
168
214
|
);
|
|
@@ -182,27 +228,6 @@ function ReferenceLineStaticOverlay({
|
|
|
182
228
|
font,
|
|
183
229
|
);
|
|
184
230
|
|
|
185
|
-
const labelColor =
|
|
186
|
-
badge?.textColor ?? line.labelColor ?? line.color ?? palette.refLabel;
|
|
187
|
-
const strokeWidth = line.strokeWidth ?? 1;
|
|
188
|
-
const intervals = line.intervals ?? [4, 4];
|
|
189
|
-
|
|
190
|
-
// Band fill + optional dashed border (border only when strokeWidth is set).
|
|
191
|
-
const bandFillOpacity = line.fillOpacity ?? BAND_FILL_OPACITY;
|
|
192
|
-
const hasBandBorder = isBand && line.strokeWidth !== undefined;
|
|
193
|
-
|
|
194
|
-
const badgeBackground = badge?.background ?? palette.tooltipBg;
|
|
195
|
-
const badgeBorderColor = badge?.borderColor ?? color;
|
|
196
|
-
const badgeBorderWidth = badge?.borderWidth ?? 1;
|
|
197
|
-
const badgeRadius = badge?.radius ?? BADGE_PILL_RADIUS;
|
|
198
|
-
// Nudge the whole badge (connector + pill + chevron + icon + label) off its anchor.
|
|
199
|
-
const badgeOffsetX = badge?.offsetX ?? 0;
|
|
200
|
-
const badgeOffsetY = badge?.offsetY ?? 0;
|
|
201
|
-
const badgeTransform =
|
|
202
|
-
badgeOffsetX !== 0 || badgeOffsetY !== 0
|
|
203
|
-
? [{ translateX: badgeOffsetX }, { translateY: badgeOffsetY }]
|
|
204
|
-
: undefined;
|
|
205
|
-
|
|
206
231
|
if (!badgeLayer) {
|
|
207
232
|
return (
|
|
208
233
|
<ReferenceLineBasePass
|
|
@@ -412,7 +437,7 @@ function ReferenceLineBadgePass({
|
|
|
412
437
|
const customTagActive =
|
|
413
438
|
suppressTag || (suppressTagWhenOffAxis && l.offAxis);
|
|
414
439
|
const customWidth = customTagActive
|
|
415
|
-
? customTagWidths?.get()[index] ?? 0
|
|
440
|
+
? (customTagWidths?.get()[index] ?? 0)
|
|
416
441
|
: 0;
|
|
417
442
|
if (customWidth > 0) {
|
|
418
443
|
if (badgePosition === "left") {
|
|
@@ -466,7 +491,9 @@ function ReferenceLineBadgePass({
|
|
|
466
491
|
const grouped = groupHidden ? groupHidden.get()[index] === true : false;
|
|
467
492
|
const customTagActive =
|
|
468
493
|
suppressTag || (suppressTagWhenOffAxis && l.offAxis);
|
|
469
|
-
return !customTagActive && !grouped && l.visible && l.label.length > 0
|
|
494
|
+
return !customTagActive && !grouped && l.visible && l.label.length > 0
|
|
495
|
+
? 1
|
|
496
|
+
: 0;
|
|
470
497
|
});
|
|
471
498
|
const iconOpacity = useDerivedValue(() => {
|
|
472
499
|
const l = layout.get();
|
|
@@ -38,11 +38,13 @@ export function ReferenceLineSeriesOverlay({
|
|
|
38
38
|
badgeLayer: boolean;
|
|
39
39
|
}) {
|
|
40
40
|
const points = line.series ?? [];
|
|
41
|
+
const extendToStart = line.extendToStart ?? true;
|
|
41
42
|
const extendToNow = line.extendToNow ?? true;
|
|
42
43
|
const geometry = useReferenceLineSeries(
|
|
43
44
|
engine,
|
|
44
45
|
padding,
|
|
45
46
|
points,
|
|
47
|
+
extendToStart,
|
|
46
48
|
extendToNow,
|
|
47
49
|
);
|
|
48
50
|
const color = line.color ?? palette.refLine;
|
|
@@ -36,6 +36,8 @@ interface ThresholdMarkerProps {
|
|
|
36
36
|
/** When set, draw the marker as this time-varying threshold polyline (`[x, y, …]`)
|
|
37
37
|
* instead of a horizontal line at `lineY`. */
|
|
38
38
|
seriesPts?: SharedValue<number[]>;
|
|
39
|
+
/** Optional first rendered X for a non-extended series badge. */
|
|
40
|
+
leftAnchorX?: SharedValue<number>;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -126,6 +128,7 @@ export function ThresholdBadgeOverlay({
|
|
|
126
128
|
palette,
|
|
127
129
|
font,
|
|
128
130
|
formatValue,
|
|
131
|
+
leftAnchorX,
|
|
129
132
|
}: ThresholdMarkerProps) {
|
|
130
133
|
const { label, labelPosition, showValue } = cfg;
|
|
131
134
|
const badgeBackground = palette.tooltipBg;
|
|
@@ -155,12 +158,19 @@ export function ThresholdBadgeOverlay({
|
|
|
155
158
|
const pillW = useDerivedValue(
|
|
156
159
|
() => measureFontTextWidth(font, labelText.get()) + BADGE_PAD_X * 2,
|
|
157
160
|
);
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
// A non-extended series' left badge follows its first rendered point, while
|
|
162
|
+
// staying fully inside the plot near the live edge. Other left badges retain
|
|
163
|
+
// the legacy canvas-edge position.
|
|
164
|
+
const pillX = useDerivedValue(() => {
|
|
165
|
+
if (labelPosition === "right") {
|
|
166
|
+
return engine.canvasWidth.get() - padding.right - BADGE_EDGE_INSET - pillW.get();
|
|
167
|
+
}
|
|
168
|
+
if (!leftAnchorX) return BADGE_EDGE_INSET;
|
|
169
|
+
const minX = padding.left + BADGE_EDGE_INSET;
|
|
170
|
+
const maxX =
|
|
171
|
+
engine.canvasWidth.get() - padding.right - BADGE_EDGE_INSET - pillW.get();
|
|
172
|
+
return Math.max(minX, Math.min(leftAnchorX.get(), maxX));
|
|
173
|
+
});
|
|
164
174
|
const pillY = useDerivedValue(
|
|
165
175
|
() => lineY.get() - baselineOffset + fontAscent - BADGE_PAD_Y,
|
|
166
176
|
);
|