@pibbl/core 0.0.3-alpha.85.1 → 0.0.3-alpha.90.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 (59) hide show
  1. package/dist/features/viz/index.d.ts +13 -0
  2. package/dist/features/viz/index.js +57 -0
  3. package/dist/features/viz/index.js.map +2 -2
  4. package/dist/features/viz/lib/graph/bipartite.d.ts +14 -0
  5. package/dist/features/viz/lib/graph/bipartite.js +250 -0
  6. package/dist/features/viz/lib/graph/bipartite.js.map +7 -0
  7. package/dist/features/viz/lib/graph/chord.d.ts +44 -0
  8. package/dist/features/viz/lib/graph/chord.js +348 -0
  9. package/dist/features/viz/lib/graph/chord.js.map +7 -0
  10. package/dist/features/viz/lib/graph/dependency.d.ts +26 -0
  11. package/dist/features/viz/lib/graph/dependency.js +141 -0
  12. package/dist/features/viz/lib/graph/dependency.js.map +7 -0
  13. package/dist/features/viz/lib/graph/force.d.ts +28 -0
  14. package/dist/features/viz/lib/graph/force.js +580 -0
  15. package/dist/features/viz/lib/graph/force.js.map +7 -0
  16. package/dist/features/viz/lib/graph/hierarchy.d.ts +21 -0
  17. package/dist/features/viz/lib/graph/hierarchy.js +112 -0
  18. package/dist/features/viz/lib/graph/hierarchy.js.map +7 -0
  19. package/dist/features/viz/lib/graph/icicle.d.ts +16 -0
  20. package/dist/features/viz/lib/graph/icicle.js +153 -0
  21. package/dist/features/viz/lib/graph/icicle.js.map +7 -0
  22. package/dist/features/viz/lib/graph/matrix.d.ts +30 -0
  23. package/dist/features/viz/lib/graph/matrix.js +243 -0
  24. package/dist/features/viz/lib/graph/matrix.js.map +7 -0
  25. package/dist/features/viz/lib/graph/neighborhood.d.ts +11 -0
  26. package/dist/features/viz/lib/graph/neighborhood.js +281 -0
  27. package/dist/features/viz/lib/graph/neighborhood.js.map +7 -0
  28. package/dist/features/viz/lib/graph/routes.d.ts +35 -0
  29. package/dist/features/viz/lib/graph/routes.js +102 -0
  30. package/dist/features/viz/lib/graph/routes.js.map +7 -0
  31. package/dist/features/viz/lib/graph/sankey.d.ts +44 -0
  32. package/dist/features/viz/lib/graph/sankey.js +341 -0
  33. package/dist/features/viz/lib/graph/sankey.js.map +7 -0
  34. package/dist/features/viz/lib/graph/shared.d.ts +59 -0
  35. package/dist/features/viz/lib/graph/shared.js +296 -0
  36. package/dist/features/viz/lib/graph/shared.js.map +7 -0
  37. package/dist/features/viz/lib/graph/tree.d.ts +11 -0
  38. package/dist/features/viz/lib/graph/tree.js +232 -0
  39. package/dist/features/viz/lib/graph/tree.js.map +7 -0
  40. package/dist/features/viz/lib/graph/treemap.d.ts +20 -0
  41. package/dist/features/viz/lib/graph/treemap.js +227 -0
  42. package/dist/features/viz/lib/graph/treemap.js.map +7 -0
  43. package/dist/features/viz/lib/graph/types.d.ts +1097 -0
  44. package/dist/filters/backdrop-contrast-gpu.d.ts +5 -0
  45. package/dist/filters/backdrop-contrast-gpu.js +33 -0
  46. package/dist/filters/backdrop-contrast-gpu.js.map +7 -0
  47. package/dist/filters/backdrop-contrast.d.ts +21 -0
  48. package/dist/filters/backdrop-contrast.js +61 -0
  49. package/dist/filters/backdrop-contrast.js.map +7 -0
  50. package/dist/filters/filter-executor.d.ts +11 -4
  51. package/dist/filters/filter-executor.js +104 -49
  52. package/dist/filters/filter-executor.js.map +2 -2
  53. package/dist/filters/render-backdrop-contrast-boundary.d.ts +31 -0
  54. package/dist/filters/render-backdrop-contrast-boundary.js +116 -0
  55. package/dist/filters/render-backdrop-contrast-boundary.js.map +7 -0
  56. package/dist/filters.d.ts +9 -7
  57. package/dist/filters.js +2 -0
  58. package/dist/filters.js.map +2 -2
  59. package/package.json +2 -2
