packages/core/src/features/viz/inspection/bar.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
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 version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision dd39ce2. ALPHA — NOT FOR PRODUCTION USE.