Skip to content

packages/core/src/features/viz/inspection/painted-point.ts

Read as Markdown

This is the source snapshot used to build these API details. View this revision on GitHub.

Back to reference

1 import type { PointRenderedSeries } from "./capture-types.js";
2 import type { RecordedNumericHit, RenderedSeriesPoint } from "./query-types.js";
3 
4 /** The original record and local geometry of a discrete circle/ring covering a query point.
5  * @see {@link RecordedNumericHit}
6  */
7 export interface PaintedPointHit<D> extends RecordedNumericHit<D> {
8   /** Circle radius before world transforms, in local logical units. */
9   readonly radiusLocal: number;
10   /** Painted stroke width before world transforms, in local logical units. */
11   readonly strokeWidthLocal: number;
12 }
13 
14 /**
15  * Selects the last-painted circle/ring covering a root-logical position in this series.
16  * This is geometric fill/stroke coverage, not alpha readback or a Pibbl event target.
17  * @param rendered - Borrowed point geometry from the last successful paint.
18  * @param options - Explicit root-logical query point; CSS/device pixels are unsupported.
19  * @returns Original datum/index and captured geometry, or undefined for clips, holes, omissions or singular transforms.
20  * @see {@link PaintedPointHit}
21  */
22 export function paintedPointAt<D>(
23   rendered: PointRenderedSeries<D>,
24   options: { rootPoint: Readonly<RenderedSeriesPoint> },
25 ): PaintedPointHit<D> | undefined {
26   rendered.assertLive();
27   const { x, y } = options.rootPoint;
28   if (!Number.isFinite(x) || !Number.isFinite(y))
29     throw new RangeError("paintedPointAt rootPoint must be finite.");
30   const local = rendered.localAtRoot(options.rootPoint);
31   if (!local || !rendered.anchorInsideClips(local)) return undefined;
32   for (let index = rendered.marks.length - 1; index >= 0; index--) {
33     const mark = rendered.marks[index];
34     const radius = mark.radiusLocal ?? 0,
35       stroke = mark.strokeWidthLocal ?? 0;
36     if (radius === 0 || (!mark.fill && stroke === 0)) continue;
37     const distance = Math.hypot(
38       local.x - mark.anchorLocal.x,
39       local.y - mark.anchorLocal.y,
40     );
41     if (
42       distance > radius + stroke / 2 ||
43       (!mark.fill && distance < Math.max(0, radius - stroke / 2))
44     )
45       continue;
46     return Object.freeze({
47       ...rendered.observations[index],
48       radiusLocal: radius,
49       strokeWidthLocal: stroke,
50     });
51   }
52   return undefined;
53 }
54 

Documentation built with @pibbl/core 0.0.2, revision dd39ce2. ALPHA — NOT FOR PRODUCTION USE.