@@ -0,0 +1,296 @@
1
+ import { jsx, jsxs } from "@pibbl/core/jsx-runtime";
2
+ import {
3
+ Group,
4
+ Rectangle,
5
+ SvgPath,
6
+ Text,
7
+ resolveSignalValue,
8
+ resolveBox,
9
+ useLayoutBox
10
+ } from "@pibbl/core";
11
+ function readAccessor(datum, accessor) {
12
+ return typeof accessor === "function" ? accessor(datum) : datum[accessor];
13
+ }
14
+ function validateBounds(bounds) {
15
+ for (const key of ["x", "y", "width", "height"]) {
16
+ if (!Number.isFinite(bounds[key]) || (key === "width" || key === "height") && bounds[key] < 0)
17
+ throw new RangeError(
18
+ `Graph bounds.${key} must be finite${key === "width" || key === "height" ? " and nonnegative" : ""}.`
19
+ );
20
+ }
21
+ if (!Number.isFinite(bounds.x + bounds.width) || !Number.isFinite(bounds.y + bounds.height))
22
+ throw new RangeError("Graph bounds overflow.");
23
+ return bounds;
24
+ }
25
+ function validKey(key) {
26
+ if (typeof key !== "string" && typeof key !== "number" || typeof key === "number" && !Number.isFinite(key))
27
+ throw new TypeError("Graph keys must be strings or finite numbers.");
28
+ return key;
29
+ }
30
+ function useGraphFrame(input) {
31
+ const allocation = useLayoutBox();
32
+ const authored = resolveSignalValue(input) ?? {};
33
+ const merged = { ...authored };
34
+ for (const rule of authored.when ?? []) {
35
+ const q = rule.query;
36
+ if ((q.minWidth === void 0 || allocation.width >= q.minWidth) && (q.maxWidth === void 0 || allocation.width <= q.maxWidth) && (q.minHeight === void 0 || allocation.height >= q.minHeight) && (q.maxHeight === void 0 || allocation.height <= q.maxHeight))
37
+ Object.assign(merged, rule.style);
38
+ }
39
+ delete merged.when;
40
+ for (const key of Object.keys(merged))
41
+ merged[key] = resolveSignalValue(merged[key]);
42
+ if (merged.width === "auto" || merged.height === "auto")
43
+ throw new RangeError(
44
+ "Graph frames require definite width and height or a finite parent allocation."
45
+ );
46
+ const style = merged;
47
+ const box = resolveBox(
48
+ {
49
+ ...style,
50
+ width: style.width ?? allocation.width,
51
+ height: style.height ?? allocation.height
52
+ },
53
+ {
54
+ minWidth: 0,
55
+ minHeight: 0,
56
+ maxWidth: allocation.width,
57
+ maxHeight: allocation.height
58
+ }
59
+ );
60
+ const bounds = validateBounds({
61
+ x: 0,
62
+ y: 0,
63
+ width: box.borderBox.width,
64
+ height: box.borderBox.height
65
+ });
66
+ const groupStyle = { ...style, width: bounds.width, height: bounds.height };
67
+ for (const key of [
68
+ "rankGap",
69
+ "nodeGap",
70
+ "levelGap",
71
+ "siblingGap",
72
+ "cellGap",
73
+ "rowLabelWidth",
74
+ "columnLabelHeight",
75
+ "nodeWidth",
76
+ "columnGap",
77
+ "headerHeight",
78
+ "ringGap",
79
+ "bandWidth",
80
+ "padAngle",
81
+ "startAngle"
82
+ ])
83
+ delete groupStyle[key];
84
+ return { bounds, style, groupStyle };
85
+ }
86
+ function resolveMarkStyle(geometry, input) {
87
+ return resolveSignalValue(
88
+ typeof input === "function" ? input(geometry) : input
89
+ );
90
+ }
91
+ function graphNodeGeometry(datum, key, bounds, label = String(key), detail) {
92
+ return {
93
+ key,
94
+ datum,
95
+ bounds,
96
+ label: {
97
+ text: label,
98
+ bounds: {
99
+ x: bounds.x + 10,
100
+ y: bounds.y + 4,
101
+ width: Math.max(0, bounds.width - 20),
102
+ height: detail ? bounds.height / 2 : Math.max(0, bounds.height - 8)
103
+ },
104
+ rotationDegrees: 0
105
+ },
106
+ detail: detail === void 0 ? null : {
107
+ text: detail,
108
+ bounds: {
109
+ x: bounds.x + 10,
110
+ y: bounds.y + bounds.height / 2,
111
+ width: Math.max(0, bounds.width - 20),
112
+ height: bounds.height / 2 - 4
113
+ },
114
+ rotationDegrees: 0
115
+ }
116
+ };
117
+ }
118
+ function defaultGraphNodeStyle(_node) {
119
+ return {
120
+ fill: "#edf5f2",
121
+ stroke: "#8eb6aa",
122
+ strokeWidth: 1,
123
+ cornerRadius: 7
124
+ };
125
+ }
126
+ function defaultGraphEdgeStyle(_edge) {
127
+ return { stroke: "#91a9a3", strokeWidth: 1.5 };
128
+ }
129
+ function defaultGraphLabelStyle(_label) {
130
+ return { fill: "#203d39", font: "13px sans-serif", textBaseline: "middle" };
131
+ }
132
+ function GraphNode({
133
+ node,
134
+ style,
135
+ children,
136
+ ...events
137
+ }) {
138
+ const { x, y, width, height } = node.bounds;
139
+ const {
140
+ keyboardFocusable,
141
+ keyboardNavigationBounds,
142
+ pointerEvents,
143
+ ...handlers
144
+ } = events;
145
+ return /* @__PURE__ */ jsxs(Group, { ...handlers, style: { translateX: x, translateY: y }, children: [
146
+ /* @__PURE__ */ jsx(
147
+ Rectangle,
148
+ {
149
+ keyboardFocusable: keyboardFocusable ?? Boolean(handlers.onClick || handlers.onKeyDown || handlers.onKeyUp),
150
+ keyboardNavigationBounds,
151
+ pointerEvents,
152
+ style: { ...resolveSignalValue(style), width, height }
153
+ }
154
+ ),
155
+ children
156
+ ] });
157
+ }
158
+ function GraphEdge({
159
+ edge,
160
+ style,
161
+ ...events
162
+ }) {
163
+ const paint = resolveSignalValue(style);
164
+ const {
165
+ keyboardFocusable,
166
+ keyboardNavigationBounds,
167
+ pointerEvents,
168
+ ...handlers
169
+ } = events;
170
+ return /* @__PURE__ */ jsxs(Group, { ...handlers, children: [
171
+ /* @__PURE__ */ jsx(
172
+ SvgPath,
173
+ {
174
+ keyboardFocusable: keyboardFocusable ?? Boolean(handlers.onClick || handlers.onKeyDown || handlers.onKeyUp),
175
+ keyboardNavigationBounds,
176
+ pointerEvents,
177
+ style: { ...paint, d: edge.path }
178
+ }
179
+ ),
180
+ edge.arrowPath && /* @__PURE__ */ jsx(
181
+ SvgPath,
182
+ {
183
+ pointerEvents,
184
+ style: {
185
+ ...paint,
186
+ fill: paint?.stroke ?? "#91a9a3",
187
+ d: edge.arrowPath
188
+ }
189
+ }
190
+ )
191
+ ] });
192
+ }
193
+ function GraphLabel({ label, style }) {
194
+ const { x, y, width, height } = label.bounds;
195
+ const paint = resolveSignalValue(style);
196
+ const align = resolveSignalValue(paint?.textAlign);
197
+ const anchor = align === "center" ? width / 2 : align === "right" || align === "end" ? width : 0;
198
+ return /* @__PURE__ */ jsx(
199
+ Group,
200
+ {
201
+ style: {
202
+ translateX: x + anchor,
203
+ translateY: y + height / 2,
204
+ rotationDegrees: label.rotationDegrees
205
+ },
206
+ children: /* @__PURE__ */ jsx(
207
+ Text,
208
+ {
209
+ pointerEvents: "none",
210
+ style: { textBaseline: "middle", fit: "ellipsis", ...paint, width },
211
+ children: label.text
212
+ }
213
+ )
214
+ }
215
+ );
216
+ }
217
+ function GraphView(props) {
218
+ const layout = resolveSignalValue(props.layout);
219
+ return /* @__PURE__ */ jsxs(Group, { style: props.style, children: [
220
+ layout.edges.map((edge) => /* @__PURE__ */ jsx(
221
+ Group,
222
+ {
223
+ onClick: (event) => props.onEdgeClick?.(edge, event),
224
+ children: props.renderEdge ? props.renderEdge(edge) : /* @__PURE__ */ jsx(
225
+ GraphEdge,
226
+ {
227
+ edge,
228
+ keyboardFocusable: Boolean(props.onEdgeClick),
229
+ style: {
230
+ ...defaultGraphEdgeStyle(edge),
231
+ ...resolveMarkStyle(edge, props.edgeStyle)
232
+ }
233
+ }
234
+ )
235
+ },
236
+ `e:${typeof edge.key}:${edge.key}`
237
+ )),
238
+ layout.nodes.map((node) => /* @__PURE__ */ jsx(
239
+ Group,
240
+ {
241
+ onClick: (event) => props.onNodeClick?.(node, event),
242
+ children: props.renderNode ? props.renderNode(node) : /* @__PURE__ */ jsxs(Group, { children: [
243
+ /* @__PURE__ */ jsx(
244
+ GraphNode,
245
+ {
246
+ node,
247
+ keyboardFocusable: Boolean(props.onNodeClick),
248
+ style: {
249
+ ...defaultGraphNodeStyle(node),
250
+ ...resolveMarkStyle(node, props.nodeStyle)
251
+ }
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsx(
255
+ GraphLabel,
256
+ {
257
+ label: node.label,
258
+ style: {
259
+ ...defaultGraphLabelStyle(node.label),
260
+ ...resolveMarkStyle(node, props.labelStyle)
261
+ }
262
+ }
263
+ ),
264
+ node.detail && /* @__PURE__ */ jsx(
265
+ GraphLabel,
266
+ {
267
+ label: node.detail,
268
+ style: {
269
+ ...defaultGraphLabelStyle(node.detail),
270
+ font: "11px sans-serif",
271
+ ...resolveMarkStyle(node, props.labelStyle)
272
+ }
273
+ }
274
+ )
275
+ ] })
276
+ },
277
+ `n:${typeof node.key}:${node.key}`
278
+ ))
279
+ ] });
280
+ }
281
+ export {
282
+ GraphEdge,
283
+ GraphLabel,
284
+ GraphNode,
285
+ GraphView,
286
+ defaultGraphEdgeStyle,
287
+ defaultGraphLabelStyle,
288
+ defaultGraphNodeStyle,
289
+ graphNodeGeometry,
290
+ readAccessor,
291
+ resolveMarkStyle,
292
+ useGraphFrame,
293
+ validKey,
294
+ validateBounds
295
+ };
296
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/features/viz/lib/graph/shared.tsx"],
4
+ "sourcesContent": ["import {\n Group,\n Rectangle,\n SvgPath,\n Text,\n resolveSignalValue,\n resolveBox,\n useLayoutBox,\n} from \"@pibbl/core\";\nimport type {\n GStyle,\n SystemStyle,\n PibblResponsiveStyle,\n PibblNode,\n} from \"@pibbl/core\";\nimport type {\n Accessor,\n Bounds,\n Style,\n MarkStyle,\n GraphNodeGeometry,\n GraphNodeProps,\n GraphEdgeGeometry,\n GraphEdgeProps,\n GraphLabelProps,\n GraphViewProps,\n LabelGeometry,\n NodeStyle,\n EdgeStyle,\n LabelStyle,\n VizKey,\n} from \"./types.js\";\n\n/** Read an application's typed property or accessor without normalizing away its record. */\nexport function readAccessor<D, V>(datum: D, accessor: Accessor<D, V>): V {\n return typeof accessor === \"function\"\n ? accessor(datum)\n : (datum[accessor] as V);\n}\n/** Validate a finite rectangle in local logical units. */\nexport function validateBounds(bounds: Bounds): Bounds {\n for (const key of [\"x\", \"y\", \"width\", \"height\"] as const) {\n if (\n !Number.isFinite(bounds[key]) ||\n ((key === \"width\" || key === \"height\") && bounds[key] < 0)\n )\n throw new RangeError(\n `Graph bounds.${key} must be finite${key === \"width\" || key === \"height\" ? \" and nonnegative\" : \"\"}.`,\n );\n }\n if (\n !Number.isFinite(bounds.x + bounds.width) ||\n !Number.isFinite(bounds.y + bounds.height)\n )\n throw new RangeError(\"Graph bounds overflow.\");\n return bounds;\n}\nexport function validKey(key: VizKey): VizKey {\n if (\n (typeof key !== \"string\" && typeof key !== \"number\") ||\n (typeof key === \"number\" && !Number.isFinite(key))\n )\n throw new TypeError(\"Graph keys must be strings or finite numbers.\");\n return key;\n}\n/** Resolve ordinary responsive frame style through public sizing and allocation. */\nexport function useGraphFrame<S extends GStyle>(\n input?: Style<S>,\n): { bounds: Bounds; style: S; groupStyle: GStyle } {\n const allocation = useLayoutBox();\n const authored = (resolveSignalValue(input) ?? {}) as PibblResponsiveStyle<S>;\n const merged = { ...authored } as Record<string, unknown>;\n for (const rule of authored.when ?? []) {\n const q = rule.query;\n if (\n (q.minWidth === undefined || allocation.width >= q.minWidth) &&\n (q.maxWidth === undefined || allocation.width <= q.maxWidth) &&\n (q.minHeight === undefined || allocation.height >= q.minHeight) &&\n (q.maxHeight === undefined || allocation.height <= q.maxHeight)\n )\n Object.assign(merged, rule.style);\n }\n delete merged.when;\n for (const key of Object.keys(merged))\n merged[key] = resolveSignalValue(merged[key]);\n if (merged.width === \"auto\" || merged.height === \"auto\")\n throw new RangeError(\n \"Graph frames require definite width and height or a finite parent allocation.\",\n );\n const style = merged as S;\n const box = resolveBox(\n {\n ...style,\n width: style.width ?? allocation.width,\n height: style.height ?? allocation.height,\n },\n {\n minWidth: 0,\n minHeight: 0,\n maxWidth: allocation.width,\n maxHeight: allocation.height,\n },\n );\n const bounds = validateBounds({\n x: 0,\n y: 0,\n width: box.borderBox.width,\n height: box.borderBox.height,\n });\n const groupStyle = { ...style, width: bounds.width, height: bounds.height };\n for (const key of [\n \"rankGap\",\n \"nodeGap\",\n \"levelGap\",\n \"siblingGap\",\n \"cellGap\",\n \"rowLabelWidth\",\n \"columnLabelHeight\",\n \"nodeWidth\",\n \"columnGap\",\n \"headerHeight\",\n \"ringGap\",\n \"bandWidth\",\n \"padAngle\",\n \"startAngle\",\n ])\n delete (groupStyle as unknown as Record<string, unknown>)[key];\n return { bounds, style, groupStyle };\n}\n/** Resolve a per-item paint callback without hiding its signal reads. */\nexport function resolveMarkStyle<G, S extends SystemStyle>(\n geometry: G,\n input?: MarkStyle<G, S>,\n): PibblResponsiveStyle<S> | undefined {\n return resolveSignalValue(\n typeof input === \"function\" ? input(geometry) : input,\n );\n}\n/** Build inspectable label regions and original-record node geometry. */\nexport function graphNodeGeometry<N>(\n datum: N,\n key: VizKey,\n bounds: Bounds,\n label = String(key),\n detail?: string,\n): GraphNodeGeometry<N> {\n return {\n key,\n datum,\n bounds,\n label: {\n text: label,\n bounds: {\n x: bounds.x + 10,\n y: bounds.y + 4,\n width: Math.max(0, bounds.width - 20),\n height: detail ? bounds.height / 2 : Math.max(0, bounds.height - 8),\n },\n rotationDegrees: 0,\n },\n detail:\n detail === undefined\n ? null\n : {\n text: detail,\n bounds: {\n x: bounds.x + 10,\n y: bounds.y + bounds.height / 2,\n width: Math.max(0, bounds.width - 20),\n height: bounds.height / 2 - 4,\n },\n rotationDegrees: 0,\n },\n };\n}\n/**\n * Return the built-in node paint as an ordinary reusable style.\n * @param _node - The node being painted. See {@link GraphNodeGeometry}.\n * @returns Default fill, stroke and corner radius. See {@link NodeStyle}.\n */\nexport function defaultGraphNodeStyle<N>(\n _node: GraphNodeGeometry<N>,\n): NodeStyle {\n return {\n fill: \"#edf5f2\",\n stroke: \"#8eb6aa\",\n strokeWidth: 1,\n cornerRadius: 7,\n };\n}\n/**\n * Return the built-in edge paint as an ordinary reusable style.\n * @param _edge - The edge being painted. See {@link GraphEdgeGeometry}.\n * @returns Default stroke and logical stroke width. See {@link EdgeStyle}.\n */\nexport function defaultGraphEdgeStyle<E>(\n _edge: GraphEdgeGeometry<E>,\n): EdgeStyle {\n return { stroke: \"#91a9a3\", strokeWidth: 1.5 };\n}\n/**\n * Return the built-in label paint as an ordinary reusable style.\n * @param _label - The label being painted. See {@link LabelGeometry}.\n * @returns Default Canvas font, fill and middle baseline. See {@link LabelStyle}.\n */\nexport function defaultGraphLabelStyle(_label: LabelGeometry): LabelStyle {\n return { fill: \"#203d39\", font: \"13px sans-serif\", textBaseline: \"middle\" };\n}\n/**\n * Draw a node box; children use node-local coordinates and ordinary Pibbl events.\n * @param props - Box geometry, paint and event handlers. See {@link GraphNodeProps}.\n * @returns A group containing the box and authored children. See {@link GraphNodeProps}.\n */\nexport function GraphNode<N>({\n node,\n style,\n children,\n ...events\n}: GraphNodeProps<N>): PibblNode {\n const { x, y, width, height } = node.bounds;\n const {\n keyboardFocusable,\n keyboardNavigationBounds,\n pointerEvents,\n ...handlers\n } = events;\n return (\n <Group {...handlers} style={{ translateX: x, translateY: y }}>\n <Rectangle\n keyboardFocusable={\n keyboardFocusable ??\n Boolean(handlers.onClick || handlers.onKeyDown || handlers.onKeyUp)\n }\n keyboardNavigationBounds={keyboardNavigationBounds}\n pointerEvents={pointerEvents}\n style={{ ...resolveSignalValue(style), width, height }}\n />\n {children}\n </Group>\n );\n}\n/**\n * Draw an edge path and optional arrow with ordinary path targeting.\n * @param props - Edge geometry, paint and event handlers. See {@link GraphEdgeProps}.\n * @returns Path content in diagram-local coordinates. See {@link GraphEdgeProps}.\n */\nexport function GraphEdge<E>({\n edge,\n style,\n ...events\n}: GraphEdgeProps<E>): PibblNode {\n const paint = resolveSignalValue(style);\n const {\n keyboardFocusable,\n keyboardNavigationBounds,\n pointerEvents,\n ...handlers\n } = events;\n return (\n <Group {...handlers}>\n <SvgPath\n keyboardFocusable={\n keyboardFocusable ??\n Boolean(handlers.onClick || handlers.onKeyDown || handlers.onKeyUp)\n }\n keyboardNavigationBounds={keyboardNavigationBounds}\n pointerEvents={pointerEvents}\n style={{ ...paint, d: edge.path }}\n />\n {edge.arrowPath && (\n <SvgPath\n pointerEvents={pointerEvents}\n style={{\n ...paint,\n fill: paint?.stroke ?? \"#91a9a3\",\n d: edge.arrowPath,\n }}\n />\n )}\n </Group>\n );\n}\n/**\n * Draw decorative label text at its supplied box and rotation.\n * @param props - Text geometry and optional text paint. See {@link GraphLabelProps}.\n * @returns A transformed decorative Text component. See {@link GraphLabelProps}.\n */\nexport function GraphLabel({ label, style }: GraphLabelProps): PibblNode {\n const { x, y, width, height } = label.bounds;\n const paint = resolveSignalValue(style);\n const align = resolveSignalValue(paint?.textAlign);\n const anchor =\n align === \"center\"\n ? width / 2\n : align === \"right\" || align === \"end\"\n ? width\n : 0;\n return (\n <Group\n style={{\n translateX: x + anchor,\n translateY: y + height / 2,\n rotationDegrees: label.rotationDegrees,\n }}\n >\n <Text\n pointerEvents=\"none\"\n style={{ textBaseline: \"middle\", fit: \"ellipsis\", ...paint, width }}\n >\n {label.text}\n </Text>\n </Group>\n );\n}\n/**\n * Render supplied graph geometry without layout, fitting, or simulation.\n * @param props - A snapshot or signal, item overrides and events. See {@link GraphViewProps}.\n * @returns An ordinary Pibbl composition preserving source paint order. See {@link GraphViewProps}.\n */\nexport function GraphView<N, E>(props: GraphViewProps<N, E>): PibblNode {\n const layout = resolveSignalValue(props.layout);\n return (\n <Group style={props.style}>\n {layout.edges.map((edge) => (\n <Group\n key={`e:${typeof edge.key}:${edge.key}`}\n onClick={(event) => props.onEdgeClick?.(edge, event)}\n >\n {props.renderEdge ? (\n props.renderEdge(edge)\n ) : (\n <GraphEdge\n edge={edge}\n keyboardFocusable={Boolean(props.onEdgeClick)}\n style={{\n ...defaultGraphEdgeStyle(edge),\n ...resolveMarkStyle(edge, props.edgeStyle),\n }}\n />\n )}\n </Group>\n ))}\n {layout.nodes.map((node) => (\n <Group\n key={`n:${typeof node.key}:${node.key}`}\n onClick={(event) => props.onNodeClick?.(node, event)}\n >\n {props.renderNode ? (\n props.renderNode(node)\n ) : (\n <Group>\n <GraphNode\n node={node}\n keyboardFocusable={Boolean(props.onNodeClick)}\n style={{\n ...defaultGraphNodeStyle(node),\n ...resolveMarkStyle(node, props.nodeStyle),\n }}\n />\n <GraphLabel\n label={node.label}\n style={{\n ...defaultGraphLabelStyle(node.label),\n ...resolveMarkStyle(node, props.labelStyle),\n }}\n />\n {node.detail && (\n <GraphLabel\n label={node.detail}\n style={{\n ...defaultGraphLabelStyle(node.detail),\n font: \"11px sans-serif\",\n ...resolveMarkStyle(node, props.labelStyle),\n }}\n />\n )}\n </Group>\n )}\n </Group>\n ))}\n </Group>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ import { type PibblNode } from "@pibbl/core";
2
+ import type { TidyTreeProps, TreeLayout, TreeOptions } from "./types.js";
3
+ /** Lay out a validated hierarchy with stable child order and variable node dimensions.
4
+ * @param root - Hierarchy root.
5
+ * @param options - Named hierarchy accessors and finite bounds.
6
+ * @returns Immutable node and edge geometry. @see {@link TreeLayout} */
7
+ export declare function layoutTree<D>(root: D, options: TreeOptions<D>): TreeLayout<D>;
8
+ /** Render a tidy hierarchy from ordinary data using default marks or caller compositions.
9
+ * @param props - Reactive hierarchy data, presentation, and frame style.
10
+ * @returns Canvas composition. @see {@link layoutTree} */
11
+ export declare function TidyTree<D>(props: TidyTreeProps<D>): PibblNode;
@@ -0,0 +1,232 @@
1
+ import { jsx, jsxs } from "@pibbl/core/jsx-runtime";
2
+ import { Group, resolveSignalValue } from "@pibbl/core";
3
+ import {
4
+ defaultGraphEdgeStyle,
5
+ defaultGraphLabelStyle,
6
+ defaultGraphNodeStyle,
7
+ GraphEdge,
8
+ GraphLabel,
9
+ GraphNode,
10
+ resolveMarkStyle,
11
+ useGraphFrame
12
+ } from "./shared.js";
13
+ import {
14
+ checkedBounds,
15
+ finiteGap,
16
+ finiteSize,
17
+ hierarchy,
18
+ hierarchyLabel
19
+ } from "./hierarchy.js";
20
+ function layoutTree(root, options) {
21
+ const bounds = checkedBounds(options.bounds, "layoutTree");
22
+ const siblingGap = finiteGap(options.siblingGap, 24, "layoutTree siblingGap");
23
+ const levelGap = finiteGap(options.levelGap, 48, "layoutTree levelGap");
24
+ const tree = hierarchy(root, options, "layoutTree");
25
+ const visible = (record) => record.collapsed ? [] : record.children;
26
+ const sizes = /* @__PURE__ */ new Map();
27
+ const spans = /* @__PURE__ */ new Map();
28
+ const depthHeights = [];
29
+ const measure = (record) => {
30
+ const size = finiteSize(record.datum, options.nodeSize, "layoutTree");
31
+ sizes.set(record, size);
32
+ depthHeights[record.depth] = Math.max(
33
+ depthHeights[record.depth] ?? 0,
34
+ size.height
35
+ );
36
+ const kids = visible(record);
37
+ const childSpan = kids.reduce(
38
+ (sum, child, index) => sum + measure(child) + (index ? siblingGap : 0),
39
+ 0
40
+ );
41
+ const span = Math.max(size.width, childSpan);
42
+ spans.set(record, span);
43
+ return span;
44
+ };
45
+ const totalWidth = measure(tree);
46
+ const totalHeight = depthHeights.reduce(
47
+ (sum, height, index) => sum + height + (index ? levelGap : 0),
48
+ 0
49
+ );
50
+ if (totalWidth > bounds.width || totalHeight > bounds.height)
51
+ throw new RangeError(
52
+ "layoutTree: bounds are too small for nodes and gaps."
53
+ );
54
+ const yAt = [];
55
+ let y = bounds.y;
56
+ for (let depth = 0; depth < depthHeights.length; depth++) {
57
+ yAt.push(y);
58
+ y += depthHeights[depth] + levelGap;
59
+ }
60
+ const positions = [];
61
+ const place = (record, left) => {
62
+ const span = spans.get(record);
63
+ const size = sizes.get(record);
64
+ const x = left + (span - size.width) / 2;
65
+ positions.push({ record, width: size.width, x });
66
+ let childLeft = left;
67
+ for (const child of visible(record)) {
68
+ place(child, childLeft);
69
+ childLeft += spans.get(child) + siblingGap;
70
+ }
71
+ };
72
+ place(tree, bounds.x + (bounds.width - totalWidth) / 2);
73
+ const nodes = positions.map(({ record, x }) => {
74
+ const size = sizes.get(record);
75
+ const box = Object.freeze({
76
+ x,
77
+ y: yAt[record.depth],
78
+ width: size.width,
79
+ height: size.height
80
+ });
81
+ return Object.freeze({
82
+ key: record.key,
83
+ datum: record.datum,
84
+ bounds: box,
85
+ label: hierarchyLabel(
86
+ record.datum,
87
+ options.label,
88
+ {
89
+ x: x + 10,
90
+ y: box.y + 10,
91
+ width: size.width - 20,
92
+ height: 18
93
+ },
94
+ String(record.key)
95
+ ),
96
+ detail: options.detail === void 0 ? null : hierarchyLabel(record.datum, options.detail, {
97
+ x: x + 10,
98
+ y: box.y + 31,
99
+ width: size.width - 20,
100
+ height: 16
101
+ }),
102
+ depth: record.depth,
103
+ parentKey: record.parent?.key ?? null,
104
+ hiddenChildren: record.hiddenChildren
105
+ });
106
+ });
107
+ const byKey = new Map(nodes.map((node) => [node.key, node]));
108
+ const edges = [];
109
+ for (const node of nodes)
110
+ if (node.parentKey !== null) {
111
+ const parent = byKey.get(node.parentKey);
112
+ const sourceAnchor = {
113
+ x: parent.bounds.x + parent.bounds.width / 2,
114
+ y: parent.bounds.y + parent.bounds.height
115
+ };
116
+ const targetAnchor = {
117
+ x: node.bounds.x + node.bounds.width / 2,
118
+ y: node.bounds.y
119
+ };
120
+ const bend = (sourceAnchor.y + targetAnchor.y) / 2;
121
+ const segments = Object.freeze([
122
+ { type: "move", ...sourceAnchor },
123
+ { type: "line", x: sourceAnchor.x, y: bend },
124
+ { type: "line", x: targetAnchor.x, y: bend },
125
+ { type: "line", ...targetAnchor }
126
+ ]);
127
+ edges.push(
128
+ Object.freeze({
129
+ key: `${String(parent.key)}\u2192${String(node.key)}`,
130
+ datum: { parent: parent.datum, child: node.datum },
131
+ sourceKey: parent.key,
132
+ targetKey: node.key,
133
+ route: Object.freeze({
134
+ sourceAnchor: Object.freeze(sourceAnchor),
135
+ targetAnchor: Object.freeze(targetAnchor),
136
+ segments,
137
+ arrowSegments: null
138
+ }),
139
+ path: `M ${sourceAnchor.x} ${sourceAnchor.y} L ${sourceAnchor.x} ${bend} L ${targetAnchor.x} ${bend} L ${targetAnchor.x} ${targetAnchor.y}`,
140
+ arrowPath: null
141
+ })
142
+ );
143
+ }
144
+ return Object.freeze({
145
+ bounds,
146
+ nodes: Object.freeze(nodes),
147
+ edges: Object.freeze(edges)
148
+ });
149
+ }
150
+ function TidyTree(props) {
151
+ const frame = useGraphFrame(props.style);
152
+ const root = resolveSignalValue(props.root);
153
+ if (frame.bounds.width === 0 || frame.bounds.height === 0)
154
+ return /* @__PURE__ */ jsx(Group, { style: frame.groupStyle, children: null });
155
+ const layout = layoutTree(root, {
156
+ ...props,
157
+ bounds: frame.bounds,
158
+ levelGap: frame.style.levelGap,
159
+ siblingGap: frame.style.siblingGap
160
+ });
161
+ return /* @__PURE__ */ jsxs(Group, { style: frame.groupStyle, children: [
162
+ layout.edges.map(
163
+ (edge) => props.renderEdge ? /* @__PURE__ */ jsx(
164
+ Group,
165
+ {
166
+ onClick: (event) => props.onEdgeClick?.(edge, event),
167
+ children: props.renderEdge(edge)
168
+ },
169
+ edge.key
170
+ ) : /* @__PURE__ */ jsx(
171
+ GraphEdge,
172
+ {
173
+ edge,
174
+ style: {
175
+ ...defaultGraphEdgeStyle(edge),
176
+ ...resolveMarkStyle(edge, props.edgeStyle)
177
+ },
178
+ onClick: (event) => props.onEdgeClick?.(edge, event)
179
+ },
180
+ edge.key
181
+ )
182
+ ),
183
+ layout.nodes.map(
184
+ (node) => props.renderNode ? /* @__PURE__ */ jsx(
185
+ Group,
186
+ {
187
+ onClick: (event) => props.onNodeClick?.(node, event),
188
+ children: props.renderNode(node)
189
+ },
190
+ node.key
191
+ ) : /* @__PURE__ */ jsxs(Group, { children: [
192
+ /* @__PURE__ */ jsx(
193
+ GraphNode,
194
+ {
195
+ node,
196
+ style: {
197
+ ...defaultGraphNodeStyle(node),
198
+ ...resolveMarkStyle(node, props.nodeStyle)
199
+ },
200
+ onClick: (event) => props.onNodeClick?.(node, event)
201
+ }
202
+ ),
203
+ /* @__PURE__ */ jsx(
204
+ GraphLabel,
205
+ {
206
+ label: node.label,
207
+ style: {
208
+ ...defaultGraphLabelStyle(node.label),
209
+ ...resolveMarkStyle(node, props.labelStyle)
210
+ }
211
+ }
212
+ ),
213
+ node.detail && /* @__PURE__ */ jsx(
214
+ GraphLabel,
215
+ {
216
+ label: node.detail,
217
+ style: {
218
+ ...defaultGraphLabelStyle(node.detail),
219
+ font: "11px sans-serif",
220
+ ...resolveMarkStyle(node, props.labelStyle)
221
+ }
222
+ }
223
+ )
224
+ ] }, node.key)
225
+ )
226
+ ] });
227
+ }
228
+ export {
229
+ TidyTree,
230
+ layoutTree
231
+ };
232
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/features/viz/lib/graph/tree.tsx"],
4
+ "sourcesContent": ["import { Group, resolveSignalValue, type PibblNode } from \"@pibbl/core\";\nimport {\n defaultGraphEdgeStyle,\n defaultGraphLabelStyle,\n defaultGraphNodeStyle,\n GraphEdge,\n GraphLabel,\n GraphNode,\n resolveMarkStyle,\n useGraphFrame,\n} from \"./shared.js\";\nimport type {\n GraphEdgeGeometry,\n TidyTreeProps,\n TreeEdge,\n TreeLayout,\n TreeNodeGeometry,\n TreeOptions,\n TreeStyle,\n} from \"./types.js\";\nimport {\n checkedBounds,\n finiteGap,\n finiteSize,\n hierarchy,\n hierarchyLabel,\n type HierarchyRecord,\n} from \"./hierarchy.js\";\n\ninterface Positioned<D> {\n record: HierarchyRecord<D>;\n width: number;\n x: number;\n}\n\n/** Lay out a validated hierarchy with stable child order and variable node dimensions.\n * @param root - Hierarchy root.\n * @param options - Named hierarchy accessors and finite bounds.\n * @returns Immutable node and edge geometry. @see {@link TreeLayout} */\nexport function layoutTree<D>(root: D, options: TreeOptions<D>): TreeLayout<D> {\n const bounds = checkedBounds(options.bounds, \"layoutTree\");\n const siblingGap = finiteGap(options.siblingGap, 24, \"layoutTree siblingGap\");\n const levelGap = finiteGap(options.levelGap, 48, \"layoutTree levelGap\");\n const tree = hierarchy(root, options, \"layoutTree\");\n const visible = (record: HierarchyRecord<D>) =>\n record.collapsed ? [] : record.children;\n const sizes = new Map<\n HierarchyRecord<D>,\n { width: number; height: number }\n >();\n const spans = new Map<HierarchyRecord<D>, number>();\n const depthHeights: number[] = [];\n const measure = (record: HierarchyRecord<D>): number => {\n const size = finiteSize(record.datum, options.nodeSize, \"layoutTree\");\n sizes.set(record, size);\n depthHeights[record.depth] = Math.max(\n depthHeights[record.depth] ?? 0,\n size.height,\n );\n const kids = visible(record);\n const childSpan = kids.reduce(\n (sum, child, index) => sum + measure(child) + (index ? siblingGap : 0),\n 0,\n );\n const span = Math.max(size.width, childSpan);\n spans.set(record, span);\n return span;\n };\n const totalWidth = measure(tree);\n const totalHeight = depthHeights.reduce(\n (sum, height, index) => sum + height + (index ? levelGap : 0),\n 0,\n );\n if (totalWidth > bounds.width || totalHeight > bounds.height)\n throw new RangeError(\n \"layoutTree: bounds are too small for nodes and gaps.\",\n );\n const yAt: number[] = [];\n let y = bounds.y;\n for (let depth = 0; depth < depthHeights.length; depth++) {\n yAt.push(y);\n y += depthHeights[depth] + levelGap;\n }\n const positions: Positioned<D>[] = [];\n const place = (record: HierarchyRecord<D>, left: number) => {\n const span = spans.get(record)!;\n const size = sizes.get(record)!;\n const x = left + (span - size.width) / 2;\n positions.push({ record, width: size.width, x });\n let childLeft = left;\n for (const child of visible(record)) {\n place(child, childLeft);\n childLeft += spans.get(child)! + siblingGap;\n }\n };\n place(tree, bounds.x + (bounds.width - totalWidth) / 2);\n const nodes = positions.map(({ record, x }) => {\n const size = sizes.get(record)!;\n const box = Object.freeze({\n x,\n y: yAt[record.depth],\n width: size.width,\n height: size.height,\n });\n return Object.freeze({\n key: record.key,\n datum: record.datum,\n bounds: box,\n label: hierarchyLabel(\n record.datum,\n options.label,\n {\n x: x + 10,\n y: box.y + 10,\n width: size.width - 20,\n height: 18,\n },\n String(record.key),\n ),\n detail:\n options.detail === undefined\n ? null\n : hierarchyLabel(record.datum, options.detail, {\n x: x + 10,\n y: box.y + 31,\n width: size.width - 20,\n height: 16,\n }),\n depth: record.depth,\n parentKey: record.parent?.key ?? null,\n hiddenChildren: record.hiddenChildren,\n } satisfies TreeNodeGeometry<D>);\n });\n const byKey = new Map(nodes.map((node) => [node.key, node]));\n const edges: GraphEdgeGeometry<TreeEdge<D>>[] = [];\n for (const node of nodes)\n if (node.parentKey !== null) {\n const parent = byKey.get(node.parentKey)!;\n const sourceAnchor = {\n x: parent.bounds.x + parent.bounds.width / 2,\n y: parent.bounds.y + parent.bounds.height,\n };\n const targetAnchor = {\n x: node.bounds.x + node.bounds.width / 2,\n y: node.bounds.y,\n };\n const bend = (sourceAnchor.y + targetAnchor.y) / 2;\n const segments = Object.freeze([\n { type: \"move\" as const, ...sourceAnchor },\n { type: \"line\" as const, x: sourceAnchor.x, y: bend },\n { type: \"line\" as const, x: targetAnchor.x, y: bend },\n { type: \"line\" as const, ...targetAnchor },\n ]);\n edges.push(\n Object.freeze({\n key: `${String(parent.key)}\u2192${String(node.key)}`,\n datum: { parent: parent.datum, child: node.datum },\n sourceKey: parent.key,\n targetKey: node.key,\n route: Object.freeze({\n sourceAnchor: Object.freeze(sourceAnchor),\n targetAnchor: Object.freeze(targetAnchor),\n segments,\n arrowSegments: null,\n }),\n path: `M ${sourceAnchor.x} ${sourceAnchor.y} L ${sourceAnchor.x} ${bend} L ${targetAnchor.x} ${bend} L ${targetAnchor.x} ${targetAnchor.y}`,\n arrowPath: null,\n }),\n );\n }\n return Object.freeze({\n bounds,\n nodes: Object.freeze(nodes),\n edges: Object.freeze(edges),\n });\n}\n\n/** Render a tidy hierarchy from ordinary data using default marks or caller compositions.\n * @param props - Reactive hierarchy data, presentation, and frame style.\n * @returns Canvas composition. @see {@link layoutTree} */\nexport function TidyTree<D>(props: TidyTreeProps<D>): PibblNode {\n const frame = useGraphFrame<TreeStyle>(props.style);\n const root = resolveSignalValue(props.root);\n if (frame.bounds.width === 0 || frame.bounds.height === 0)\n return <Group style={frame.groupStyle}>{null}</Group>;\n const layout = layoutTree(root, {\n ...props,\n bounds: frame.bounds,\n levelGap: frame.style.levelGap,\n siblingGap: frame.style.siblingGap,\n });\n return (\n <Group style={frame.groupStyle}>\n {layout.edges.map((edge) =>\n props.renderEdge ? (\n <Group\n key={edge.key}\n onClick={(event) => props.onEdgeClick?.(edge, event)}\n >\n {props.renderEdge(edge)}\n </Group>\n ) : (\n <GraphEdge\n key={edge.key}\n edge={edge}\n style={{\n ...defaultGraphEdgeStyle(edge),\n ...resolveMarkStyle(edge, props.edgeStyle),\n }}\n onClick={(event) => props.onEdgeClick?.(edge, event)}\n />\n ),\n )}\n {layout.nodes.map((node) =>\n props.renderNode ? (\n <Group\n key={node.key}\n onClick={(event) => props.onNodeClick?.(node, event)}\n >\n {props.renderNode(node)}\n </Group>\n ) : (\n <Group key={node.key}>\n <GraphNode\n node={node}\n style={{\n ...defaultGraphNodeStyle(node),\n ...resolveMarkStyle(node, props.nodeStyle),\n }}\n onClick={(event) => props.onNodeClick?.(node, event)}\n />\n <GraphLabel\n label={node.label}\n style={{\n ...defaultGraphLabelStyle(node.label),\n ...resolveMarkStyle(node, props.labelStyle),\n }}\n />\n {node.detail && (\n <GraphLabel\n label={node.detail}\n style={{\n ...defaultGraphLabelStyle(node.detail),\n font: \"11px sans-serif\",\n ...resolveMarkStyle(node, props.labelStyle),\n }}\n />\n )}\n </Group>\n ),\n )}\n </Group>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ import type { NodeStyle } from "./types.js";
2
+ import { type PibblNode } from "@pibbl/core";
3
+ import type { PartitionCellGeometry, PartitionLayout, PartitionCellProps, TreemapOptions, TreemapProps } from "./types.js";
4
+ /** Return the built-in depth-aware partition paint.
5
+ * @param cell - Partition cell geometry.
6
+ * @returns Ordinary rectangle style. @see {@link PartitionCellGeometry} */
7
+ export declare function defaultPartitionCellStyle<D>(cell: PartitionCellGeometry<D>): NodeStyle;
8
+ /** Squarify weighted hierarchy leaves into stable, compact rectangles.
9
+ * @param root - Hierarchy root.
10
+ * @param options - Accessors, units, and finite bounds.
11
+ * @returns Immutable partition geometry. @see {@link PartitionLayout} */
12
+ export declare function layoutTreemap<D>(root: D, options: TreemapOptions<D>): PartitionLayout<D>;
13
+ /** Paint one partition cell at its immutable geometry.
14
+ * @param props - Cell geometry, paint, events, and optional global-coordinate children.
15
+ * @returns Canvas composition. @see {@link PartitionCellGeometry} */
16
+ export declare function TreemapCell<D>(props: PartitionCellProps<D>): PibblNode;
17
+ /** Render a squarified hierarchy with visible quantitative labels.
18
+ * @param props - Reactive hierarchy data and presentation.
19
+ * @returns Canvas composition. @see {@link layoutTreemap} */
20
+ export declare function Treemap<D>(props: TreemapProps<D>): PibblNode;