Skip to content

packages/core/src/features/viz/inspection/bar.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 { LayoutBox } from "@pibbl/core";
2 import type { BarSeriesProps } from "../lib/bar-series.js";
3 import type { BandCategory } from "../lib/types.js";
4 import type { RenderedSeriesPoint } from "./query-types.js";
5 import { useRenderedRef } from "./owned-ref.js";
6 
7 /** Original range/value/grouped bar with its captured endpoints and local paint bounds.
8  * @see {@link BarRenderedSeries}
9  */
10 export interface PaintedBarHit<D> {
11   /** Caller-owned original record. */
12   readonly datum: D;
13   /** Original zero-based source position. */
14   readonly sourceIndex: number;
15   /** Original discrete category, without numeric coercion. */
16   readonly category: BandCategory;
17   /** Original subgroup when grouping is enabled. */
18   readonly group: BandCategory | undefined;
19   /** Data-space starting endpoint, including an ordinary bar's baseline. */
20   readonly valueStart: number;
21   /** Data-space ending endpoint. */
22   readonly valueEnd: number;
23   /** Logical rectangle bounds before world transforms. */
24   readonly boundsLocal: Readonly<LayoutBox>;
25 }
26 /** @internal Captured rectangle paint flags used only for geometric lookup. */
27 export interface CapturedBarMark<D> extends PaintedBarHit<D> {
28   /** Whether this rectangle paints its interior. */
29   readonly fill: boolean;
30   /** Painted stroke width in local logical units. */
31   readonly strokeWidthLocal: number;
32 }
33 /** A borrowed committed categorical bar generation; contains no numeric category mapping.
34  * @see {@link PaintedBarHit}
35  */
36 export interface BarRenderedSeries<D> {
37   /** Original records and geometry in source paint order. */
38   readonly marks: readonly CapturedBarMark<D>[];
39   /** Throws after replacement or disposal. */
40   assertLive(): void;
41   /** Converts root logical units into this generation's local geometry.
42    * @param point - Root-logical Canvas point.
43    * @returns Local point or undefined for a singular transform.
44    */
45   localAtRoot(
46     point: Readonly<RenderedSeriesPoint>,
47   ): Readonly<RenderedSeriesPoint> | undefined;
48   /** Tests a local point against every committed clip/layer bound.
49    * @param point - Local-logical Canvas point.
50    * @returns Whether the point is inside every clip and layer bound.
51    */
52   anchorInsideClips(point: Readonly<RenderedSeriesPoint>): boolean;
53 }
54 /** Mount-owned optional categorical-bar capture.
55  * @see {@link BarRenderedSeries}
56  */
57 export interface BarInspectionRef<D> {
58   /** Returns the last successful generation.
59    * @returns Borrowed bar geometry or undefined before paint/after disposal.
60    */
61   rendered(): BarRenderedSeries<D> | undefined;
62   /** @internal Records candidate values and paint geometry without executing a search.
63    * @param input - Source-ordered captured rectangle marks.
64    */
65   capture(input: readonly CapturedBarMark<D>[]): void;
66 }
67 
68 /**
69  * Owns opt-in capture for ordinary, range or grouped categorical bars.
70  * @param _inputs - Bar inputs used for datum inference; no data is read or subscribed here.
71  * @returns A stable ref to pass to one BarSeries inspection prop; captured endpoints share one fixed contract.
72  * @see {@link BarInspectionRef}
73  */
74 export function useBarInspection<D>(
75   _inputs: Pick<BarSeriesProps<D>, "data" | "category">,
76 ): BarInspectionRef<D> {
77   return useRenderedRef<readonly CapturedBarMark<D>[], BarRenderedSeries<D>>(
78     "barSeriesInspection",
79     (marks, geometry) =>
80       Object.freeze({
81         marks: Object.freeze(marks.slice()),
82         assertLive: geometry.assertLive,
83         localAtRoot: geometry.localAtRoot,
84         anchorInsideClips: geometry.anchorInsideClips,
85       }),
86   );
87 }
88 
89 /**
90  * Finds the last-painted bar covering a root-logical point in this explicit series.
91  * @param rendered - Borrowed committed categorical bar values and rectangle geometry.
92  * @param options - Root-logical query point; respects every clip, fill/stroke and source order.
93  * @returns Original record, category/group, endpoints and local bounds, or undefined for gaps and singular transforms.
94  * @see {@link PaintedBarHit}
95  */
96 export function paintedBarAt<D>(
97   rendered: BarRenderedSeries<D>,
98   options: { rootPoint: Readonly<RenderedSeriesPoint> },
99 ): PaintedBarHit<D> | undefined {
100   rendered.assertLive();
101   if (
102     !Number.isFinite(options.rootPoint.x) ||
103     !Number.isFinite(options.rootPoint.y)
104   )
105     throw new RangeError("paintedBarAt rootPoint must be finite.");
106   const point = rendered.localAtRoot(options.rootPoint);
107   if (!point || !rendered.anchorInsideClips(point)) return undefined;
108   for (let index = rendered.marks.length - 1; index >= 0; index--) {
109     const mark = rendered.marks[index],
110       b = mark.boundsLocal,
111       half = mark.strokeWidthLocal / 2;
112     const inside =
113       point.x >= b.x &&
114       point.x <= b.x + b.width &&
115       point.y >= b.y &&
116       point.y <= b.y + b.height;
117     const outline =
118       half > 0 &&
119       point.x >= b.x - half &&
120       point.x <= b.x + b.width + half &&
121       point.y >= b.y - half &&
122       point.y <= b.y + b.height + half &&
123       !(
124         point.x > b.x + half &&
125         point.x < b.x + b.width - half &&
126         point.y > b.y + half &&
127         point.y < b.y + b.height - half
128       );
129     if (!((mark.fill && inside) || outline)) continue;
130     const {
131       datum,
132       sourceIndex,
133       category,
134       group,
135       valueStart,
136       valueEnd,
137       boundsLocal,
138     } = mark;
139     return Object.freeze({
140       datum,
141       sourceIndex,
142       category,
143       group,
144       valueStart,
145       valueEnd,
146       boundsLocal,
147     });
148   }
149   return undefined;
150 }
151 

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