@zvk/graphs 0.1.1 → 0.1.3
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/CHANGELOG.md +14 -0
- package/README.md +380 -4
- package/dist/algorithms.d.ts +73 -1
- package/dist/algorithms.js +287 -0
- package/dist/diagnostics.d.ts +74 -2
- package/dist/diagnostics.js +577 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/layout/dag.d.ts +5 -2
- package/dist/layout/dag.js +38 -12
- package/dist/layout/manual.d.ts +3 -2
- package/dist/layout/manual.js +11 -8
- package/dist/layout/tree.d.ts +3 -2
- package/dist/layout/tree.js +15 -8
- package/dist/layout/types.d.ts +76 -2
- package/dist/layout/types.js +336 -9
- package/dist/model.d.ts +47 -0
- package/dist/serialization.d.ts +14 -0
- package/dist/serialization.js +68 -0
- package/dist/styles.css +209 -7
- package/dist/svg.js +234 -19
- package/package.json +23 -8
package/dist/algorithms.js
CHANGED
|
@@ -38,6 +38,180 @@ export function getGraphDegrees(graph) {
|
|
|
38
38
|
}
|
|
39
39
|
return degrees;
|
|
40
40
|
}
|
|
41
|
+
function graphModelFields(options) {
|
|
42
|
+
return {
|
|
43
|
+
...(options.id !== undefined ? { id: options.id } : {}),
|
|
44
|
+
title: options.title,
|
|
45
|
+
...(options.description !== undefined ? { description: options.description } : {}),
|
|
46
|
+
...(options.direction !== undefined ? { direction: options.direction } : {}),
|
|
47
|
+
...(options.groups !== undefined ? { groups: options.groups } : {})
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function addGraphInteropNode(nodes, nodeIds, nodeId, getNodeLabel) {
|
|
51
|
+
if (nodeIds.has(nodeId)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const index = nodes.length;
|
|
55
|
+
nodeIds.add(nodeId);
|
|
56
|
+
nodes.push({
|
|
57
|
+
id: nodeId,
|
|
58
|
+
label: getNodeLabel?.(nodeId, { nodeId, index }) ?? nodeId
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function uniqueGeneratedEdgeId(baseId, edgeIds) {
|
|
62
|
+
if (!edgeIds.has(baseId)) {
|
|
63
|
+
edgeIds.add(baseId);
|
|
64
|
+
return baseId;
|
|
65
|
+
}
|
|
66
|
+
let nextIndex = 2;
|
|
67
|
+
let nextId = `${baseId}-${nextIndex}`;
|
|
68
|
+
while (edgeIds.has(nextId)) {
|
|
69
|
+
nextIndex += 1;
|
|
70
|
+
nextId = `${baseId}-${nextIndex}`;
|
|
71
|
+
}
|
|
72
|
+
edgeIds.add(nextId);
|
|
73
|
+
return nextId;
|
|
74
|
+
}
|
|
75
|
+
export function graphFromEdgeList(edgeList, options) {
|
|
76
|
+
const nodes = [];
|
|
77
|
+
const nodeIds = new Set();
|
|
78
|
+
const edgeIds = new Set();
|
|
79
|
+
for (const node of options.nodes ?? []) {
|
|
80
|
+
if (!nodeIds.has(node.id)) {
|
|
81
|
+
nodeIds.add(node.id);
|
|
82
|
+
nodes.push(node);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const entry of edgeList) {
|
|
86
|
+
addGraphInteropNode(nodes, nodeIds, entry.source, options.getNodeLabel);
|
|
87
|
+
addGraphInteropNode(nodes, nodeIds, entry.target, options.getNodeLabel);
|
|
88
|
+
}
|
|
89
|
+
const edges = edgeList.map((entry, index) => {
|
|
90
|
+
const { id: explicitId, ...edge } = entry;
|
|
91
|
+
const proposedId = explicitId ?? `${entry.source}-${entry.target}`;
|
|
92
|
+
const customId = explicitId ?? options.getEdgeId?.(entry, { entry, index, proposedId });
|
|
93
|
+
const id = customId ?? uniqueGeneratedEdgeId(proposedId, edgeIds);
|
|
94
|
+
if (customId !== undefined) {
|
|
95
|
+
edgeIds.add(customId);
|
|
96
|
+
}
|
|
97
|
+
return { id, ...edge };
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
...graphModelFields(options),
|
|
101
|
+
nodes,
|
|
102
|
+
edges
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function graphToEdgeList(graph) {
|
|
106
|
+
return graph.edges.map((edge) => ({ ...edge }));
|
|
107
|
+
}
|
|
108
|
+
export function graphToAdjacencyList(graph) {
|
|
109
|
+
const targetsBySource = new Map();
|
|
110
|
+
for (const node of graph.nodes) {
|
|
111
|
+
targetsBySource.set(node.id, []);
|
|
112
|
+
}
|
|
113
|
+
for (const edge of graph.edges) {
|
|
114
|
+
targetsBySource.set(edge.source, [...(targetsBySource.get(edge.source) ?? []), edge.target]);
|
|
115
|
+
}
|
|
116
|
+
return [...targetsBySource.entries()].map(([source, targets]) => ({ source, targets }));
|
|
117
|
+
}
|
|
118
|
+
export function graphFromAdjacencyList(adjacencyList, options) {
|
|
119
|
+
const nodes = [];
|
|
120
|
+
const nodeIds = new Set();
|
|
121
|
+
const edgeIds = new Set();
|
|
122
|
+
const edges = [];
|
|
123
|
+
for (const node of options.nodes ?? []) {
|
|
124
|
+
if (!nodeIds.has(node.id)) {
|
|
125
|
+
nodeIds.add(node.id);
|
|
126
|
+
nodes.push(node);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const { source, targets } of adjacencyList) {
|
|
130
|
+
addGraphInteropNode(nodes, nodeIds, source, options.getNodeLabel);
|
|
131
|
+
for (const target of targets) {
|
|
132
|
+
addGraphInteropNode(nodes, nodeIds, target, options.getNodeLabel);
|
|
133
|
+
edges.push({
|
|
134
|
+
id: uniqueGeneratedEdgeId(`${source}-${target}`, edgeIds),
|
|
135
|
+
source,
|
|
136
|
+
target
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
...graphModelFields(options),
|
|
142
|
+
nodes,
|
|
143
|
+
edges
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function pickGraphSubgraph(graph, options) {
|
|
147
|
+
const selectedNodeIds = new Set(options.nodeIds);
|
|
148
|
+
const includeConnectedEdges = options.includeConnectedEdges ?? true;
|
|
149
|
+
const includeDanglingEdges = options.includeDanglingEdges ?? false;
|
|
150
|
+
const nodes = graph.nodes.filter((node) => selectedNodeIds.has(node.id));
|
|
151
|
+
const edges = graph.edges.filter((edge) => {
|
|
152
|
+
const hasSource = selectedNodeIds.has(edge.source);
|
|
153
|
+
const hasTarget = selectedNodeIds.has(edge.target);
|
|
154
|
+
if (includeDanglingEdges) {
|
|
155
|
+
return hasSource || hasTarget;
|
|
156
|
+
}
|
|
157
|
+
if (!includeConnectedEdges) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return hasSource && hasTarget;
|
|
161
|
+
});
|
|
162
|
+
return { ...graph, nodes, edges };
|
|
163
|
+
}
|
|
164
|
+
function adjacentNodeId(edge, nodeId, direction) {
|
|
165
|
+
if (direction === "incoming") {
|
|
166
|
+
return edge.source;
|
|
167
|
+
}
|
|
168
|
+
if (direction === "outgoing") {
|
|
169
|
+
return edge.target;
|
|
170
|
+
}
|
|
171
|
+
return edge.source === nodeId ? edge.target : edge.source;
|
|
172
|
+
}
|
|
173
|
+
export function getGraphNeighborhood(graph, options) {
|
|
174
|
+
const index = createGraphIndex(graph);
|
|
175
|
+
const maxDepth = Math.max(0, Math.floor(options.maxDepth));
|
|
176
|
+
const direction = options.direction ?? "outgoing";
|
|
177
|
+
if (!index.nodeById.has(options.rootId)) {
|
|
178
|
+
return { ...graph, nodes: [], edges: [] };
|
|
179
|
+
}
|
|
180
|
+
const selectedNodeIds = new Set([options.rootId]);
|
|
181
|
+
const queue = [{ nodeId: options.rootId, depth: 0 }];
|
|
182
|
+
for (let cursor = 0; cursor < queue.length; cursor += 1) {
|
|
183
|
+
const item = queue[cursor];
|
|
184
|
+
if (!item || item.depth >= maxDepth) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const edges = direction === "both"
|
|
188
|
+
? [...(index.outgoingByNodeId.get(item.nodeId) ?? []), ...(index.incomingByNodeId.get(item.nodeId) ?? [])]
|
|
189
|
+
: direction === "incoming"
|
|
190
|
+
? (index.incomingByNodeId.get(item.nodeId) ?? [])
|
|
191
|
+
: (index.outgoingByNodeId.get(item.nodeId) ?? []);
|
|
192
|
+
for (const edge of edges) {
|
|
193
|
+
const nextNodeId = adjacentNodeId(edge, item.nodeId, direction);
|
|
194
|
+
if (selectedNodeIds.has(nextNodeId) || !index.nodeById.has(nextNodeId)) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
selectedNodeIds.add(nextNodeId);
|
|
198
|
+
queue.push({ nodeId: nextNodeId, depth: item.depth + 1 });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return pickGraphSubgraph(graph, { nodeIds: [...selectedNodeIds] });
|
|
202
|
+
}
|
|
203
|
+
export function mapGraphNodes(graph, mapper) {
|
|
204
|
+
return {
|
|
205
|
+
...graph,
|
|
206
|
+
nodes: graph.nodes.map((node, index) => mapper(node, { graph, index }))
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
export function mapGraphEdges(graph, mapper) {
|
|
210
|
+
return {
|
|
211
|
+
...graph,
|
|
212
|
+
edges: graph.edges.map((edge, index) => mapper(edge, { graph, index }))
|
|
213
|
+
};
|
|
214
|
+
}
|
|
41
215
|
export function detectGraphCycle(graph) {
|
|
42
216
|
const index = createGraphIndex(graph);
|
|
43
217
|
const visiting = new Set();
|
|
@@ -142,6 +316,119 @@ export function getReachableNodeIds(graph, startId) {
|
|
|
142
316
|
}
|
|
143
317
|
return reachable;
|
|
144
318
|
}
|
|
319
|
+
function reachableSet(graph, startId, omittedEdgeId) {
|
|
320
|
+
const index = createGraphIndex(graph);
|
|
321
|
+
const visited = new Set();
|
|
322
|
+
const queue = [];
|
|
323
|
+
if (!index.nodeById.has(startId)) {
|
|
324
|
+
return visited;
|
|
325
|
+
}
|
|
326
|
+
visited.add(startId);
|
|
327
|
+
queue.push(startId);
|
|
328
|
+
for (let cursor = 0; cursor < queue.length; cursor += 1) {
|
|
329
|
+
const nodeId = queue[cursor];
|
|
330
|
+
if (!nodeId) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
for (const edge of index.outgoingByNodeId.get(nodeId) ?? []) {
|
|
334
|
+
if (edge.id === omittedEdgeId || visited.has(edge.target) || !index.nodeById.has(edge.target)) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
visited.add(edge.target);
|
|
338
|
+
queue.push(edge.target);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return visited;
|
|
342
|
+
}
|
|
343
|
+
export function getUnreachableNodeIds(graph, startId) {
|
|
344
|
+
const reachable = reachableSet(graph, startId);
|
|
345
|
+
return graph.nodes.filter((node) => node.id !== startId && !reachable.has(node.id)).map((node) => node.id);
|
|
346
|
+
}
|
|
347
|
+
export function getIsolatedNodeIds(graph) {
|
|
348
|
+
const degrees = getGraphDegrees(graph);
|
|
349
|
+
return graph.nodes.filter((node) => degrees[node.id]?.total === 0).map((node) => node.id);
|
|
350
|
+
}
|
|
351
|
+
export function getShortestPath(graph, sourceId, targetId) {
|
|
352
|
+
const index = createGraphIndex(graph);
|
|
353
|
+
if (!index.nodeById.has(sourceId) || !index.nodeById.has(targetId)) {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
if (sourceId === targetId) {
|
|
357
|
+
return [sourceId];
|
|
358
|
+
}
|
|
359
|
+
const visited = new Set([sourceId]);
|
|
360
|
+
const queue = [[sourceId]];
|
|
361
|
+
for (let cursor = 0; cursor < queue.length; cursor += 1) {
|
|
362
|
+
const path = queue[cursor];
|
|
363
|
+
const nodeId = path?.at(-1);
|
|
364
|
+
if (!path || !nodeId) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
for (const edge of index.outgoingByNodeId.get(nodeId) ?? []) {
|
|
368
|
+
if (visited.has(edge.target) || !index.nodeById.has(edge.target)) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const nextPath = [...path, edge.target];
|
|
372
|
+
if (edge.target === targetId) {
|
|
373
|
+
return nextPath;
|
|
374
|
+
}
|
|
375
|
+
visited.add(edge.target);
|
|
376
|
+
queue.push(nextPath);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
export function getStronglyConnectedComponents(graph) {
|
|
382
|
+
const assigned = new Set();
|
|
383
|
+
const reachableByNode = new Map();
|
|
384
|
+
const components = [];
|
|
385
|
+
for (const node of graph.nodes) {
|
|
386
|
+
reachableByNode.set(node.id, reachableSet(graph, node.id));
|
|
387
|
+
}
|
|
388
|
+
for (const node of graph.nodes) {
|
|
389
|
+
if (assigned.has(node.id)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const nodeReachable = reachableByNode.get(node.id) ?? new Set();
|
|
393
|
+
const component = graph.nodes
|
|
394
|
+
.filter((candidate) => {
|
|
395
|
+
if (assigned.has(candidate.id)) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
const candidateReachable = reachableByNode.get(candidate.id) ?? new Set();
|
|
399
|
+
return nodeReachable.has(candidate.id) && candidateReachable.has(node.id);
|
|
400
|
+
})
|
|
401
|
+
.map((candidate) => candidate.id);
|
|
402
|
+
for (const componentNodeId of component) {
|
|
403
|
+
assigned.add(componentNodeId);
|
|
404
|
+
}
|
|
405
|
+
components.push(component);
|
|
406
|
+
}
|
|
407
|
+
return components;
|
|
408
|
+
}
|
|
409
|
+
export function getTransitiveReductionEdges(graph) {
|
|
410
|
+
const cycle = detectGraphCycle(graph);
|
|
411
|
+
if (cycle) {
|
|
412
|
+
return {
|
|
413
|
+
edges: [],
|
|
414
|
+
diagnostics: [
|
|
415
|
+
{
|
|
416
|
+
code: "cycle-detected",
|
|
417
|
+
severity: "error",
|
|
418
|
+
message: `Transitive reduction requires an acyclic graph; found ${cycle.join(" -> ")}.`
|
|
419
|
+
}
|
|
420
|
+
]
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
const index = createGraphIndex(graph);
|
|
424
|
+
const edges = graph.edges.filter((edge) => {
|
|
425
|
+
if (!index.nodeById.has(edge.source) || !index.nodeById.has(edge.target)) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
return !reachableSet(graph, edge.source, edge.id).has(edge.target);
|
|
429
|
+
});
|
|
430
|
+
return { edges, diagnostics: [] };
|
|
431
|
+
}
|
|
145
432
|
export function getWeaklyConnectedComponents(graph) {
|
|
146
433
|
const index = createGraphIndex(graph);
|
|
147
434
|
const visited = new Set();
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,19 +1,91 @@
|
|
|
1
1
|
import type { GraphEdge, GraphId, GraphModel, GraphNode } from "./model.js";
|
|
2
|
-
export type GraphDiagnosticCode = "missing-graph-title" | "duplicate-node-id" | "duplicate-edge-id" | "dangling-edge-source" | "dangling-edge-target" | "self-loop" | "cycle-detected" | "missing-node-label" | "missing-edge-label" | "missing-node-position" | "tree-multiple-roots" | "tree-multiple-parents" | "tree-disconnected";
|
|
3
|
-
export type GraphDiagnosticSeverity = "error" | "warning";
|
|
2
|
+
export type GraphDiagnosticCode = "missing-graph-title" | "duplicate-node-id" | "duplicate-edge-id" | "duplicate-group-id" | "dangling-edge-source" | "dangling-edge-target" | "unknown-node-group" | "self-loop" | "cycle-detected" | "missing-group-label" | "missing-node-label" | "missing-edge-label" | "missing-node-description" | "missing-edge-description" | "missing-graph-description" | "duplicate-node-label" | "duplicate-edge-label" | "fallback-disabled" | "missing-node-position" | "missing-node-size" | "duplicate-node-position" | "large-graph" | "dense-graph" | "large-fallback-content" | "large-visible-label-count" | "high-route-complexity" | "large-metadata-summary" | "parallel-edge" | "edge-label-overlap-risk" | "empty-group" | "tree-multiple-roots" | "tree-multiple-parents" | "tree-disconnected";
|
|
3
|
+
export type GraphDiagnosticSeverity = "error" | "warning" | "info";
|
|
4
|
+
export type GraphDiagnosticCategory = "accessibility" | "structure" | "layout" | "performance" | "fallback" | "tree";
|
|
5
|
+
export type GraphDiagnosticDetails = Readonly<Record<string, string | number | boolean | null>>;
|
|
6
|
+
export interface GraphDiagnosticFix {
|
|
7
|
+
readonly title: string;
|
|
8
|
+
readonly description?: string;
|
|
9
|
+
readonly target?: "graph" | "group" | "node" | "edge" | "layout";
|
|
10
|
+
}
|
|
4
11
|
export interface GraphDiagnostic {
|
|
5
12
|
readonly code: GraphDiagnosticCode;
|
|
6
13
|
readonly severity: GraphDiagnosticSeverity;
|
|
7
14
|
readonly message: string;
|
|
8
15
|
readonly nodeId?: GraphId;
|
|
9
16
|
readonly edgeId?: GraphId;
|
|
17
|
+
readonly category?: GraphDiagnosticCategory;
|
|
18
|
+
readonly details?: GraphDiagnosticDetails;
|
|
19
|
+
readonly fix?: GraphDiagnosticFix;
|
|
10
20
|
}
|
|
11
21
|
export interface ValidateGraphOptions {
|
|
12
22
|
readonly allowSelfLoops?: boolean;
|
|
13
23
|
readonly requireEdgeLabels?: boolean;
|
|
24
|
+
readonly requireGraphDescription?: boolean;
|
|
25
|
+
readonly auditAccessibility?: boolean;
|
|
26
|
+
readonly warnDuplicateLabels?: boolean;
|
|
27
|
+
readonly fallbackMode?: "list" | "tree" | "none";
|
|
14
28
|
}
|
|
15
29
|
export declare function validateGraph(graph: GraphModel, options?: ValidateGraphOptions): GraphDiagnostic[];
|
|
16
30
|
export declare function hasGraphErrors(diagnostics: readonly GraphDiagnostic[]): boolean;
|
|
31
|
+
export type GraphDiagnosticGroupBy = "severity" | "code" | "category";
|
|
32
|
+
export interface GraphDiagnosticFilter {
|
|
33
|
+
readonly severity?: GraphDiagnosticSeverity;
|
|
34
|
+
readonly code?: GraphDiagnosticCode;
|
|
35
|
+
readonly category?: GraphDiagnosticCategory;
|
|
36
|
+
readonly nodeId?: GraphId;
|
|
37
|
+
readonly edgeId?: GraphId;
|
|
38
|
+
}
|
|
39
|
+
export declare function countGraphDiagnostics(diagnostics: readonly GraphDiagnostic[], groupBy: GraphDiagnosticGroupBy): Record<string, number>;
|
|
40
|
+
export declare function groupGraphDiagnostics(diagnostics: readonly GraphDiagnostic[], groupBy: GraphDiagnosticGroupBy): Record<string, readonly GraphDiagnostic[]>;
|
|
41
|
+
export declare function filterGraphDiagnostics(diagnostics: readonly GraphDiagnostic[], filter: GraphDiagnosticFilter): readonly GraphDiagnostic[];
|
|
42
|
+
export declare function getGraphDiagnosticFixes(diagnostics: readonly GraphDiagnostic[]): readonly GraphDiagnosticFix[];
|
|
17
43
|
export declare function formatGraphDiagnostic(diagnosticEntry: GraphDiagnostic): string;
|
|
18
44
|
export declare function getNodeLabel(node: GraphNode): string;
|
|
19
45
|
export declare function getEdgeAccessibleLabel(edge: GraphEdge, graph: GraphModel): string;
|
|
46
|
+
export interface ValidateGraphLayoutOptions {
|
|
47
|
+
readonly warnFallbackRowsAt?: number;
|
|
48
|
+
readonly warnLargeGraphAt?: number;
|
|
49
|
+
readonly warnDenseGraphAt?: number;
|
|
50
|
+
readonly warnMetadataSummariesAt?: number;
|
|
51
|
+
readonly warnMissingSizes?: boolean;
|
|
52
|
+
readonly warnDuplicatePositions?: boolean;
|
|
53
|
+
readonly warnEdgeLabelOverlaps?: boolean;
|
|
54
|
+
readonly warnParallelEdges?: boolean;
|
|
55
|
+
readonly warnRoutePointsAt?: number;
|
|
56
|
+
readonly warnSelfLoops?: boolean;
|
|
57
|
+
readonly warnVisibleLabelsAt?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface GraphRenderCostThresholdOptions {
|
|
60
|
+
readonly warnFallbackRowsAt?: number;
|
|
61
|
+
readonly warnMetadataSummariesAt?: number;
|
|
62
|
+
readonly warnRoutePointsAt?: number;
|
|
63
|
+
readonly warnVisibleLabelsAt?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface GraphRenderCostEstimate {
|
|
66
|
+
readonly complexRouteCount: number;
|
|
67
|
+
readonly edgeCount: number;
|
|
68
|
+
readonly fallbackRowCount: number;
|
|
69
|
+
readonly groupCount: number;
|
|
70
|
+
readonly metadataSummaryCount: number;
|
|
71
|
+
readonly nodeCount: number;
|
|
72
|
+
readonly roughSvgElementCount: number;
|
|
73
|
+
readonly routedEdgeCount: number;
|
|
74
|
+
readonly routePointCount: number;
|
|
75
|
+
readonly visibleLabelCount: number;
|
|
76
|
+
}
|
|
77
|
+
export interface GraphHealthReportOptions extends ValidateGraphOptions, ValidateGraphLayoutOptions {
|
|
78
|
+
}
|
|
79
|
+
export interface GraphHealthReport {
|
|
80
|
+
readonly diagnostics: readonly GraphDiagnostic[];
|
|
81
|
+
readonly errors: readonly GraphDiagnostic[];
|
|
82
|
+
readonly warnings: readonly GraphDiagnostic[];
|
|
83
|
+
readonly info: readonly GraphDiagnostic[];
|
|
84
|
+
readonly countsByCode: Readonly<Record<string, number>>;
|
|
85
|
+
readonly countsByCategory: Readonly<Record<string, number>>;
|
|
86
|
+
readonly canRenderStaticSvg: boolean;
|
|
87
|
+
readonly shouldRenderFallback: boolean;
|
|
88
|
+
}
|
|
89
|
+
export declare function estimateGraphRenderCost(graph: GraphModel): GraphRenderCostEstimate;
|
|
90
|
+
export declare function validateGraphLayout(graph: GraphModel, options?: ValidateGraphLayoutOptions): GraphDiagnostic[];
|
|
91
|
+
export declare function createGraphHealthReport(graph: GraphModel, options?: GraphHealthReportOptions): GraphHealthReport;
|