@statelyai/graph 1.0.0 → 2.1.0
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.
- package/README.md +121 -44
- package/dist/{adjacency-list-VsUaH9SJ.mjs → adjacency-list-DQ32Mmhx.mjs} +3 -1
- package/dist/algorithms-D1cgly0g.d.mts +452 -0
- package/dist/algorithms-DBpH74hR.mjs +3309 -0
- package/dist/algorithms.d.mts +2 -2
- package/dist/algorithms.mjs +2 -2
- package/dist/config-Dt5u1gSf.mjs +793 -0
- package/dist/{converter-udLITX36.mjs → converter-DB6Rg6Vd.mjs} +2 -2
- package/dist/format-support.mjs +38 -11
- package/dist/formats/adjacency-list/index.d.mts +1 -1
- package/dist/formats/adjacency-list/index.mjs +1 -1
- package/dist/formats/converter/index.d.mts +1 -1
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +4 -4
- package/dist/formats/cytoscape/index.mjs +10 -4
- package/dist/formats/d2/index.d.mts +1 -1
- package/dist/formats/d2/index.mjs +26 -12
- package/dist/formats/d3/index.d.mts +4 -4
- package/dist/formats/d3/index.mjs +10 -4
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/dot/index.mjs +22 -6
- package/dist/formats/edge-list/index.d.mts +1 -1
- package/dist/formats/edge-list/index.mjs +1 -1
- package/dist/formats/elk/index.d.mts +1 -1
- package/dist/formats/elk/index.mjs +63 -24
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +43 -16
- package/dist/formats/gml/index.d.mts +4 -4
- package/dist/formats/gml/index.mjs +28 -15
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +96 -23
- package/dist/formats/jgf/index.d.mts +4 -4
- package/dist/formats/jgf/index.mjs +12 -5
- package/dist/formats/mermaid/index.d.mts +1 -1
- package/dist/formats/mermaid/index.mjs +49 -12
- package/dist/formats/tgf/index.d.mts +4 -4
- package/dist/formats/tgf/index.mjs +4 -4
- package/dist/formats/xyflow/index.d.mts +12 -6
- package/dist/formats/xyflow/index.mjs +42 -10
- package/dist/{index-D9Kj6Fe3.d.mts → index-BlbSWUvH.d.mts} +1 -1
- package/dist/{index-CHoriXZD.d.mts → index-CNvqxPLJ.d.mts} +157 -30
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +290 -307
- package/dist/layout/cytoscape.d.mts +66 -0
- package/dist/layout/cytoscape.mjs +114 -0
- package/dist/layout/d3-force.d.mts +52 -0
- package/dist/layout/d3-force.mjs +127 -0
- package/dist/layout/d3-hierarchy.d.mts +39 -0
- package/dist/layout/d3-hierarchy.mjs +135 -0
- package/dist/layout/dagre.d.mts +32 -0
- package/dist/layout/dagre.mjs +99 -0
- package/dist/layout/elk.d.mts +47 -0
- package/dist/layout/elk.mjs +73 -0
- package/dist/layout/forceatlas2.d.mts +48 -0
- package/dist/layout/forceatlas2.mjs +100 -0
- package/dist/layout/graphviz.d.mts +50 -0
- package/dist/layout/graphviz.mjs +179 -0
- package/dist/layout/index.d.mts +185 -0
- package/dist/layout/index.mjs +181 -0
- package/dist/layout/webcola.d.mts +40 -0
- package/dist/layout/webcola.mjs +104 -0
- package/dist/{queries-BlkA1HAN.d.mts → queries-B6quF529.d.mts} +43 -12
- package/dist/queries-BMM0XAv_.mjs +986 -0
- package/dist/queries.d.mts +1 -1
- package/dist/queries.mjs +1 -768
- package/dist/schemas.d.mts +19 -1
- package/dist/schemas.mjs +32 -84
- package/dist/{types-3-FS9NV2.d.mts → types-BAEQTwK_.d.mts} +99 -7
- package/dist/validate-BsfSOv0S.mjs +190 -0
- package/package.json +59 -7
- package/schemas/edge.schema.json +27 -0
- package/schemas/graph.schema.json +27 -0
- package/dist/algorithms-Ba7o7niK.mjs +0 -2394
- package/dist/algorithms-fTqmvhzP.d.mts +0 -178
- package/dist/indexing-DR8M1vBy.mjs +0 -137
- /package/dist/{edge-list-DP4otyPU.mjs → edge-list-CA9UTvn2.mjs} +0 -0
- /package/dist/{mode-D8OnHFBk.mjs → mode-gu_mhKKs.mjs} +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { d as getLCA } from "../queries-BMM0XAv_.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/layout/index.ts
|
|
4
|
+
/** Fallback node size when neither `measure` nor node dimensions are set. */
|
|
5
|
+
const DEFAULT_NODE_SIZE = {
|
|
6
|
+
width: 100,
|
|
7
|
+
height: 50
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a node's layout size: `options.measure` → node `width`/`height` →
|
|
11
|
+
* {@link DEFAULT_NODE_SIZE}. Zero sizes count as unset (layout engines
|
|
12
|
+
* overlap zero-sized nodes).
|
|
13
|
+
*/
|
|
14
|
+
function getNodeSize(node, options) {
|
|
15
|
+
const measured = options?.measure?.(node);
|
|
16
|
+
if (measured) return measured;
|
|
17
|
+
const width = node.width !== void 0 && node.width > 0 ? node.width : 0;
|
|
18
|
+
const height = node.height !== void 0 && node.height > 0 ? node.height : 0;
|
|
19
|
+
return {
|
|
20
|
+
width: width || DEFAULT_NODE_SIZE.width,
|
|
21
|
+
height: height || DEFAULT_NODE_SIZE.height
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* **Mutable.** Write a {@link LayoutFrame}'s positions onto the graph's nodes
|
|
26
|
+
* in place. Positions are non-structural, so this is safe under the index
|
|
27
|
+
* contract (no `invalidateIndex` needed) and cheap enough for per-animation-
|
|
28
|
+
* frame use. Nodes absent from the frame are left untouched.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* for (const frame of genForceLayout(graph)) {
|
|
33
|
+
* applyLayoutFrame(graph, frame);
|
|
34
|
+
* render(graph);
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function applyLayoutFrame(graph, frame) {
|
|
39
|
+
for (const node of graph.nodes) {
|
|
40
|
+
const position = frame.positions[node.id];
|
|
41
|
+
if (position !== void 0) {
|
|
42
|
+
node.x = position.x;
|
|
43
|
+
node.y = position.y;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Bounding rect of all positioned nodes (and edge route points, when
|
|
49
|
+
* present). Returns a zero rect for graphs with no geometry.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const bounds = getLayoutBounds(laidOut);
|
|
54
|
+
* svg.setAttribute('viewBox', `${bounds.x} ${bounds.y} ${bounds.width} ${bounds.height}`);
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
function getLayoutBounds(graph) {
|
|
58
|
+
let minX = Infinity;
|
|
59
|
+
let minY = Infinity;
|
|
60
|
+
let maxX = -Infinity;
|
|
61
|
+
let maxY = -Infinity;
|
|
62
|
+
for (const node of graph.nodes) {
|
|
63
|
+
if (node.x === void 0 || node.y === void 0) continue;
|
|
64
|
+
minX = Math.min(minX, node.x);
|
|
65
|
+
minY = Math.min(minY, node.y);
|
|
66
|
+
maxX = Math.max(maxX, node.x + (node.width ?? 0));
|
|
67
|
+
maxY = Math.max(maxY, node.y + (node.height ?? 0));
|
|
68
|
+
}
|
|
69
|
+
for (const edge of graph.edges) for (const point of edge.points ?? []) {
|
|
70
|
+
minX = Math.min(minX, point.x);
|
|
71
|
+
minY = Math.min(minY, point.y);
|
|
72
|
+
maxX = Math.max(maxX, point.x);
|
|
73
|
+
maxY = Math.max(maxY, point.y);
|
|
74
|
+
}
|
|
75
|
+
if (minX === Infinity) return {
|
|
76
|
+
x: 0,
|
|
77
|
+
y: 0,
|
|
78
|
+
width: 0,
|
|
79
|
+
height: 0
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
x: minX,
|
|
83
|
+
y: minY,
|
|
84
|
+
width: maxX - minX,
|
|
85
|
+
height: maxY - minY
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Animate between two layouts of the same graph: yields interpolated
|
|
90
|
+
* {@link LayoutFrame}s from the node positions in `from` to those in `to`
|
|
91
|
+
* (drive them with {@link applyLayoutFrame}, e.g. one per animation frame),
|
|
92
|
+
* and returns `to`. This is what makes layouts swappable live — lay out with
|
|
93
|
+
* one engine, re-lay out with another, and tween between them.
|
|
94
|
+
*
|
|
95
|
+
* Nodes are matched by id; nodes without a position in `from` (or absent from
|
|
96
|
+
* it) start at their `to` position. Edge routes are not interpolated — frames
|
|
97
|
+
* carry node positions only; hide or re-route edges during the transition.
|
|
98
|
+
* `alpha` cools linearly 1 → 0 like the physics layouts.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const next = await getElkLayout(graph);
|
|
103
|
+
* for (const frame of genLayoutTransition(graph, next)) {
|
|
104
|
+
* applyLayoutFrame(graph, frame);
|
|
105
|
+
* render(graph);
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
function* genLayoutTransition(from, to, options) {
|
|
110
|
+
const steps = Math.max(1, Math.floor(options?.steps ?? 30));
|
|
111
|
+
const ease = options?.ease ?? ((t) => t * t * (3 - 2 * t));
|
|
112
|
+
const fromById = new Map(from.nodes.map((node) => [node.id, node]));
|
|
113
|
+
const tweens = to.nodes.map((node) => {
|
|
114
|
+
const fromNode = fromById.get(node.id);
|
|
115
|
+
const hasStart = fromNode?.x !== void 0 && fromNode.y !== void 0;
|
|
116
|
+
return {
|
|
117
|
+
id: node.id,
|
|
118
|
+
startX: hasStart ? fromNode.x : node.x,
|
|
119
|
+
startY: hasStart ? fromNode.y : node.y,
|
|
120
|
+
endX: node.x,
|
|
121
|
+
endY: node.y
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
for (let step = 1; step <= steps; step++) {
|
|
125
|
+
const t = ease(step / steps);
|
|
126
|
+
const positions = {};
|
|
127
|
+
for (const tween of tweens) positions[tween.id] = {
|
|
128
|
+
x: tween.startX + (tween.endX - tween.startX) * t,
|
|
129
|
+
y: tween.startY + (tween.endY - tween.startY) * t
|
|
130
|
+
};
|
|
131
|
+
yield {
|
|
132
|
+
positions,
|
|
133
|
+
alpha: 1 - step / steps
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return to;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* **Mutable.** Shift the graph's geometry by `(dx, dy)` in place: node
|
|
140
|
+
* positions, edge route `points`, and edge label rects. Non-structural, so no
|
|
141
|
+
* index invalidation is needed.
|
|
142
|
+
*
|
|
143
|
+
* Hierarchy-aware: child nodes (`parentId` set) use parent-relative
|
|
144
|
+
* coordinates (the ELK/xyflow convention), so only top-level nodes are
|
|
145
|
+
* shifted — children move with their parents. Likewise, an edge's geometry is
|
|
146
|
+
* shifted only when its containing coordinate system is the root (the LCA of
|
|
147
|
+
* its endpoints is no node).
|
|
148
|
+
*/
|
|
149
|
+
function translateGraph(graph, dx, dy) {
|
|
150
|
+
for (const node of graph.nodes) {
|
|
151
|
+
if (node.parentId != null) continue;
|
|
152
|
+
if (node.x !== void 0) node.x += dx;
|
|
153
|
+
if (node.y !== void 0) node.y += dy;
|
|
154
|
+
}
|
|
155
|
+
for (const edge of graph.edges) {
|
|
156
|
+
if (getLCA(graph, edge.sourceId, edge.targetId) !== void 0) continue;
|
|
157
|
+
if (edge.x !== void 0) edge.x += dx;
|
|
158
|
+
if (edge.y !== void 0) edge.y += dy;
|
|
159
|
+
for (const point of edge.points ?? []) {
|
|
160
|
+
point.x += dx;
|
|
161
|
+
point.y += dy;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* **Mutable.** Translate the graph in place so its {@link getLayoutBounds}
|
|
167
|
+
* center coincides with `rect`'s center — e.g. center a fresh layout in the
|
|
168
|
+
* viewport. Graphs without geometry are left untouched.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* centerGraph(laidOut, { x: 0, y: 0, width: canvas.width, height: canvas.height });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function centerGraph(graph, rect) {
|
|
176
|
+
const bounds = getLayoutBounds(graph);
|
|
177
|
+
translateGraph(graph, rect.x + rect.width / 2 - (bounds.x + bounds.width / 2), rect.y + rect.height / 2 - (bounds.y + bounds.height / 2));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
export { DEFAULT_NODE_SIZE, applyLayoutFrame, centerGraph, genLayoutTransition, getLayoutBounds, getNodeSize, translateGraph };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { I as VisualGraph, f as Graph } from "../types-BAEQTwK_.mjs";
|
|
2
|
+
import { LayoutOptions } from "./index.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/layout/webcola.d.ts
|
|
5
|
+
interface ColaLayoutOptions extends LayoutOptions {
|
|
6
|
+
/** Target distance between linked nodes. Default: 80. */
|
|
7
|
+
linkDistance?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Constraint-based layout via WebCola (optional peer dependency). Runs the
|
|
11
|
+
* solver to convergence synchronously (`keepRunning: false`) and returns a
|
|
12
|
+
* positioned {@link VisualGraph}.
|
|
13
|
+
*
|
|
14
|
+
* - Overlap avoidance is always on: node rects (from `options.measure` →
|
|
15
|
+
* node `width`/`height` → defaults) are kept disjoint.
|
|
16
|
+
* - Deterministic: WebCola itself has no `Math.random` in its 2D solver
|
|
17
|
+
* (its descent uses an internally seeded PRNG), so the seeded initial
|
|
18
|
+
* scatter (`options.seed`) fully pins the result — same seed, same layout.
|
|
19
|
+
* - `options.isFixed` pins positioned nodes at their current `x`/`y` (cola's
|
|
20
|
+
* `fixed` flag). During overlap projection cola holds fixed nodes with a
|
|
21
|
+
* large-but-finite weight, so pinning is within a small tolerance, not
|
|
22
|
+
* exact.
|
|
23
|
+
* - `options.direction ?? graph.direction` set → DAG-flow constraints via
|
|
24
|
+
* cola's `flowLayout`: edges are separated along the axis ('down'/'up' →
|
|
25
|
+
* `y`, 'left'/'right' → `x`) by at least `spacing.layer` (default 50).
|
|
26
|
+
* Note: cola only separates source-before-target along the axis — 'up' and
|
|
27
|
+
* 'left' flow the same way as 'down'/'right', not reversed.
|
|
28
|
+
* - Hierarchy is ignored (flat layout); self-loops are skipped by the solver
|
|
29
|
+
* but kept in the output graph.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { getColaLayout } from '@statelyai/graph/layout/webcola';
|
|
34
|
+
*
|
|
35
|
+
* const laidOut = getColaLayout(graph, { seed: 42, direction: 'down' });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function getColaLayout(graph: Graph | VisualGraph, options?: ColaLayoutOptions): VisualGraph;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { ColaLayoutOptions, getColaLayout };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { f as createVisualGraph, n as toNodeConfig, t as toEdgeConfig } from "../config-Dt5u1gSf.mjs";
|
|
2
|
+
import { getNodeSize } from "./index.mjs";
|
|
3
|
+
import { Layout } from "webcola";
|
|
4
|
+
|
|
5
|
+
//#region src/layout/webcola.ts
|
|
6
|
+
/** mulberry32 — same seeded PRNG the rest of the library uses. */
|
|
7
|
+
function mulberry32(seed) {
|
|
8
|
+
let s = seed | 0;
|
|
9
|
+
return () => {
|
|
10
|
+
s = s + 1831565813 | 0;
|
|
11
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
12
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
13
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Constraint-based layout via WebCola (optional peer dependency). Runs the
|
|
18
|
+
* solver to convergence synchronously (`keepRunning: false`) and returns a
|
|
19
|
+
* positioned {@link VisualGraph}.
|
|
20
|
+
*
|
|
21
|
+
* - Overlap avoidance is always on: node rects (from `options.measure` →
|
|
22
|
+
* node `width`/`height` → defaults) are kept disjoint.
|
|
23
|
+
* - Deterministic: WebCola itself has no `Math.random` in its 2D solver
|
|
24
|
+
* (its descent uses an internally seeded PRNG), so the seeded initial
|
|
25
|
+
* scatter (`options.seed`) fully pins the result — same seed, same layout.
|
|
26
|
+
* - `options.isFixed` pins positioned nodes at their current `x`/`y` (cola's
|
|
27
|
+
* `fixed` flag). During overlap projection cola holds fixed nodes with a
|
|
28
|
+
* large-but-finite weight, so pinning is within a small tolerance, not
|
|
29
|
+
* exact.
|
|
30
|
+
* - `options.direction ?? graph.direction` set → DAG-flow constraints via
|
|
31
|
+
* cola's `flowLayout`: edges are separated along the axis ('down'/'up' →
|
|
32
|
+
* `y`, 'left'/'right' → `x`) by at least `spacing.layer` (default 50).
|
|
33
|
+
* Note: cola only separates source-before-target along the axis — 'up' and
|
|
34
|
+
* 'left' flow the same way as 'down'/'right', not reversed.
|
|
35
|
+
* - Hierarchy is ignored (flat layout); self-loops are skipped by the solver
|
|
36
|
+
* but kept in the output graph.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { getColaLayout } from '@statelyai/graph/layout/webcola';
|
|
41
|
+
*
|
|
42
|
+
* const laidOut = getColaLayout(graph, { seed: 42, direction: 'down' });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function getColaLayout(graph, options) {
|
|
46
|
+
const rng = mulberry32(options?.seed ?? 1);
|
|
47
|
+
const sizes = /* @__PURE__ */ new Map();
|
|
48
|
+
const scatterRadius = Math.max(80, 30 * Math.sqrt(graph.nodes.length));
|
|
49
|
+
const colaNodes = graph.nodes.map((node) => {
|
|
50
|
+
const size = getNodeSize(node, options);
|
|
51
|
+
sizes.set(node.id, size);
|
|
52
|
+
const colaNode = {
|
|
53
|
+
id: node.id,
|
|
54
|
+
...size
|
|
55
|
+
};
|
|
56
|
+
if (node.x !== void 0 && node.y !== void 0) {
|
|
57
|
+
colaNode.x = node.x + size.width / 2;
|
|
58
|
+
colaNode.y = node.y + size.height / 2;
|
|
59
|
+
if (options?.isFixed?.(node)) colaNode.fixed = 1;
|
|
60
|
+
} else {
|
|
61
|
+
const angle = rng() * 2 * Math.PI;
|
|
62
|
+
const radius = scatterRadius * Math.sqrt(rng());
|
|
63
|
+
colaNode.x = Math.cos(angle) * radius;
|
|
64
|
+
colaNode.y = Math.sin(angle) * radius;
|
|
65
|
+
}
|
|
66
|
+
return colaNode;
|
|
67
|
+
});
|
|
68
|
+
const indexById = new Map(graph.nodes.map((node, i) => [node.id, i]));
|
|
69
|
+
const colaLinks = graph.edges.filter((edge) => indexById.has(edge.sourceId) && indexById.has(edge.targetId) && edge.sourceId !== edge.targetId).map((edge) => ({
|
|
70
|
+
source: indexById.get(edge.sourceId),
|
|
71
|
+
target: indexById.get(edge.targetId)
|
|
72
|
+
}));
|
|
73
|
+
const layout = new Layout().nodes(colaNodes).links(colaLinks).linkDistance(options?.linkDistance ?? 80).avoidOverlaps(true);
|
|
74
|
+
const direction = options?.direction ?? graph.direction;
|
|
75
|
+
if (direction !== void 0) {
|
|
76
|
+
const axis = direction === "left" || direction === "right" ? "x" : "y";
|
|
77
|
+
layout.flowLayout(axis, options?.spacing?.layer ?? 50);
|
|
78
|
+
}
|
|
79
|
+
const centerGraph = !colaNodes.some((colaNode) => colaNode.fixed);
|
|
80
|
+
layout.start(30, 30, 60, 0, false, centerGraph);
|
|
81
|
+
const byId = new Map(colaNodes.map((colaNode) => [colaNode.id, colaNode]));
|
|
82
|
+
return createVisualGraph({
|
|
83
|
+
id: graph.id,
|
|
84
|
+
mode: graph.mode,
|
|
85
|
+
initialNodeId: graph.initialNodeId ?? void 0,
|
|
86
|
+
direction: graph.direction,
|
|
87
|
+
data: graph.data,
|
|
88
|
+
...graph.style !== void 0 && { style: graph.style },
|
|
89
|
+
nodes: graph.nodes.map((node) => {
|
|
90
|
+
const size = sizes.get(node.id);
|
|
91
|
+
const colaNode = byId.get(node.id);
|
|
92
|
+
return {
|
|
93
|
+
...toNodeConfig(node),
|
|
94
|
+
...size,
|
|
95
|
+
x: (colaNode.x ?? 0) - size.width / 2,
|
|
96
|
+
y: (colaNode.y ?? 0) - size.height / 2
|
|
97
|
+
};
|
|
98
|
+
}),
|
|
99
|
+
edges: graph.edges.map((edge) => toEdgeConfig(edge))
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
export { getColaLayout };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as GraphPort, f as Graph, h as GraphEdge, y as GraphNode } from "./types-BAEQTwK_.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/queries.d.ts
|
|
4
4
|
|
|
@@ -20,7 +20,9 @@ import { _ as GraphNode, b as GraphPort, p as GraphEdge, u as Graph } from "./ty
|
|
|
20
20
|
*/
|
|
21
21
|
declare function getEdgesOf<N, E>(graph: Graph<N, E>, nodeId: string): GraphEdge<E>[];
|
|
22
22
|
/**
|
|
23
|
-
* Returns incoming edges to a node
|
|
23
|
+
* Returns incoming edges to a node, by *authored* direction
|
|
24
|
+
* (`edge.targetId === nodeId`), regardless of edge mode. For mode-aware
|
|
25
|
+
* traversal use {@link getPredecessors} or {@link getEdgesOf}.
|
|
24
26
|
*
|
|
25
27
|
* @example
|
|
26
28
|
* ```ts
|
|
@@ -36,7 +38,9 @@ declare function getEdgesOf<N, E>(graph: Graph<N, E>, nodeId: string): GraphEdge
|
|
|
36
38
|
*/
|
|
37
39
|
declare function getInEdges<N, E>(graph: Graph<N, E>, nodeId: string): GraphEdge<E>[];
|
|
38
40
|
/**
|
|
39
|
-
* Returns outgoing edges from a node
|
|
41
|
+
* Returns outgoing edges from a node, by *authored* direction
|
|
42
|
+
* (`edge.sourceId === nodeId`), regardless of edge mode. For mode-aware
|
|
43
|
+
* traversal use {@link getSuccessors} or {@link getEdgesOf}.
|
|
40
44
|
*
|
|
41
45
|
* @example
|
|
42
46
|
* ```ts
|
|
@@ -69,7 +73,9 @@ declare function getOutEdges<N, E>(graph: Graph<N, E>, nodeId: string): GraphEdg
|
|
|
69
73
|
*/
|
|
70
74
|
declare function getEdgesBetween<N, E>(graph: Graph<N, E>, sourceId: string, targetId: string): GraphEdge<E>[];
|
|
71
75
|
/**
|
|
72
|
-
* Returns direct successor nodes
|
|
76
|
+
* Returns direct successor nodes — nodes reachable by traversing one edge
|
|
77
|
+
* away from `nodeId`. Edges whose effective mode is not `'directed'` are
|
|
78
|
+
* traversable both ways, so their other endpoint also counts as a successor.
|
|
73
79
|
*
|
|
74
80
|
* @example
|
|
75
81
|
* ```ts
|
|
@@ -86,7 +92,9 @@ declare function getEdgesBetween<N, E>(graph: Graph<N, E>, sourceId: string, tar
|
|
|
86
92
|
*/
|
|
87
93
|
declare function getSuccessors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
|
|
88
94
|
/**
|
|
89
|
-
* Returns direct predecessor nodes
|
|
95
|
+
* Returns direct predecessor nodes — nodes from which `nodeId` is reachable
|
|
96
|
+
* by traversing one edge. Edges whose effective mode is not `'directed'` are
|
|
97
|
+
* traversable both ways, so their other endpoint also counts as a predecessor.
|
|
90
98
|
*
|
|
91
99
|
* @example
|
|
92
100
|
* ```ts
|
|
@@ -120,9 +128,10 @@ declare function getPredecessors<N>(graph: Graph<N>, nodeId: string): GraphNode<
|
|
|
120
128
|
*/
|
|
121
129
|
declare function getNeighbors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[];
|
|
122
130
|
/**
|
|
123
|
-
* Returns the total degree of a node (
|
|
124
|
-
*
|
|
125
|
-
*
|
|
131
|
+
* Returns the total degree of a node (number of incident edge endpoints).
|
|
132
|
+
* Each incident edge whose effective mode is not `'directed'` is counted
|
|
133
|
+
* once (a non-directed self-loop counts once; a directed self-loop counts
|
|
134
|
+
* twice — once in, once out).
|
|
126
135
|
*
|
|
127
136
|
* @example
|
|
128
137
|
* ```ts
|
|
@@ -139,7 +148,9 @@ declare function getNeighbors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[
|
|
|
139
148
|
*/
|
|
140
149
|
declare function getDegree(graph: Graph, nodeId: string): number;
|
|
141
150
|
/**
|
|
142
|
-
* Returns the in-degree of a node
|
|
151
|
+
* Returns the in-degree of a node — the number of edges traversable *into*
|
|
152
|
+
* the node. Edges whose effective mode is not `'directed'` count toward both
|
|
153
|
+
* endpoints' in-degree (once per edge).
|
|
143
154
|
*
|
|
144
155
|
* @example
|
|
145
156
|
* ```ts
|
|
@@ -153,7 +164,9 @@ declare function getDegree(graph: Graph, nodeId: string): number;
|
|
|
153
164
|
*/
|
|
154
165
|
declare function getInDegree(graph: Graph, nodeId: string): number;
|
|
155
166
|
/**
|
|
156
|
-
* Returns the out-degree of a node
|
|
167
|
+
* Returns the out-degree of a node — the number of edges traversable *out of*
|
|
168
|
+
* the node. Edges whose effective mode is not `'directed'` count toward both
|
|
169
|
+
* endpoints' out-degree (once per edge).
|
|
157
170
|
*
|
|
158
171
|
* @example
|
|
159
172
|
* ```ts
|
|
@@ -207,6 +220,10 @@ declare function getParent<N>(graph: Graph<N>, nodeId: string): GraphNode<N> | u
|
|
|
207
220
|
/**
|
|
208
221
|
* Returns all ancestors from the node up to the root (nearest parent first).
|
|
209
222
|
*
|
|
223
|
+
* If the parent chain contains a cycle (authored `parentId` cycles are not
|
|
224
|
+
* rejected by `createGraph`), the walk stops at the first repeated node and
|
|
225
|
+
* returns the ancestors collected so far — each ancestor appears exactly once.
|
|
226
|
+
*
|
|
210
227
|
* @example
|
|
211
228
|
* ```ts
|
|
212
229
|
* const graph = createGraph({
|
|
@@ -224,6 +241,10 @@ declare function getAncestors<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[
|
|
|
224
241
|
/**
|
|
225
242
|
* Returns all descendants recursively (depth-first).
|
|
226
243
|
*
|
|
244
|
+
* If the hierarchy contains a parent cycle (authored `parentId` cycles are
|
|
245
|
+
* not rejected by `createGraph`), each node is visited at most once: the walk
|
|
246
|
+
* stops at the first repeated node and returns the descendants collected so far.
|
|
247
|
+
*
|
|
227
248
|
* @example
|
|
228
249
|
* ```ts
|
|
229
250
|
* const graph = createGraph({
|
|
@@ -291,6 +312,10 @@ declare function isLeaf(graph: Graph, nodeId: string): boolean;
|
|
|
291
312
|
* Depth of a node in the hierarchy (root = 0).
|
|
292
313
|
* Returns -1 if the node is not found.
|
|
293
314
|
*
|
|
315
|
+
* If the parent chain contains a cycle (authored `parentId` cycles are not
|
|
316
|
+
* rejected by `createGraph`), the walk stops at the first repeated node and
|
|
317
|
+
* returns the number of unique ancestors walked up to that point.
|
|
318
|
+
*
|
|
294
319
|
* @example
|
|
295
320
|
* ```ts
|
|
296
321
|
* const graph = createGraph({
|
|
@@ -328,6 +353,10 @@ declare function getSiblings<N>(graph: Graph<N>, nodeId: string): GraphNode<N>[]
|
|
|
328
353
|
* Least Common Ancestor -- deepest proper ancestor of all given nodes.
|
|
329
354
|
* A proper ancestor excludes the input nodes themselves.
|
|
330
355
|
*
|
|
356
|
+
* If a parent chain contains a cycle (authored `parentId` cycles are not
|
|
357
|
+
* rejected by `createGraph`), each chain walk stops at the first repeated
|
|
358
|
+
* node, so every ancestor is considered exactly once.
|
|
359
|
+
*
|
|
331
360
|
* @example
|
|
332
361
|
* ```ts
|
|
333
362
|
* const graph = createGraph({
|
|
@@ -422,7 +451,8 @@ declare function getRelativeDistanceMap(graph: Graph, parentId: string | null):
|
|
|
422
451
|
*/
|
|
423
452
|
declare function getRelativeDistance(graph: Graph, nodeId: string): number | undefined;
|
|
424
453
|
/**
|
|
425
|
-
* Nodes with no incoming edges (inDegree 0).
|
|
454
|
+
* Nodes with no incoming edges (inDegree 0). A node incident to an edge whose
|
|
455
|
+
* effective mode is not `'directed'` is never a source (the edge points in).
|
|
426
456
|
*
|
|
427
457
|
* @example
|
|
428
458
|
* ```ts
|
|
@@ -439,7 +469,8 @@ declare function getRelativeDistance(graph: Graph, nodeId: string): number | und
|
|
|
439
469
|
*/
|
|
440
470
|
declare function getSources<N>(graph: Graph<N>): GraphNode<N>[];
|
|
441
471
|
/**
|
|
442
|
-
* Nodes with no outgoing edges (outDegree 0).
|
|
472
|
+
* Nodes with no outgoing edges (outDegree 0). A node incident to an edge whose
|
|
473
|
+
* effective mode is not `'directed'` is never a sink (the edge points out).
|
|
443
474
|
*
|
|
444
475
|
* @example
|
|
445
476
|
* ```ts
|