react-native-livechart 4.20.0 → 4.21.1

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.
Files changed (111) hide show
  1. package/dist/components/AxisLabelOverlay.d.ts +5 -1
  2. package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
  3. package/dist/components/CrosshairOverlay.d.ts +4 -2
  4. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  5. package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
  6. package/dist/components/CustomThresholdBadgeOverlay.d.ts +18 -0
  7. package/dist/components/CustomThresholdBadgeOverlay.d.ts.map +1 -0
  8. package/dist/components/CustomTooltipOverlay.d.ts +4 -2
  9. package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
  10. package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
  11. package/dist/components/ExtremaConnectorOverlay.d.ts +5 -1
  12. package/dist/components/ExtremaConnectorOverlay.d.ts.map +1 -1
  13. package/dist/components/LiveChart.d.ts.map +1 -1
  14. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  15. package/dist/components/MarkerOverlay.d.ts +4 -2
  16. package/dist/components/MarkerOverlay.d.ts.map +1 -1
  17. package/dist/components/ReferenceLineOverlay.d.ts +10 -5
  18. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
  19. package/dist/components/ReferenceLineSeriesOverlay.d.ts +19 -0
  20. package/dist/components/ReferenceLineSeriesOverlay.d.ts.map +1 -0
  21. package/dist/core/chartDataPresence.d.ts +8 -0
  22. package/dist/core/chartDataPresence.d.ts.map +1 -0
  23. package/dist/core/liveChartEngineTick.d.ts +6 -1
  24. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  25. package/dist/core/resolveConfig.d.ts +32 -1
  26. package/dist/core/resolveConfig.d.ts.map +1 -1
  27. package/dist/core/useLiveChartEngine.d.ts +9 -1
  28. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  29. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  30. package/dist/draw/candle.d.ts +3 -1
  31. package/dist/draw/candle.d.ts.map +1 -1
  32. package/dist/draw/candleGap.d.ts +14 -0
  33. package/dist/draw/candleGap.d.ts.map +1 -0
  34. package/dist/draw/lineGap.d.ts +25 -0
  35. package/dist/draw/lineGap.d.ts.map +1 -0
  36. package/dist/draw/markerAtlas.d.ts +3 -2
  37. package/dist/draw/markerAtlas.d.ts.map +1 -1
  38. package/dist/hooks/crosshairShared.d.ts +7 -3
  39. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  40. package/dist/hooks/useCandleGapPaths.d.ts +12 -0
  41. package/dist/hooks/useCandleGapPaths.d.ts.map +1 -0
  42. package/dist/hooks/useChartPaths.d.ts +4 -1
  43. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  44. package/dist/hooks/useChartReveal.d.ts +1 -1
  45. package/dist/hooks/useChartReveal.d.ts.map +1 -1
  46. package/dist/hooks/useCrosshair.d.ts +9 -5
  47. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  48. package/dist/hooks/useDegen.d.ts.map +1 -1
  49. package/dist/hooks/useLineGapPaths.d.ts +10 -0
  50. package/dist/hooks/useLineGapPaths.d.ts.map +1 -0
  51. package/dist/hooks/useLiveChartHasData.d.ts +2 -2
  52. package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
  53. package/dist/hooks/useReferenceLine.d.ts +4 -3
  54. package/dist/hooks/useReferenceLine.d.ts.map +1 -1
  55. package/dist/hooks/useReferenceLineSeries.d.ts +23 -0
  56. package/dist/hooks/useReferenceLineSeries.d.ts.map +1 -0
  57. package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
  58. package/dist/hooks/useThreshold.d.ts +13 -13
  59. package/dist/hooks/useThreshold.d.ts.map +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/math/candleGaps.d.ts +18 -0
  63. package/dist/math/candleGaps.d.ts.map +1 -0
  64. package/dist/math/referenceLineSeries.d.ts +12 -0
  65. package/dist/math/referenceLineSeries.d.ts.map +1 -0
  66. package/dist/math/referenceLines.d.ts +5 -5
  67. package/dist/math/referenceLines.d.ts.map +1 -1
  68. package/dist/math/spline.d.ts +5 -1
  69. package/dist/math/spline.d.ts.map +1 -1
  70. package/dist/types.d.ts +191 -17
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/components/AxisLabelOverlay.tsx +162 -81
  74. package/src/components/CrosshairOverlay.tsx +394 -147
  75. package/src/components/CustomMarkerOverlay.tsx +9 -6
  76. package/src/components/CustomThresholdBadgeOverlay.tsx +74 -0
  77. package/src/components/CustomTooltipOverlay.tsx +7 -3
  78. package/src/components/DegenParticlesOverlay.tsx +34 -23
  79. package/src/components/ExtremaConnectorOverlay.tsx +43 -3
  80. package/src/components/LiveChart.tsx +1918 -974
  81. package/src/components/LiveChartSeries.tsx +445 -296
  82. package/src/components/MarkerOverlay.tsx +215 -156
  83. package/src/components/ReferenceLineOverlay.tsx +113 -55
  84. package/src/components/ReferenceLineSeriesOverlay.tsx +141 -0
  85. package/src/core/chartDataPresence.ts +24 -0
  86. package/src/core/liveChartEngineTick.ts +34 -1
  87. package/src/core/resolveConfig.ts +186 -0
  88. package/src/core/useLiveChartEngine.ts +36 -1
  89. package/src/core/useLiveChartSeriesEngine.ts +2 -0
  90. package/src/draw/candle.ts +1 -1
  91. package/src/draw/candleGap.ts +132 -0
  92. package/src/draw/lineGap.ts +180 -0
  93. package/src/draw/markerAtlas.ts +8 -3
  94. package/src/hooks/crosshairShared.ts +92 -11
  95. package/src/hooks/useCandleGapPaths.ts +88 -0
  96. package/src/hooks/useChartPaths.ts +81 -22
  97. package/src/hooks/useChartReveal.ts +1 -1
  98. package/src/hooks/useCrosshair.ts +178 -69
  99. package/src/hooks/useDegen.ts +82 -53
  100. package/src/hooks/useLineGapPaths.ts +76 -0
  101. package/src/hooks/useLiveChartHasData.ts +8 -4
  102. package/src/hooks/useReferenceLine.ts +6 -5
  103. package/src/hooks/useReferenceLineSeries.ts +101 -0
  104. package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
  105. package/src/hooks/useThreshold.ts +31 -22
  106. package/src/index.ts +15 -0
  107. package/src/math/candleGaps.ts +120 -0
  108. package/src/math/referenceLineSeries.ts +83 -0
  109. package/src/math/referenceLines.ts +34 -6
  110. package/src/math/spline.ts +15 -5
  111. package/src/types.ts +202 -17
