@davidsouther/agathon 0.0.0-stage → 0.0.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/package.json +19 -4
- package/src/assert.ts +67 -0
- package/src/geometry/box.ts +76 -0
- package/src/geometry/circle.ts +296 -0
- package/src/geometry/geometry.ts +318 -0
- package/src/geometry/path.ts +315 -0
- package/src/geometry/quadtree.ts +321 -0
- package/src/geometry/rectangle.ts +129 -0
- package/src/geometry/rngon.ts +132 -0
- package/src/geometry/types.ts +102 -0
- package/src/geometry.ts +61 -0
- package/src/highlight/highlight.ts +85 -0
- package/src/index.ts +5 -0
- package/src/layout/grid/manager.ts +55 -0
- package/src/layout/grid.ts +1 -0
- package/src/layout/hierarchic/README.md +21 -0
- package/src/layout/hierarchic/acyclic.ts +249 -0
- package/src/layout/hierarchic/children.ts +169 -0
- package/src/layout/hierarchic/debug.ts +186 -0
- package/src/layout/hierarchic/layout.ts +266 -0
- package/src/layout/hierarchic/manager.ts +65 -0
- package/src/layout/hierarchic/normalize.ts +177 -0
- package/src/layout/hierarchic/order.ts +321 -0
- package/src/layout/hierarchic/position.ts +514 -0
- package/src/layout/hierarchic/rank.ts +629 -0
- package/src/layout/hierarchic.ts +2 -0
- package/src/layout/hulls/arrange.ts +94 -0
- package/src/layout/hulls/hulls.ts +243 -0
- package/src/layout/hulls/segments.ts +174 -0
- package/src/layout/hulls.ts +3 -0
- package/src/layout/manager/child_manager.ts +42 -0
- package/src/layout/manager/drag_manager.ts +112 -0
- package/src/layout/manager/manager.ts +425 -0
- package/src/layout/manager/pan_manager.ts +146 -0
- package/src/layout/manager/visibility_manager.ts +84 -0
- package/src/layout/manager/zoom_manager.ts +263 -0
- package/src/layout/manager.ts +6 -0
- package/src/layout/properties/defaults.ts +99 -0
- package/src/layout/properties/symbol.ts +99 -0
- package/src/layout/properties/weak_map.ts +73 -0
- package/src/layout/properties.ts +111 -0
- package/src/layout/types.ts +470 -0
- package/src/layout/util.ts +444 -0
- package/src/layout.ts +6 -0
- package/src/samples/diff_graph.ts +15 -0
- package/src/samples/edge_cases.ts +168 -0
- package/src/samples/gansner_with_states.ts +67 -0
- package/src/samples/load_generator.ts +213 -0
- package/src/samples/search_graph.ts +35 -0
- package/src/tsabre/algo.ts +126 -0
- package/src/tsabre/builder.ts +97 -0
- package/src/tsabre/compress.ts +15 -0
- package/src/tsabre/dijkstra/dijkstra.ts +76 -0
- package/src/tsabre/dijkstra/heap.ts +83 -0
- package/src/tsabre/graph.ts +808 -0
- package/src/tsabre/parse.ts +634 -0
- package/src/tsabre/print.ts +188 -0
- package/src/tsabre/sourcebuilder/code_content_buffer.ts +275 -0
- package/src/tsabre/sourcebuilder/printable.ts +33 -0
- package/src/tsabre.ts +6 -0
- package/src/types.ts +424 -0
- package/README.md +0 -3
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
*
|
|
4
|
+
* Topological layout has a number of internal properties used throughout the
|
|
5
|
+
* computation. Some are configurable by the user. To consistently get this
|
|
6
|
+
* data, instances of SmartGraph, Edge, and Node used for layout can have an
|
|
7
|
+
* optional `layout` property, with an object matching the exposed `Config`
|
|
8
|
+
* interfaces. These are merged into the default objects, in this file, before
|
|
9
|
+
* being stored in the [Symbol.Layout] property of each object. This ensures
|
|
10
|
+
* it's initialized a single time, and then always reused on future property
|
|
11
|
+
* lookups.
|
|
12
|
+
*
|
|
13
|
+
* This file also has utilities for adding virtual nodes and edges to a graph,
|
|
14
|
+
* an operation used in several places.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { assertFinite, checkExhaustive } from "../assert.ts";
|
|
18
|
+
import type { Geometry } from "../geometry/types.ts";
|
|
19
|
+
import { RNGon } from "../geometry.ts";
|
|
20
|
+
import type { Edge, FullGraph, Node, NodeId, Point, Segment, SmartGraph } from "../types.ts";
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
clearEdgeProps,
|
|
24
|
+
clearGraphProps,
|
|
25
|
+
clearNodeProps,
|
|
26
|
+
clearSegmentProps,
|
|
27
|
+
getEdgeProps,
|
|
28
|
+
getGraphProps,
|
|
29
|
+
getNodeProps,
|
|
30
|
+
getSegmentProps,
|
|
31
|
+
} from "./properties.ts";
|
|
32
|
+
import type {
|
|
33
|
+
EdgeProperties,
|
|
34
|
+
LayoutEdge,
|
|
35
|
+
LayoutGraph,
|
|
36
|
+
LayoutNode,
|
|
37
|
+
NodeProperties,
|
|
38
|
+
} from "./types.ts";
|
|
39
|
+
|
|
40
|
+
// Reexport properties fns, for backwards compatibility.
|
|
41
|
+
export {
|
|
42
|
+
clearEdgeProps,
|
|
43
|
+
clearGraphProps,
|
|
44
|
+
clearNodeProps,
|
|
45
|
+
clearSegmentProps,
|
|
46
|
+
getEdgeProps,
|
|
47
|
+
getGraphProps,
|
|
48
|
+
getNodeProps,
|
|
49
|
+
getSegmentProps,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const CacheAction = {
|
|
53
|
+
WRITE: "WRITE",
|
|
54
|
+
READ: "READ",
|
|
55
|
+
} as const;
|
|
56
|
+
type CacheAction = (typeof CacheAction)[keyof typeof CacheAction];
|
|
57
|
+
|
|
58
|
+
/** Memoize virtual nodes' ranks and orders. */
|
|
59
|
+
export function writeVirtualNodeLayoutCache(g: FullGraph): void {
|
|
60
|
+
virtualNodeLayoutCache(g, CacheAction.WRITE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Read virtual nodes' ranks and orders from cache */
|
|
64
|
+
export function readVirtualNodeLayoutCache(g: FullGraph): void {
|
|
65
|
+
virtualNodeLayoutCache(g, CacheAction.READ);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function virtualNodeLayoutCache(g: FullGraph, action: CacheAction): void {
|
|
69
|
+
const graphProps = getGraphProps(g);
|
|
70
|
+
const { virtualNodeLayout } = graphProps;
|
|
71
|
+
|
|
72
|
+
for (const node of g.allNodes) {
|
|
73
|
+
const nodeProps = getNodeProps(node);
|
|
74
|
+
|
|
75
|
+
if (nodeProps.virtual) {
|
|
76
|
+
switch (action) {
|
|
77
|
+
case CacheAction.WRITE:
|
|
78
|
+
virtualNodeLayout!.set(node.id, {
|
|
79
|
+
rank: nodeProps.rank,
|
|
80
|
+
order: nodeProps.order,
|
|
81
|
+
});
|
|
82
|
+
break;
|
|
83
|
+
case CacheAction.READ:
|
|
84
|
+
{
|
|
85
|
+
const vNodeLayoutCache = virtualNodeLayout!.get(node.id);
|
|
86
|
+
nodeProps.rank = vNodeLayoutCache!.rank;
|
|
87
|
+
nodeProps.order = vNodeLayoutCache!.order;
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
checkExhaustive(action);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Pull nodes into a 2d matrix of [rank, order] with a node id. */
|
|
98
|
+
export function buildLayerMatrix(g: SmartGraph<Node, {}>): NodeId[][] {
|
|
99
|
+
const layers: NodeId[][] = range(maxRank(g) + 1).map(() => []);
|
|
100
|
+
for (const node of g.allNodes) {
|
|
101
|
+
const id = node.id;
|
|
102
|
+
const { rank, order } = getNodeProps(node);
|
|
103
|
+
// Position in the matrix row is associated with a node's order. If we have
|
|
104
|
+
// one node with {id: '1st', rank: 0, order: 0} and another with {id: '2nd',
|
|
105
|
+
// rank: 0, order: 3}, this would create a layer ['1st', empty x 2, '2nd'].
|
|
106
|
+
// These undefined elements in the layer matrix row can lead to
|
|
107
|
+
// ReferenceErrors further in the code...[CONT.]
|
|
108
|
+
layers[rank][order] = id;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//[CONT.]...to prevent that, holes in each layer are filtered out.
|
|
112
|
+
return layers.map((layer) => layer.filter(Boolean));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Return an edge with layout set. */
|
|
116
|
+
export function makeEdge(
|
|
117
|
+
source: string,
|
|
118
|
+
target: string,
|
|
119
|
+
layout?: Partial<EdgeProperties>,
|
|
120
|
+
name?: string,
|
|
121
|
+
): Edge<{ layout: Partial<EdgeProperties> | undefined }> {
|
|
122
|
+
return { source, target, name, layout };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Find the highest rank value in a graph.
|
|
127
|
+
*/
|
|
128
|
+
export function maxRank(g: SmartGraph<Node, {}>): number {
|
|
129
|
+
const ranks = Array.from(g.allNodes).map((node) =>
|
|
130
|
+
assertFinite(getNodeProps(node).rank, `Invalid rank ${getNodeProps(node).rank} for ${node.id}`),
|
|
131
|
+
);
|
|
132
|
+
// Include 0, so that no nodes still returns a valid max rank.
|
|
133
|
+
return Math.max(0, ...ranks);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Generate an array with n undefined values. */
|
|
137
|
+
export function range(n: number): undefined[] {
|
|
138
|
+
return new Array(n).fill(undefined);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Virtual nodes have a type corresponding to their usage. */
|
|
142
|
+
export const Virtual = {
|
|
143
|
+
EDGE: "edge",
|
|
144
|
+
BORDER: "border",
|
|
145
|
+
ROOT: "root",
|
|
146
|
+
} as const;
|
|
147
|
+
export type Virtual = (typeof Virtual)[keyof typeof Virtual];
|
|
148
|
+
|
|
149
|
+
let vnodeIdx = 0;
|
|
150
|
+
/**
|
|
151
|
+
* Create a virtual node.
|
|
152
|
+
*/
|
|
153
|
+
export function addVirtualNode(
|
|
154
|
+
g: FullGraph,
|
|
155
|
+
virtual: Virtual,
|
|
156
|
+
layout: Partial<NodeProperties>,
|
|
157
|
+
id?: NodeId,
|
|
158
|
+
prefix = "",
|
|
159
|
+
): NodeId {
|
|
160
|
+
layout.virtual = virtual;
|
|
161
|
+
|
|
162
|
+
if (!id) {
|
|
163
|
+
do {
|
|
164
|
+
id = `(϶${prefix}${vnodeIdx++})`;
|
|
165
|
+
} while (g.hasNode(id));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const node: LayoutNode = { id, layout };
|
|
169
|
+
g.setNode(id, node);
|
|
170
|
+
|
|
171
|
+
return id;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Valid border directions */
|
|
175
|
+
export const Border = {
|
|
176
|
+
TOP: "_bt_",
|
|
177
|
+
BOTTOM: "_bb_",
|
|
178
|
+
} as const;
|
|
179
|
+
export type Border = (typeof Border)[keyof typeof Border];
|
|
180
|
+
|
|
181
|
+
/** Add a virtual border node */
|
|
182
|
+
export function addBorderNode(g: FullGraph, prefix: Border, rank?: number, order?: number): NodeId {
|
|
183
|
+
const layout: Partial<NodeProperties> = {
|
|
184
|
+
geometry: RNGon.point(),
|
|
185
|
+
rank,
|
|
186
|
+
order,
|
|
187
|
+
};
|
|
188
|
+
return addVirtualNode(g, Virtual.BORDER, layout, undefined, prefix);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create an initial layout matrix, with no ordering constraints. While this is
|
|
193
|
+
* a terrible order in practice, it's a convenient first step for further
|
|
194
|
+
* ordering calculations. When used for debugging, will use the order as set on
|
|
195
|
+
* the node if called after ordering has taken place.
|
|
196
|
+
*/
|
|
197
|
+
export function initOrder(g: FullGraph): NodeId[][] {
|
|
198
|
+
const visited = new Set<NodeId>();
|
|
199
|
+
// Working from the topmost rank down, assign initial ranks to subgraph leaf
|
|
200
|
+
// nodes.
|
|
201
|
+
const simpleNodes = Array.from(g.nodes)
|
|
202
|
+
.filter((id) => g.children(id).size === 0)
|
|
203
|
+
.sort((a, b) => {
|
|
204
|
+
const rankA = getNodeProps(g.node(a)!).rank;
|
|
205
|
+
const rankB = getNodeProps(g.node(b)!).rank;
|
|
206
|
+
return rankA - rankB;
|
|
207
|
+
});
|
|
208
|
+
const layers: NodeId[][] = [];
|
|
209
|
+
for (const id of simpleNodes) {
|
|
210
|
+
iterate(id);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Ensure all layers are initialized
|
|
214
|
+
for (let i = 0; i < layers.length; i++) {
|
|
215
|
+
layers[i] = layers[i] || [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return layers;
|
|
219
|
+
|
|
220
|
+
// Just push each node onto layers in whatever order it happens to be found.
|
|
221
|
+
function iterate(id: NodeId) {
|
|
222
|
+
if (visited.has(id)) return;
|
|
223
|
+
visited.add(id);
|
|
224
|
+
|
|
225
|
+
const { rank } = getNodeProps(g.node(id)!);
|
|
226
|
+
if (layers[rank] === undefined) {
|
|
227
|
+
layers[rank] = [];
|
|
228
|
+
}
|
|
229
|
+
layers[rank].push(id);
|
|
230
|
+
|
|
231
|
+
for (const cid of g.successors(id)) iterate(cid);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get the correct coordinates for a node, taking fixed values over updated
|
|
237
|
+
* values.
|
|
238
|
+
*/
|
|
239
|
+
export function position(node: Node): Point {
|
|
240
|
+
const {
|
|
241
|
+
geometry: { x, y },
|
|
242
|
+
fx,
|
|
243
|
+
fy,
|
|
244
|
+
} = getNodeProps(node);
|
|
245
|
+
// Use "fixed" fx, fy if provided, otherwise
|
|
246
|
+
return {
|
|
247
|
+
x: fx ?? x,
|
|
248
|
+
y: fy ?? y,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Returns a line segment with ends on borders of given nodes and lying on the
|
|
254
|
+
* line defined by nodes center points.
|
|
255
|
+
*/
|
|
256
|
+
export function getLineSegmentBetweenNodes(
|
|
257
|
+
nodeA: Geometry,
|
|
258
|
+
nodeB: Geometry,
|
|
259
|
+
): { source: Point; target: Point } {
|
|
260
|
+
const s = nodeA.clip([nodeB, nodeA]);
|
|
261
|
+
const t = nodeB.clip([nodeA, nodeB]);
|
|
262
|
+
|
|
263
|
+
return { source: s, target: t };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Given two points, P0 & P1, and a Y value, find the point P1' along P0->P1
|
|
268
|
+
* such that P1'.Y = Y. Update P1 to be P1'. This modifies the referenced point.
|
|
269
|
+
*/
|
|
270
|
+
export function projectPointAlongLineToYValue(p0: Readonly<Point>, p1: Point, endY: number): void {
|
|
271
|
+
const { x: x0, y: y0 } = p0;
|
|
272
|
+
const { x: x1, y: y1 } = p1;
|
|
273
|
+
const dx = x1 - x0;
|
|
274
|
+
const dy = y1 - y0;
|
|
275
|
+
const t = (endY - y0) / dy;
|
|
276
|
+
const endX = x0 + t * dx;
|
|
277
|
+
p1.x = endX;
|
|
278
|
+
p1.y = endY;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Given a graph and a node, find all segments that could be affected by
|
|
283
|
+
* moving that node. Those segments are the segment neighborhood of the root
|
|
284
|
+
* ancestor of the given node. Segments below collapsed nodes are not included.
|
|
285
|
+
* The iterable will iterate nodes from top to bottom.
|
|
286
|
+
*/
|
|
287
|
+
export function findSegmentNeighborhood(
|
|
288
|
+
graph: LayoutGraph,
|
|
289
|
+
id?: NodeId,
|
|
290
|
+
): Iterable<Segment<LayoutEdge>> {
|
|
291
|
+
const neighborhood = new Set<Segment<LayoutEdge>>();
|
|
292
|
+
|
|
293
|
+
const findSegmentSegments = (segment: Segment<LayoutEdge>): void => {
|
|
294
|
+
for (const previous of segment.previous) {
|
|
295
|
+
if (!graph.node(previous.target)!.expanded) continue;
|
|
296
|
+
neighborhood.add(previous);
|
|
297
|
+
findNodeSegments(previous.source);
|
|
298
|
+
findSegmentSegments(previous);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const findNodeSegments = (id?: NodeId): void => {
|
|
303
|
+
if (graph.hasNode(id) && !graph.node(id)!.expanded) return;
|
|
304
|
+
for (const segment of graph.childSegments(id)) {
|
|
305
|
+
neighborhood.add(segment);
|
|
306
|
+
findSegmentSegments(segment);
|
|
307
|
+
}
|
|
308
|
+
for (const child of graph.children(id)) {
|
|
309
|
+
findNodeSegments(child);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Include segments below this node
|
|
314
|
+
findNodeSegments(id);
|
|
315
|
+
|
|
316
|
+
// Include segments into and out from this node, if present
|
|
317
|
+
if (id) {
|
|
318
|
+
for (const segment of graph.nodeSegments(id)) {
|
|
319
|
+
neighborhood.add(segment);
|
|
320
|
+
findSegmentSegments(segment);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return neighborhood;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Given an item, determine at runtime whether it's a Node or a Segment.
|
|
329
|
+
*/
|
|
330
|
+
export function isSegment(entity: NodeId | Segment<LayoutEdge>): entity is Segment<LayoutEdge> {
|
|
331
|
+
return (entity as Segment<LayoutEdge>).source !== undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Returns a bottom-up breadth-first traversal of topNode's children tree. If no
|
|
336
|
+
* topNode given, returns the entire tree's traversal. Nodes that have an
|
|
337
|
+
* unexpanded ancestor do not get into the traversal, as they do not need to be
|
|
338
|
+
* recalculated. Based on code taken from: https://stackoverflow.com/a/23981550
|
|
339
|
+
*/
|
|
340
|
+
export function bottomUpBFTraversal(graph: LayoutGraph, topNode?: NodeId): NodeId[] {
|
|
341
|
+
let neighborhood: NodeId[];
|
|
342
|
+
let hulls: Array<Set<NodeId>>;
|
|
343
|
+
|
|
344
|
+
if (topNode) {
|
|
345
|
+
hulls = [graph.children(topNode)];
|
|
346
|
+
neighborhood = [topNode];
|
|
347
|
+
} else {
|
|
348
|
+
hulls = [graph.children()];
|
|
349
|
+
neighborhood = [];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
while (hulls.length > 0) {
|
|
353
|
+
const hull: Set<NodeId> = hulls.shift()!;
|
|
354
|
+
neighborhood.unshift(...hull);
|
|
355
|
+
|
|
356
|
+
for (const id of hull) {
|
|
357
|
+
if (graph.node(id)!.expanded && graph.hasChildren(id)) {
|
|
358
|
+
hulls.push(graph.children(id));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return neighborhood;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* staggerRow will take a single rank and "stagger" adjacent items. This
|
|
368
|
+
* reduces visual density along long single ranks, and improves visibility
|
|
369
|
+
* of such ranks. It works by creating "rows" for every few items, as
|
|
370
|
+
* specified by staggerRows.
|
|
371
|
+
*
|
|
372
|
+
* For instance, with staggerRows = 3 and with 8 items, it would turn this
|
|
373
|
+
* rank
|
|
374
|
+
*
|
|
375
|
+
* 0 0 0 0 0 0 0 0
|
|
376
|
+
*
|
|
377
|
+
* into
|
|
378
|
+
*
|
|
379
|
+
* 0 0 0
|
|
380
|
+
* 0 0 0
|
|
381
|
+
* 0 0
|
|
382
|
+
*
|
|
383
|
+
* For further ranks, this rank's height is the sum of the height of the rows.
|
|
384
|
+
* rankSep does not apply between rows within ranks, it only applies between
|
|
385
|
+
* ranks. All nodes in a row have the same Y center position. A row's height
|
|
386
|
+
* is the height of the largest single node within the rank.
|
|
387
|
+
*
|
|
388
|
+
* The default staggerRows is set to 10.
|
|
389
|
+
*/
|
|
390
|
+
export function staggerRow(
|
|
391
|
+
g: LayoutGraph,
|
|
392
|
+
layer: NodeId[],
|
|
393
|
+
coordinates: Record<NodeId, number>,
|
|
394
|
+
staggerRows: number = Math.sqrt(layer.length),
|
|
395
|
+
): number {
|
|
396
|
+
if (layer.length === 0) return 40;
|
|
397
|
+
|
|
398
|
+
const rowSpread = Math.ceil(layer.length / staggerRows);
|
|
399
|
+
|
|
400
|
+
const rowMaxHeights: number[] = [];
|
|
401
|
+
// Determine the max height for every staggered row.
|
|
402
|
+
for (let i = 0; i < layer.length; i++) {
|
|
403
|
+
const j = i % rowSpread;
|
|
404
|
+
const node = g.node(layer[i])!;
|
|
405
|
+
rowMaxHeights[j] = Math.max(
|
|
406
|
+
rowMaxHeights[j] || 0,
|
|
407
|
+
node ? getNodeProps(node).geometry.height : 0,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const rowHeights: number[] = [rowMaxHeights[0] / 2];
|
|
412
|
+
for (let i = 1; i < rowMaxHeights.length; i++) {
|
|
413
|
+
rowHeights[i] = rowHeights[i - 1] + rowMaxHeights[i] / 2;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (let i = 0; i < layer.length; i++) {
|
|
417
|
+
const j = i % rowSpread;
|
|
418
|
+
const id = layer[i];
|
|
419
|
+
coordinates[id] = rowHeights[j];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return rowHeights[rowSpread - 1] + rowMaxHeights[rowMaxHeights.length - 1] / 2;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Given a layer of nodes, distribute them evenly along the horizontal axis.
|
|
427
|
+
*/
|
|
428
|
+
export function distributeHorizontalLayer(
|
|
429
|
+
g: LayoutGraph,
|
|
430
|
+
layer: NodeId[],
|
|
431
|
+
coordinates: Record<NodeId, number>,
|
|
432
|
+
): number {
|
|
433
|
+
let width = 0;
|
|
434
|
+
const padding = getGraphProps(g).nodeSep;
|
|
435
|
+
for (const id of layer) {
|
|
436
|
+
const { geometry } = getNodeProps(g.node(id)!);
|
|
437
|
+
coordinates[id] = width + geometry.width / 2;
|
|
438
|
+
width += geometry.width;
|
|
439
|
+
width += padding;
|
|
440
|
+
}
|
|
441
|
+
width -= padding;
|
|
442
|
+
|
|
443
|
+
return width;
|
|
444
|
+
}
|
package/src/layout.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * as grid from "./layout/grid.ts";
|
|
2
|
+
export * from "./layout/hierarchic.ts";
|
|
3
|
+
export * as manager from "./layout/manager.ts";
|
|
4
|
+
export * as properties from "./layout/properties.ts";
|
|
5
|
+
export * as types from "./layout/types.ts";
|
|
6
|
+
export * as util from "./layout/util.ts";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Basic diff test graph. */
|
|
2
|
+
export const DIFF_GRAPH_TEST = `digraph {
|
|
3
|
+
a [presence=both];
|
|
4
|
+
b [presence="left"];
|
|
5
|
+
c [presence=both];
|
|
6
|
+
d [presence="right"];
|
|
7
|
+
|
|
8
|
+
a -> b [presence="left"];
|
|
9
|
+
b -> c [presence="left"];
|
|
10
|
+
c -> a [presence="left"];
|
|
11
|
+
|
|
12
|
+
a -> c [presence="right"];
|
|
13
|
+
c -> d [presence="right"];
|
|
14
|
+
d -> a [presence="right"];
|
|
15
|
+
}`;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gansner graph from Gansner paper
|
|
3
|
+
*
|
|
4
|
+
* a
|
|
5
|
+
* /|\
|
|
6
|
+
* bef
|
|
7
|
+
* |||
|
|
8
|
+
* c\|
|
|
9
|
+
* | g
|
|
10
|
+
* \ /
|
|
11
|
+
* h
|
|
12
|
+
*/
|
|
13
|
+
export const GANSNER_GRAPH = `digraph {
|
|
14
|
+
a -> b;
|
|
15
|
+
b -> c;
|
|
16
|
+
c -> d;
|
|
17
|
+
d -> h;
|
|
18
|
+
a -> e;
|
|
19
|
+
e -> g;
|
|
20
|
+
g -> h;
|
|
21
|
+
a -> f;
|
|
22
|
+
f -> g;
|
|
23
|
+
}`;
|
|
24
|
+
|
|
25
|
+
/** Fixed rank diamond */
|
|
26
|
+
export const FIXED_DIAMOND = `digraph {
|
|
27
|
+
a [rank=1; fixedRank=true];
|
|
28
|
+
b [rank=2; fixedRank=true];
|
|
29
|
+
c [rank=3; fixedRank=true];
|
|
30
|
+
d [rank=4; fixedRank=true];
|
|
31
|
+
|
|
32
|
+
a -> b;
|
|
33
|
+
a -> c;
|
|
34
|
+
b -> d;
|
|
35
|
+
c -> d;
|
|
36
|
+
}`;
|
|
37
|
+
|
|
38
|
+
/** A simple 2-hulled example showing traffic between cousins. */
|
|
39
|
+
export const TWO_HULLS = `digraph {
|
|
40
|
+
A [status = good] {
|
|
41
|
+
AA { AAA {AAAA;} AAB; }
|
|
42
|
+
}
|
|
43
|
+
B [status = bad] {
|
|
44
|
+
BB;
|
|
45
|
+
}
|
|
46
|
+
AAA -> BB;
|
|
47
|
+
AAB -> BB;
|
|
48
|
+
AAAA -> BB;
|
|
49
|
+
}`;
|
|
50
|
+
|
|
51
|
+
/** Very deep hulls, to test child positioning. */
|
|
52
|
+
export const DEEP_HULLS = `digraph {
|
|
53
|
+
A {
|
|
54
|
+
AA {
|
|
55
|
+
AAA { AAAA; AAAB; }
|
|
56
|
+
AAB { AABA; AABB; }
|
|
57
|
+
}
|
|
58
|
+
AB {
|
|
59
|
+
ABA { ABAA; ABAB; }
|
|
60
|
+
ABB { ABBA; ABBB; }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
B {
|
|
64
|
+
BA {
|
|
65
|
+
BAA { BAAA; BAAB; }
|
|
66
|
+
BAB { BABA; BABB; }
|
|
67
|
+
}
|
|
68
|
+
BB {
|
|
69
|
+
BBA { BBAA; BBAB; }
|
|
70
|
+
BBB { BBBA; BBBB; }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}`;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A small cyclic graph who's edge c -> a should be reversed, as it's the
|
|
77
|
+
* lightest.
|
|
78
|
+
*/
|
|
79
|
+
export const SMALL_REVERSED = `digraph {
|
|
80
|
+
a -> b[weight=2;];
|
|
81
|
+
b -> c[weight=2;];
|
|
82
|
+
c -> a[weight=1;];
|
|
83
|
+
c -> d[weight=2;];
|
|
84
|
+
c -> e[weight=2;];
|
|
85
|
+
d -> e[weight=2;];
|
|
86
|
+
}`;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A large acyclic graph, loosely modeled after the GraphViz 0.11 Unices
|
|
90
|
+
* sample.
|
|
91
|
+
*/
|
|
92
|
+
export const LARGE_GRAPH = `digraph {
|
|
93
|
+
[printDebug=true]
|
|
94
|
+
Tiger -> Lion;
|
|
95
|
+
Tiger -> Alligator;
|
|
96
|
+
Lion -> Bison;
|
|
97
|
+
Lion -> Shark;
|
|
98
|
+
Lion -> Ant;
|
|
99
|
+
Lion -> Wollongong;
|
|
100
|
+
Lion -> Zebra;
|
|
101
|
+
Zebra -> Deer;
|
|
102
|
+
Zebra -> Hippo;
|
|
103
|
+
Zebra -> Pig;
|
|
104
|
+
Pig -> Giraffe;
|
|
105
|
+
Pig -> Crab;
|
|
106
|
+
Pig -> Cow;
|
|
107
|
+
Pig -> Oyster;
|
|
108
|
+
Pig -> Lemur;
|
|
109
|
+
Pig -> Jaguar;
|
|
110
|
+
Cow -> Oyster;
|
|
111
|
+
Giraffe -> Kangaroo;
|
|
112
|
+
Shark -> Otter;
|
|
113
|
+
Otter -> Goat;
|
|
114
|
+
Goat -> Oyster;
|
|
115
|
+
Goat -> Lynx;
|
|
116
|
+
Crab -> Koala;
|
|
117
|
+
Koala -> Kiwi;
|
|
118
|
+
Kiwi -> Horse;
|
|
119
|
+
Horse -> Frog;
|
|
120
|
+
Horse -> Goat;
|
|
121
|
+
Horse -> Giraffe;
|
|
122
|
+
Frog -> Newt;
|
|
123
|
+
Frog -> Magpie;
|
|
124
|
+
Alligator -> Duck;
|
|
125
|
+
Alligator -> Snake;
|
|
126
|
+
Duck -> Hippo;
|
|
127
|
+
Snake -> Chicken;
|
|
128
|
+
Snake -> Eagle;
|
|
129
|
+
Chicken -> Butterfly;
|
|
130
|
+
Butterfly -> Turtle;
|
|
131
|
+
Turtle -> Rabbit;
|
|
132
|
+
Turtle -> Parrot;
|
|
133
|
+
Eagle -> Owl;
|
|
134
|
+
Owl -> Deer;
|
|
135
|
+
Hippo -> Deer;
|
|
136
|
+
Sheep -> Deer;
|
|
137
|
+
Deer -> Monkey;
|
|
138
|
+
Rabbit -> Monkey;
|
|
139
|
+
Turtle -> Monkey;
|
|
140
|
+
Monkey -> Bird;
|
|
141
|
+
Bird -> Fish;
|
|
142
|
+
Fish -> Mole;
|
|
143
|
+
}`;
|
|
144
|
+
|
|
145
|
+
/** A graph with a search and a focus. */
|
|
146
|
+
export const SEARCH_MATCH = `digraph {
|
|
147
|
+
[search="weasley"; match="fred";]
|
|
148
|
+
gryffindor;
|
|
149
|
+
ron_weasley;
|
|
150
|
+
fred_weasley;
|
|
151
|
+
george_weasley;
|
|
152
|
+
}`;
|
|
153
|
+
|
|
154
|
+
/** Small node-pool example for developing the expanded view */
|
|
155
|
+
export const NODE_POOL = `digraph {
|
|
156
|
+
A; B; C;
|
|
157
|
+
N [expanded=true; type="parent";] { NA; NB; NC; }
|
|
158
|
+
A -> NA; B -> NB; C -> NC;
|
|
159
|
+
}`;
|
|
160
|
+
|
|
161
|
+
/** Graph that has multiple geometries */
|
|
162
|
+
export const GEOMETRIES = `digraph {
|
|
163
|
+
A [geometry='{"type": "rngon", "n": 3, "radius": 12}'];
|
|
164
|
+
B [radius='35'];
|
|
165
|
+
C [geometry='{"type": "square", "side": 12}'];
|
|
166
|
+
A -> C;
|
|
167
|
+
B -> C;
|
|
168
|
+
}`;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Good bounds for snapshot tests. */
|
|
2
|
+
export const GANSNER_WITH_STATES_BOUNDS = {
|
|
3
|
+
width: 800,
|
|
4
|
+
height: 1000,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/** The Marquee "Put everything in" graph. */
|
|
8
|
+
export const GANSNER_WITH_STATES = `digraph {
|
|
9
|
+
[search="a"; match="aa"; icon="icons:workload"]
|
|
10
|
+
|
|
11
|
+
a -> b;
|
|
12
|
+
b -> c;
|
|
13
|
+
c -> d;
|
|
14
|
+
d -> h;
|
|
15
|
+
a -> e;
|
|
16
|
+
e -> g [presence = left];
|
|
17
|
+
g -> h [presence = left];
|
|
18
|
+
a -> f;
|
|
19
|
+
f -> g [presence = left];
|
|
20
|
+
|
|
21
|
+
b [chip="status-warning"];
|
|
22
|
+
|
|
23
|
+
a [expanded=true; chip="status-success"] {
|
|
24
|
+
aa [expanded=true; chip="status-success"] {
|
|
25
|
+
aaa [chip="status-success"];
|
|
26
|
+
aab [chip="status-success"];
|
|
27
|
+
}
|
|
28
|
+
ab [chip="status-success"] {
|
|
29
|
+
aba [chip="status-success"];
|
|
30
|
+
abb [chip="status-success"];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
c [expanded = true; chip="status-error"; filtered=true] {
|
|
35
|
+
ca [chip="status-error"; presence=left; filtered=true]{
|
|
36
|
+
caa [chip="status-error"; presence=left; filtered=true];
|
|
37
|
+
cab [chip="status-error"; presence=left; filtered=true];
|
|
38
|
+
}
|
|
39
|
+
cb [chip="status-error"; presence = right; filtered=true];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
f [matched = true];
|
|
43
|
+
|
|
44
|
+
g [expanded=true; selected=true; presence=left] {
|
|
45
|
+
ga [presence = left];
|
|
46
|
+
gb [presence = left];
|
|
47
|
+
gc [selected = true; presence=left];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ab -> b [presence = left];
|
|
51
|
+
ab -> f [presence = right];
|
|
52
|
+
aab -> e;
|
|
53
|
+
aab -> f;
|
|
54
|
+
aaa -> e;
|
|
55
|
+
aaa -> f;
|
|
56
|
+
aba -> e;
|
|
57
|
+
b -> ca [presence=left];
|
|
58
|
+
b -> cb [presence=right];
|
|
59
|
+
ca -> d [presence=left];
|
|
60
|
+
e -> gc [presence=left];
|
|
61
|
+
e -> gb [presence=left];
|
|
62
|
+
e -> ga [presence=left];
|
|
63
|
+
f -> gc [presence=left];
|
|
64
|
+
e -> h [presence=right];
|
|
65
|
+
f -> h [presence=right];
|
|
66
|
+
}
|
|
67
|
+
`;
|