@davidsouther/agathon 0.0.0-stage → 2026.11.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.
- 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,263 @@
|
|
|
1
|
+
import type { Geometry } from "../../geometry/types.ts";
|
|
2
|
+
import { CircleGeometry } from "../../geometry.ts";
|
|
3
|
+
import type { Point, Segment } from "../../types.ts";
|
|
4
|
+
import type { LayoutEdge, LayoutNode, SegmentPosition } from "../types.ts";
|
|
5
|
+
import {
|
|
6
|
+
getGraphProps,
|
|
7
|
+
getLineSegmentBetweenNodes,
|
|
8
|
+
getNodeProps,
|
|
9
|
+
getSegmentProps,
|
|
10
|
+
} from "../util.ts";
|
|
11
|
+
|
|
12
|
+
import type { NoopLayoutManager } from "./manager.ts";
|
|
13
|
+
|
|
14
|
+
const ZOOM_LEVELS_PER_WHEEL = 1;
|
|
15
|
+
const ZOOM_EXPANSION_RATIO = 10;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Noop zoom manager which handles setting positions but does not update zoom
|
|
19
|
+
* level.
|
|
20
|
+
*/
|
|
21
|
+
export class NoopZoomManager {
|
|
22
|
+
protected zoomLevel = 0;
|
|
23
|
+
protected expansionFactor = 1;
|
|
24
|
+
|
|
25
|
+
get level(): number {
|
|
26
|
+
return this.zoomLevel;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected readonly layoutManager: NoopLayoutManager;
|
|
30
|
+
constructor(layoutManager: NoopLayoutManager) {
|
|
31
|
+
this.layoutManager = layoutManager;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
dragAmount({ x, y }: Point): Point {
|
|
35
|
+
return {
|
|
36
|
+
x: x / this.expansionFactor,
|
|
37
|
+
y: y / this.expansionFactor,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
zoomIn(center?: Point): void {
|
|
42
|
+
this.zoom(this.zoomLevel + ZOOM_LEVELS_PER_WHEEL, center);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
zoomOut(center?: Point): void {
|
|
46
|
+
this.zoom(this.zoomLevel - ZOOM_LEVELS_PER_WHEEL, center);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
zoom(level: number, center: Point | undefined): void {
|
|
50
|
+
// noop
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Given a point, project it along its axis away from the center of the
|
|
55
|
+
* layout by `expansionFactor` amount. That is, if the point was 5 units from
|
|
56
|
+
* the center, and the expansionFactor is 2 (zoom level 10), the new position
|
|
57
|
+
* would be 10 units away along the same line.
|
|
58
|
+
*/
|
|
59
|
+
diffusePoint({ x, y }: Point, { x: cx, y: cy } = this.layoutManager.center): Point {
|
|
60
|
+
return {
|
|
61
|
+
x: place(x, this.expansionFactor, cx),
|
|
62
|
+
y: place(y, this.expansionFactor, cy),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
undiffusePoint({ x, y }: Point, { x: cx, y: cy } = this.layoutManager.center): Point {
|
|
67
|
+
return {
|
|
68
|
+
x: place(x, 1 / this.expansionFactor, cx),
|
|
69
|
+
y: place(y, 1 / this.expansionFactor, cy),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Given a node and its position, calculate a new node position with all
|
|
75
|
+
* points diffused by expansionFactor away. Top level nodes are moved
|
|
76
|
+
* relative to the center of the layout; lower nodes are moved relative to the
|
|
77
|
+
* <0, 0> center of the hull. Hull sizes are recalculated as the smallest
|
|
78
|
+
* bounding circle.
|
|
79
|
+
*/
|
|
80
|
+
diffuseNodePosition(node: LayoutNode, position: Geometry): Geometry {
|
|
81
|
+
const { baseRadius } = getGraphProps(this.layoutManager.graph);
|
|
82
|
+
const isTopLevel = !this.layoutManager.graph.hasNode(this.layoutManager.graph.parent(node.id));
|
|
83
|
+
position = position.clone();
|
|
84
|
+
position.moveTo(this.diffusePoint(position, isTopLevel ? undefined : { x: 0, y: 0 }));
|
|
85
|
+
|
|
86
|
+
if (node.expanded && this.layoutManager.graph.hasChildren(node.id)) {
|
|
87
|
+
// only recalculate node size if this expanded node has children. If it
|
|
88
|
+
// doesn't have children, the size is set by a constraint other than
|
|
89
|
+
// "smallest enclosing circle"
|
|
90
|
+
// Recursively layout all children first
|
|
91
|
+
const children: Geometry[] = [...this.layoutManager.graph.children(node.id)].map((cid) => {
|
|
92
|
+
try {
|
|
93
|
+
return this.layoutManager.getNodePosition(cid);
|
|
94
|
+
} catch (e: unknown) {
|
|
95
|
+
// TODO: Handle this exception better.
|
|
96
|
+
return getNodeProps(this.layoutManager.getNode(cid)).geometry;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Find the smallest bounding circle
|
|
101
|
+
position.fitTo(children, baseRadius);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return position;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Given a segment, diffuse its position, but keep endpoints connected to
|
|
109
|
+
* their node positions. Node positions must be calculated before calculating
|
|
110
|
+
* segment positions.
|
|
111
|
+
*/
|
|
112
|
+
diffuseSegmentPosition(segment: Segment<LayoutEdge>, position: SegmentPosition): SegmentPosition {
|
|
113
|
+
if (!segment.next) {
|
|
114
|
+
position = this.diffuseSiblingSegment(segment, position);
|
|
115
|
+
} else {
|
|
116
|
+
position = this.diffuseRisingSegment(segment, position);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
position = this.diffuseSegmentLabel(segment, position);
|
|
120
|
+
|
|
121
|
+
return position;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected diffuseSiblingSegment(
|
|
125
|
+
segment: Segment<LayoutEdge>,
|
|
126
|
+
position: SegmentPosition,
|
|
127
|
+
): SegmentPosition {
|
|
128
|
+
const length = position.points.length;
|
|
129
|
+
|
|
130
|
+
const { points } = getSegmentProps(segment);
|
|
131
|
+
for (let i = 0; i < length; i++) {
|
|
132
|
+
position.points[i] = this.diffusePoint(points[i]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return this.clipSiblingSegment(segment, position);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
protected clipSiblingSegment(
|
|
139
|
+
segment: Segment<LayoutEdge>,
|
|
140
|
+
position: SegmentPosition,
|
|
141
|
+
): SegmentPosition {
|
|
142
|
+
const length = position.points.length;
|
|
143
|
+
if (length < 2) {
|
|
144
|
+
// It's unclear how a segment will have less than two points. However that
|
|
145
|
+
// happened, there's no way to "correct" the end points relative to one
|
|
146
|
+
// another, so we skip fixing the first and last point to the node
|
|
147
|
+
// centers.
|
|
148
|
+
return position;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const source = this.layoutManager.getNodePosition(segment.source);
|
|
152
|
+
const target = this.layoutManager.getNodePosition(segment.target);
|
|
153
|
+
|
|
154
|
+
const hasIntermediatePoints = length > 2;
|
|
155
|
+
const nextPoint = hasIntermediatePoints ? CircleGeometry.point(position.points[1]) : target;
|
|
156
|
+
const lastPoint = hasIntermediatePoints
|
|
157
|
+
? CircleGeometry.point(position.points[length - 2])
|
|
158
|
+
: source;
|
|
159
|
+
|
|
160
|
+
position.points[0] = getLineSegmentBetweenNodes(source, nextPoint).source;
|
|
161
|
+
position.points[length - 1] = getLineSegmentBetweenNodes(lastPoint, target).target;
|
|
162
|
+
|
|
163
|
+
return position;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected diffuseRisingSegment(
|
|
167
|
+
segment: Segment<LayoutEdge>,
|
|
168
|
+
position: SegmentPosition,
|
|
169
|
+
): SegmentPosition {
|
|
170
|
+
const next = segment.next;
|
|
171
|
+
if (!next) return this.diffuseSiblingSegment(segment, position);
|
|
172
|
+
const nextPosition = this.layoutManager.getSegmentPosition(next);
|
|
173
|
+
|
|
174
|
+
// source point is source node position, source node radius
|
|
175
|
+
const source = this.layoutManager.getNodePosition(segment.source);
|
|
176
|
+
|
|
177
|
+
// target point is segment.next.points[0] (or [l] if segment is
|
|
178
|
+
// top-level-reversed), translated to local node space
|
|
179
|
+
const nextPoint = next.source === segment.target ? 0 : nextPosition.points.length - 1;
|
|
180
|
+
const targetPoint = { ...nextPosition.points[nextPoint] };
|
|
181
|
+
const target = this.layoutManager.getNodePosition(segment.target);
|
|
182
|
+
targetPoint.x -= target.x;
|
|
183
|
+
targetPoint.y -= target.y;
|
|
184
|
+
|
|
185
|
+
const line = getLineSegmentBetweenNodes(source, CircleGeometry.point(targetPoint));
|
|
186
|
+
position.points[0] = line.source;
|
|
187
|
+
position.points[1] = line.target;
|
|
188
|
+
position.points.length = 2;
|
|
189
|
+
|
|
190
|
+
return position;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private diffuseSegmentLabel(
|
|
194
|
+
segment: Segment<LayoutEdge>,
|
|
195
|
+
position: SegmentPosition,
|
|
196
|
+
): SegmentPosition {
|
|
197
|
+
if (position.points.length < 2) return position;
|
|
198
|
+
|
|
199
|
+
const median = (position.points.length / 2) | 0;
|
|
200
|
+
let { x: lsx, y: lsy } = position.points[median - 1];
|
|
201
|
+
let { x: ltx, y: lty } = position.points[median];
|
|
202
|
+
if (lsx > ltx) {
|
|
203
|
+
const [ttx, tty] = [ltx, lty];
|
|
204
|
+
[ltx, lty] = [lsx, lsy];
|
|
205
|
+
[lsx, lsy] = [ttx, tty];
|
|
206
|
+
position.label.flippedBy180 = true;
|
|
207
|
+
} else {
|
|
208
|
+
position.label.flippedBy180 = false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
position.label.position.x = Math.round(lsx / 2 + ltx / 2);
|
|
212
|
+
position.label.position.y = Math.round(lsy / 2 + lty / 2);
|
|
213
|
+
position.label.rotation = (Math.atan2(lty - lsy, ltx - lsx) * 180) / Math.PI;
|
|
214
|
+
|
|
215
|
+
return position;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Zoom manager which actually updates zoom based on requested levels. */
|
|
220
|
+
export class ZoomManager extends NoopZoomManager {
|
|
221
|
+
constructor(layoutManager: NoopLayoutManager) {
|
|
222
|
+
super(layoutManager);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
override zoom(
|
|
226
|
+
level: number,
|
|
227
|
+
center: Point = {
|
|
228
|
+
x: this.layoutManager.pageBounds.width / 2,
|
|
229
|
+
y: this.layoutManager.pageBounds.height / 2,
|
|
230
|
+
},
|
|
231
|
+
): void {
|
|
232
|
+
// Given the center point relative to the layoutManager's page bounds,
|
|
233
|
+
// determine its equivalent position in the layout. The equivalent position
|
|
234
|
+
// is determined by having pan manager remove the relative offset and zoom
|
|
235
|
+
// values.
|
|
236
|
+
const projectedPointOnGraph = this.layoutManager.pan.pagePointToGraphPoint(center);
|
|
237
|
+
|
|
238
|
+
// Adjust level and expansion factor
|
|
239
|
+
this.zoomLevel = Math.min(level, this.layoutManager.depth * ZOOM_EXPANSION_RATIO);
|
|
240
|
+
this.expansionFactor = 2 ** (this.zoomLevel / ZOOM_EXPANSION_RATIO);
|
|
241
|
+
|
|
242
|
+
// After changing the expansion factor, convert the projected point back to
|
|
243
|
+
// page space, and calculate the delta necessary to adjust the graph to keep
|
|
244
|
+
// the center point at the same relative bounds position.
|
|
245
|
+
const correctedPointInScreenSpace =
|
|
246
|
+
this.layoutManager.pan.graphPointToPagePoint(projectedPointOnGraph);
|
|
247
|
+
const delta = {
|
|
248
|
+
x: center.x - correctedPointInScreenSpace.x,
|
|
249
|
+
y: center.y - correctedPointInScreenSpace.y,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Recalculate positions and update the graph.
|
|
253
|
+
this.layoutManager.moveGraphBy(delta);
|
|
254
|
+
this.layoutManager.updateAt();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Given two points on the real number line, return the point p such that the
|
|
259
|
+
// distance from p to c is equal to the distance from n to c times a factor.
|
|
260
|
+
// This call is highly homomorphic, and will get inlined
|
|
261
|
+
function place(n: number, factor: number, c: number): number {
|
|
262
|
+
return c + factor * (n - c);
|
|
263
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ChildManager } from "./manager/child_manager.ts";
|
|
2
|
+
export { DragManager } from "./manager/drag_manager.ts";
|
|
3
|
+
export { NoopLayoutManager } from "./manager/manager.ts";
|
|
4
|
+
export { PanManager } from "./manager/pan_manager.ts";
|
|
5
|
+
export { VisibilityManager } from "./manager/visibility_manager.ts";
|
|
6
|
+
export { ZoomManager } from "./manager/zoom_manager.ts";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CircleGeometry } from "../../geometry.ts";
|
|
2
|
+
import type { NodeId } from "../../types.ts";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
EdgeConfig,
|
|
6
|
+
EdgeProperties,
|
|
7
|
+
GraphConfig,
|
|
8
|
+
GraphProperties,
|
|
9
|
+
NodeConfig,
|
|
10
|
+
NodeProperties,
|
|
11
|
+
SegmentConfig,
|
|
12
|
+
SegmentProperties,
|
|
13
|
+
} from "../types.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default properties for a node.
|
|
17
|
+
*
|
|
18
|
+
* Makes a fresh object every call.
|
|
19
|
+
*/
|
|
20
|
+
export function defaultNodeProperties(layout: Partial<NodeConfig> = {}): NodeProperties {
|
|
21
|
+
return {
|
|
22
|
+
geometry: new CircleGeometry({ radius: 20 }),
|
|
23
|
+
rank: 0,
|
|
24
|
+
// Start order at -1 so initOrder knows whether to push or assign
|
|
25
|
+
// in the row.
|
|
26
|
+
order: -1,
|
|
27
|
+
lim: Number.MAX_SAFE_INTEGER,
|
|
28
|
+
low: 1,
|
|
29
|
+
virtual: false,
|
|
30
|
+
fixedRank: false,
|
|
31
|
+
...layout,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Default properties for an edge.
|
|
37
|
+
*
|
|
38
|
+
* Makes a fresh object every call.
|
|
39
|
+
*/
|
|
40
|
+
export function defaultEdgeProperties(layout: Partial<EdgeConfig> = {}): EdgeProperties {
|
|
41
|
+
return {
|
|
42
|
+
minLength: 1,
|
|
43
|
+
weight: 1,
|
|
44
|
+
cutValue: 0,
|
|
45
|
+
reversed: false,
|
|
46
|
+
nestingEdge: false,
|
|
47
|
+
points: [],
|
|
48
|
+
...layout,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default properties for a segment.
|
|
54
|
+
*
|
|
55
|
+
* Makes a fresh object every call.
|
|
56
|
+
*/
|
|
57
|
+
export function defaultSegmentProperties(layout: Partial<SegmentConfig> = {}): SegmentProperties {
|
|
58
|
+
return {
|
|
59
|
+
metrics: {
|
|
60
|
+
forward: undefined,
|
|
61
|
+
reverse: undefined,
|
|
62
|
+
},
|
|
63
|
+
annotations: {
|
|
64
|
+
forward: undefined,
|
|
65
|
+
reverse: undefined,
|
|
66
|
+
},
|
|
67
|
+
points: [],
|
|
68
|
+
label: {
|
|
69
|
+
position: { x: 0, y: 0 },
|
|
70
|
+
rotation: 0,
|
|
71
|
+
flippedBy180: false,
|
|
72
|
+
},
|
|
73
|
+
...layout,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Default properties for a graph.
|
|
79
|
+
*
|
|
80
|
+
* Makes a fresh object every call.
|
|
81
|
+
*/
|
|
82
|
+
export function defaultGraphProperties(layout: Partial<GraphConfig> = {}): GraphProperties {
|
|
83
|
+
return {
|
|
84
|
+
rankSep: 50,
|
|
85
|
+
edgeSep: 10,
|
|
86
|
+
nodeSep: 50,
|
|
87
|
+
nodeRankFactor: 1,
|
|
88
|
+
baseRadius: 20,
|
|
89
|
+
hullRotation: 25,
|
|
90
|
+
childrenRelative: false,
|
|
91
|
+
bendLongEdges: true,
|
|
92
|
+
staggerRows: 10,
|
|
93
|
+
timing: false,
|
|
94
|
+
printDebug: false,
|
|
95
|
+
virtualNodeLayout: new Map<NodeId, { rank: number; order: number }>(),
|
|
96
|
+
fas: undefined,
|
|
97
|
+
...layout,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Edge, Node, Segment, SmartGraph } from "../../types.ts";
|
|
2
|
+
import type {
|
|
3
|
+
EdgeConfig,
|
|
4
|
+
EdgeProperties,
|
|
5
|
+
GraphProperties,
|
|
6
|
+
NodeConfig,
|
|
7
|
+
NodeProperties,
|
|
8
|
+
PropertiesStrategy,
|
|
9
|
+
SegmentConfig,
|
|
10
|
+
SegmentProperties,
|
|
11
|
+
} from "../types.ts";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
defaultEdgeProperties,
|
|
15
|
+
defaultGraphProperties,
|
|
16
|
+
defaultNodeProperties,
|
|
17
|
+
defaultSegmentProperties,
|
|
18
|
+
} from "./defaults.ts";
|
|
19
|
+
|
|
20
|
+
const LAYOUT: unique symbol = Symbol("Layout");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A PropertiesStrategy which stores properties in a private Symbol on entities.
|
|
24
|
+
*/
|
|
25
|
+
export const LAYOUT_SYMBOL_PROPERTIES_STRATEGY: PropertiesStrategy = {
|
|
26
|
+
getNodeProps(
|
|
27
|
+
obj: Node & {
|
|
28
|
+
layout?: Partial<NodeConfig>;
|
|
29
|
+
[LAYOUT]?: NodeProperties;
|
|
30
|
+
},
|
|
31
|
+
): NodeProperties {
|
|
32
|
+
return (obj[LAYOUT] = obj[LAYOUT] ?? {
|
|
33
|
+
...defaultNodeProperties(obj.layout),
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
getEdgeProps(
|
|
38
|
+
obj: Edge & {
|
|
39
|
+
layout?: Partial<EdgeConfig>;
|
|
40
|
+
[LAYOUT]?: EdgeProperties;
|
|
41
|
+
},
|
|
42
|
+
): EdgeProperties {
|
|
43
|
+
return (obj[LAYOUT] = obj[LAYOUT] ?? defaultEdgeProperties(obj.layout));
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
getSegmentProps(
|
|
47
|
+
obj: Segment & {
|
|
48
|
+
layout?: Partial<SegmentConfig>;
|
|
49
|
+
[LAYOUT]?: SegmentProperties;
|
|
50
|
+
},
|
|
51
|
+
): SegmentProperties {
|
|
52
|
+
return (obj[LAYOUT] = obj[LAYOUT] ?? defaultSegmentProperties(obj.layout));
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
getGraphProps(
|
|
56
|
+
obj: SmartGraph<{}, {}> & {
|
|
57
|
+
layout?: Partial<GraphProperties>;
|
|
58
|
+
[LAYOUT]?: GraphProperties;
|
|
59
|
+
},
|
|
60
|
+
): GraphProperties {
|
|
61
|
+
return (obj[LAYOUT] = obj[LAYOUT] ?? defaultGraphProperties(obj.layout));
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
clearNodeProps(
|
|
65
|
+
obj: Node & {
|
|
66
|
+
layout?: Partial<NodeProperties>;
|
|
67
|
+
[LAYOUT]?: NodeProperties;
|
|
68
|
+
},
|
|
69
|
+
) {
|
|
70
|
+
obj[LAYOUT] = undefined;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
clearEdgeProps(
|
|
74
|
+
obj: Edge & {
|
|
75
|
+
layout?: Partial<EdgeProperties>;
|
|
76
|
+
[LAYOUT]?: EdgeProperties;
|
|
77
|
+
},
|
|
78
|
+
) {
|
|
79
|
+
obj[LAYOUT] = undefined;
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
clearSegmentProps(
|
|
83
|
+
obj: Segment & {
|
|
84
|
+
layout?: Partial<SegmentConfig>;
|
|
85
|
+
[LAYOUT]?: EdgeProperties;
|
|
86
|
+
},
|
|
87
|
+
) {
|
|
88
|
+
obj[LAYOUT] = undefined;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
clearGraphProps(
|
|
92
|
+
obj: SmartGraph<{}, {}> & {
|
|
93
|
+
layout?: Partial<GraphProperties>;
|
|
94
|
+
[LAYOUT]?: GraphProperties;
|
|
95
|
+
},
|
|
96
|
+
) {
|
|
97
|
+
obj[LAYOUT] = undefined;
|
|
98
|
+
},
|
|
99
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Edge, Node, Segment, SmartGraph } from "../../types.ts";
|
|
2
|
+
import type {
|
|
3
|
+
EdgeConfig,
|
|
4
|
+
EdgeProperties,
|
|
5
|
+
GraphProperties,
|
|
6
|
+
NodeConfig,
|
|
7
|
+
NodeProperties,
|
|
8
|
+
PropertiesStrategy,
|
|
9
|
+
SegmentConfig,
|
|
10
|
+
SegmentProperties,
|
|
11
|
+
} from "../types.ts";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
defaultEdgeProperties,
|
|
15
|
+
defaultGraphProperties,
|
|
16
|
+
defaultNodeProperties,
|
|
17
|
+
defaultSegmentProperties,
|
|
18
|
+
} from "./defaults.ts";
|
|
19
|
+
|
|
20
|
+
const EDGE_MAP = new WeakMap<Edge, EdgeProperties>();
|
|
21
|
+
const GRAPH_MAP = new WeakMap<SmartGraph<{}, {}>, GraphProperties>();
|
|
22
|
+
const NODE_MAP = new WeakMap<Node, NodeProperties>();
|
|
23
|
+
const SEGMENT_MAP = new WeakMap<Segment, SegmentProperties>();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A PropertiesStrategy which stores properties in a WeakMap from entity to
|
|
27
|
+
* properties.
|
|
28
|
+
*/
|
|
29
|
+
export const WEAK_MAP_PROPERTIES_STRATEGY: PropertiesStrategy = {
|
|
30
|
+
getNodeProps(obj: Node & { layout?: Partial<NodeConfig> }): NodeProperties {
|
|
31
|
+
const properties = NODE_MAP.get(obj) ?? {
|
|
32
|
+
...defaultNodeProperties(obj.layout),
|
|
33
|
+
};
|
|
34
|
+
NODE_MAP.set(obj, properties);
|
|
35
|
+
return properties;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
getEdgeProps(obj: Edge & { layout?: Partial<EdgeConfig> }): EdgeProperties {
|
|
39
|
+
const properties = EDGE_MAP.get(obj) ?? {
|
|
40
|
+
...defaultEdgeProperties(obj.layout),
|
|
41
|
+
};
|
|
42
|
+
EDGE_MAP.set(obj, properties);
|
|
43
|
+
return properties;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
getSegmentProps(obj: Segment & { layout?: Partial<SegmentConfig> }): SegmentProperties {
|
|
47
|
+
const properties = SEGMENT_MAP.get(obj) ?? defaultSegmentProperties(obj.layout);
|
|
48
|
+
SEGMENT_MAP.set(obj, properties);
|
|
49
|
+
return properties;
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
getGraphProps(obj: SmartGraph<{}, {}> & { layout?: Partial<GraphProperties> }): GraphProperties {
|
|
53
|
+
const properties = GRAPH_MAP.get(obj) ?? defaultGraphProperties(obj.layout);
|
|
54
|
+
GRAPH_MAP.set(obj, properties);
|
|
55
|
+
return properties;
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
clearNodeProps(obj: Node) {
|
|
59
|
+
NODE_MAP.delete(obj);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
clearEdgeProps(obj: Edge) {
|
|
63
|
+
EDGE_MAP.delete(obj);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
clearSegmentProps(obj: Segment) {
|
|
67
|
+
SEGMENT_MAP.delete(obj);
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
clearGraphProps(obj: SmartGraph<{}, {}>) {
|
|
71
|
+
GRAPH_MAP.delete(obj);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview utilities to track properties for Agathon entities.
|
|
3
|
+
*
|
|
4
|
+
* The Agathon layout engine tracks numerous properties about a graph and its
|
|
5
|
+
* nodes, edges, and segments. Historically, these were bundled per instance in
|
|
6
|
+
* a property grab bag hidden behind a symbol. While quick to implement at the
|
|
7
|
+
* time, and it certainly gets the job done, it is unclear whether how this
|
|
8
|
+
* approach handles a variety of situations from performance or other stand
|
|
9
|
+
* points.
|
|
10
|
+
*
|
|
11
|
+
* This centralized property lookup tool lets clients of Agathon choose which
|
|
12
|
+
* mechanism works best for their workload.
|
|
13
|
+
*/
|
|
14
|
+
export { LAYOUT_SYMBOL_PROPERTIES_STRATEGY } from "./properties/symbol.ts";
|
|
15
|
+
export { WEAK_MAP_PROPERTIES_STRATEGY } from "./properties/weak_map.ts";
|
|
16
|
+
|
|
17
|
+
import type { Edge, Node, Segment, SmartGraph } from "../types.ts";
|
|
18
|
+
import { LAYOUT_SYMBOL_PROPERTIES_STRATEGY } from "./properties/symbol.ts";
|
|
19
|
+
import type {
|
|
20
|
+
EdgeConfig,
|
|
21
|
+
EdgeProperties,
|
|
22
|
+
GraphProperties,
|
|
23
|
+
NodeConfig,
|
|
24
|
+
NodeProperties,
|
|
25
|
+
PropertiesStrategy,
|
|
26
|
+
SegmentConfig,
|
|
27
|
+
SegmentProperties,
|
|
28
|
+
} from "./types.ts";
|
|
29
|
+
|
|
30
|
+
let propertyStrategy: PropertiesStrategy;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Choose a global PropertiesStrategy to be used for all Agathon layout
|
|
34
|
+
* calculations.
|
|
35
|
+
*/
|
|
36
|
+
export function setPropertiesStrategy(strategy: PropertiesStrategy): void {
|
|
37
|
+
propertyStrategy = strategy;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reset the PropertiesStrategy to be used for Agathon layout calculations.
|
|
42
|
+
*/
|
|
43
|
+
export function resetPropertiesStrategy(): void {
|
|
44
|
+
setPropertiesStrategy(LAYOUT_SYMBOL_PROPERTIES_STRATEGY);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
resetPropertiesStrategy();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize layout properties, pulling values from the layout properties if
|
|
51
|
+
* provided for initial values.
|
|
52
|
+
*/
|
|
53
|
+
export function getNodeProps(obj: Node & { layout?: Partial<NodeConfig> }): NodeProperties {
|
|
54
|
+
return propertyStrategy.getNodeProps(obj);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Initialize layout properties for an edge, pulling values from the layout
|
|
59
|
+
* properties if provided for initial values.
|
|
60
|
+
*/
|
|
61
|
+
export function getEdgeProps(obj: Edge & { layout?: Partial<EdgeConfig> }): EdgeProperties {
|
|
62
|
+
return propertyStrategy.getEdgeProps(obj);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initialize layout properties for a segment, pulling values from the layout
|
|
67
|
+
* properties if provided for initial values.
|
|
68
|
+
*/
|
|
69
|
+
export function getSegmentProps(
|
|
70
|
+
obj: Segment & { layout?: Partial<SegmentConfig> },
|
|
71
|
+
): SegmentProperties {
|
|
72
|
+
return propertyStrategy.getSegmentProps(obj);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Initialize layout properties for a graph, pulling values from the layout
|
|
77
|
+
* properties if provided for initial values.
|
|
78
|
+
*/
|
|
79
|
+
export function getGraphProps(
|
|
80
|
+
obj: SmartGraph<{}, {}> & { layout?: Partial<GraphProperties> },
|
|
81
|
+
): GraphProperties {
|
|
82
|
+
return propertyStrategy.getGraphProps(obj);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Clear layout properties for a node.
|
|
87
|
+
*/
|
|
88
|
+
export function clearNodeProps(obj: Node): void {
|
|
89
|
+
propertyStrategy.clearNodeProps(obj);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Clear layout properties for an edge.
|
|
94
|
+
*/
|
|
95
|
+
export function clearEdgeProps(obj: Edge): void {
|
|
96
|
+
propertyStrategy.clearEdgeProps(obj);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clear layout properties for a segment.
|
|
101
|
+
*/
|
|
102
|
+
export function clearSegmentProps(obj: Segment): void {
|
|
103
|
+
propertyStrategy.clearSegmentProps(obj);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Clear layout properties for a graph.
|
|
108
|
+
*/
|
|
109
|
+
export function clearGraphProps(obj: SmartGraph<{}, {}>): void {
|
|
110
|
+
propertyStrategy.clearGraphProps(obj);
|
|
111
|
+
}
|