@@ -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(invScale * mul, 0, cx - (bg.w * mul) / 2, cy - (bg.h * mul) / 2),
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
- export function MarkerOverlay({
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[]>(() => markers.get().slice());
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 groupBadgeKey = groupBadgeCfg
322
- ? `${groupBadgeCfg.icon ?? ""}|${groupBadgeCfg.color ?? ""}|${groupBadgeCfg.pill ? 1 : 0}|${groupBadgeCfg.size ?? ""}|${groupBadgeCfg.letterSpacing ?? ""}`
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
- palette,
373
+ atlasPalette,
329
374
  font,
330
375
  dpr,
331
376
  clusterStacked,
332
- groupBadgeCfg,
377
+ atlasGroupBadge,
333
378
  ),
334
- // eslint-disable-next-line react-hooks/exhaustive-deps -- appearanceKey/paletteKey/groupBadgeKey capture the inputs that change cell pixels
335
- [appearanceKey, paletteKey, font, dpr, clusterStacked, groupBadgeKey, groupBadgeImage],
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
- const ms = markers.get();
373
- const proj = projRef.current!;
374
- proj.tick = !proj.tick;
375
- const buf = proj.tick ? proj.a : proj.b;
376
- projectMarkers(ms, buf, {
377
- canvasWidth: engine.canvasWidth.get(),
378
- canvasHeight: engine.canvasHeight.get(),
379
- padTop: padding.top,
380
- padBottom: padding.bottom,
381
- padLeft: padding.left,
382
- padRight: padding.right,
383
- timestamp: engine.timestamp.get(),
384
- displayWindow: engine.displayWindow.get(),
385
- displayMin: engine.displayMin.get(),
386
- displayMax: engine.displayMax.get(),
387
- series: series?.get(),
388
- lineData: lineData?.get(),
389
- lineLinear,
390
- });
391
- clusterMarkers(ms, buf, {
392
- config: cluster,
393
- minY: 0,
394
- maxY: engine.canvasHeight.get(),
395
- });
396
- const atlasFrames = atlasFrameRef.current!;
397
- atlasFrames.tick = !atlasFrames.tick;
398
- const frame = atlasFrames.tick ? atlasFrames.a : atlasFrames.b;
399
- const transforms = frame.transforms;
400
- const sprites = frame.sprites;
401
- transforms.length = 0;
402
- sprites.length = 0;
403
- for (let i = 0; i < ms.length; i++) {
404
- const pt = buf[i];
405
- // Collapsed-cluster members fold into their representative's badge.
406
- if (!pt.visible || pt.hidden) continue;
407
- const m = ms[i];
408
- if (isConnectorMarker(m)) continue;
409
- // Custom-rendered markers are floated as RN views, not drawn here.
410
- if (customIds[m.id]) continue;
411
- // Collapsed cluster. By default a round count badge; with
412
- // `groupBadge: "marker"` the representative marker's own glyph, or with a
413
- // `MarkerGroupBadge` object a dedicated baked badge — either optionally
414
- // carrying a corner count, all within this one `drawAtlas`.
415
- if (pt.isGrouped) {
416
- const repColor = m.color ?? defaultMarkerColor(m.kind, palette);
417
- const gb = cluster.groupBadge;
418
- // Pick the non-count glyph cell: a dedicated group badge (object form)
419
- // or the representative marker's own appearance.
420
- const glyphCell =
421
- typeof gb === "object" && gb !== null
422
- ? cells[GROUP_BADGE_SIG]
423
- : gb === "marker"
424
- ? cells[markerAppearanceSig(m)]
425
- : undefined;
426
- if (glyphCell) {
427
- transforms.push(
428
- Skia.RSXform(
429
- invScale,
430
- 0,
431
- pt.x - glyphCell.w / 2,
432
- pt.y - glyphCell.h / 2,
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
- const cell = cells[markerAppearanceSig(m)];
471
- if (!cell) continue;
472
- // Center the cell on the projected point. The cell's source rect is in
473
- // device pixels, so scale by 1/dpr to land it at its logical size.
474
- transforms.push(
475
- Skia.RSXform(invScale, 0, pt.x - cell.w / 2, pt.y - cell.h / 2),
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
- sprites.push(cell.rect);
519
+ continue;
478
520
  }
479
- return frame;
480
- },
481
- [
482
- cells,
483
- customIds,
484
- invScale,
485
- digitWidths,
486
- markers,
487
- engine,
488
- padding,
489
- palette,
490
- series,
491
- lineData,
492
- lineLinear,
493
- cluster,
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 && (