@3plate/graph-core 0.1.0 → 0.1.2
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/dist/index.cjs +2198 -1322
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +127 -45
- package/dist/index.js +2193 -1308
- package/package.json +10 -3
- package/dist/index.d.mts +0 -51
- package/dist/index.mjs +0 -323
package/dist/index.js
CHANGED
|
@@ -1,733 +1,601 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
mergeOrder: ["target", "source"],
|
|
7
|
-
nodeMargin: 15,
|
|
8
|
-
dummyNodeSize: 15,
|
|
9
|
-
defaultPortOffset: 20,
|
|
10
|
-
nodeAlign: "natural",
|
|
11
|
-
// 'natural' || 'top' || 'bottom' || 'left' || 'right'
|
|
12
|
-
edgeSpacing: 10,
|
|
13
|
-
turnRadius: 10,
|
|
14
|
-
graphPadding: 20,
|
|
15
|
-
orientation: "TB",
|
|
16
|
-
layerMargin: 5,
|
|
17
|
-
alignIterations: 5,
|
|
18
|
-
alignThreshold: 10,
|
|
19
|
-
separateTrackSets: true
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// src/graph-nodes.js
|
|
23
|
-
import { Set as ISet } from "immutable";
|
|
1
|
+
// src/canvas/render-node.tsx
|
|
2
|
+
import { jsx } from "jsx-dom/jsx-runtime";
|
|
3
|
+
function renderNode(node) {
|
|
4
|
+
return /* @__PURE__ */ jsx("div", { children: node?.id || "" });
|
|
5
|
+
}
|
|
24
6
|
|
|
25
|
-
// src/
|
|
26
|
-
import
|
|
7
|
+
// src/graph/types/graph.ts
|
|
8
|
+
import { Map as IMap, List as IList, Set as ISet6 } from "immutable";
|
|
27
9
|
|
|
28
|
-
// src/
|
|
29
|
-
import
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
break;
|
|
46
|
-
case "info":
|
|
47
|
-
console.info(message);
|
|
48
|
-
break;
|
|
49
|
-
default:
|
|
50
|
-
console.log(message);
|
|
51
|
-
}
|
|
52
|
-
callback();
|
|
53
|
-
}
|
|
10
|
+
// src/graph/types/node.ts
|
|
11
|
+
import { Record, Set as ISet } from "immutable";
|
|
12
|
+
var defaultNodeProps = {
|
|
13
|
+
id: "",
|
|
14
|
+
aligned: {},
|
|
15
|
+
edges: { in: ISet(), out: ISet() },
|
|
16
|
+
segs: { in: ISet(), out: ISet() },
|
|
17
|
+
ports: { in: [], out: [] },
|
|
18
|
+
layerId: "",
|
|
19
|
+
isDummy: false,
|
|
20
|
+
isMerged: false,
|
|
21
|
+
edgeIds: [],
|
|
22
|
+
index: void 0,
|
|
23
|
+
pos: void 0,
|
|
24
|
+
lpos: void 0,
|
|
25
|
+
dims: void 0,
|
|
26
|
+
mutable: false
|
|
54
27
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
transports = [new winston.transports.Console()];
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
var log = winston.createLogger({
|
|
81
|
-
level: process.env.LOG_LEVEL || "warn",
|
|
82
|
-
format,
|
|
83
|
-
transports
|
|
84
|
-
});
|
|
85
|
-
function logger(module) {
|
|
86
|
-
return log.child({ module });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// src/graph-nodes.js
|
|
90
|
-
var log2 = logger("nodes");
|
|
91
|
-
var GraphNodes = {
|
|
92
|
-
/**
|
|
93
|
-
* Add a node to the graph
|
|
94
|
-
*
|
|
95
|
-
* @param {Object} props - Node properties
|
|
96
|
-
* @param {string} props.id - Node ID (required)
|
|
97
|
-
* @param {string} props.layerId - Node layer ID (optional)
|
|
98
|
-
* @returns {Object} The added node
|
|
99
|
-
*/
|
|
100
|
-
_addNode(props) {
|
|
101
|
-
const node = {
|
|
28
|
+
var Node = class _Node extends Record(defaultNodeProps) {
|
|
29
|
+
static dummyPrefix = "d:";
|
|
30
|
+
get edgeId() {
|
|
31
|
+
if (!this.isDummy)
|
|
32
|
+
throw new Error(`node ${this.id} is not a dummy`);
|
|
33
|
+
if (this.isMerged)
|
|
34
|
+
throw new Error(`node ${this.id} is merged`);
|
|
35
|
+
return this.get("edgeIds")[0];
|
|
36
|
+
}
|
|
37
|
+
get edgeIds() {
|
|
38
|
+
if (!this.isDummy)
|
|
39
|
+
throw new Error(`node ${this.id} is not a dummy`);
|
|
40
|
+
if (!this.isMerged)
|
|
41
|
+
throw new Error(`node ${this.id} is not merged`);
|
|
42
|
+
return this.get("edgeIds");
|
|
43
|
+
}
|
|
44
|
+
static isDummyId(nodeId) {
|
|
45
|
+
return nodeId.startsWith(_Node.dummyPrefix);
|
|
46
|
+
}
|
|
47
|
+
static addNormal(g, props) {
|
|
48
|
+
const layer = g.layerAt(0);
|
|
49
|
+
const node = new _Node({
|
|
102
50
|
...props,
|
|
103
51
|
edges: { in: ISet(), out: ISet() },
|
|
104
52
|
segs: { in: ISet(), out: ISet() },
|
|
105
|
-
aligned: {}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
53
|
+
aligned: {},
|
|
54
|
+
edgeIds: [],
|
|
55
|
+
layerId: layer.id
|
|
56
|
+
});
|
|
57
|
+
layer.addNode(g, node.id);
|
|
58
|
+
g.nodes.set(node.id, node);
|
|
59
|
+
g.dirtyNodes.add(node.id);
|
|
112
60
|
return node;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* @param {Object} props - Node properties
|
|
118
|
-
* @param {string} props.layerId - Node layer ID (optional)
|
|
119
|
-
* @returns {Object} The added node
|
|
120
|
-
*/
|
|
121
|
-
_addDummy(props) {
|
|
122
|
-
return this._addNode({
|
|
61
|
+
}
|
|
62
|
+
static addDummy(g, props) {
|
|
63
|
+
const layer = g.getLayer(props.layerId);
|
|
64
|
+
const node = new _Node({
|
|
123
65
|
...props,
|
|
124
|
-
id:
|
|
66
|
+
id: `${_Node.dummyPrefix}${g.nextDummyId++}`,
|
|
67
|
+
edges: { in: ISet(), out: ISet() },
|
|
68
|
+
segs: { in: ISet(), out: ISet() },
|
|
69
|
+
aligned: {},
|
|
125
70
|
isDummy: true,
|
|
126
71
|
dims: {
|
|
127
|
-
|
|
128
|
-
|
|
72
|
+
w: g.options.dummyNodeSize,
|
|
73
|
+
h: g.options.dummyNodeSize
|
|
129
74
|
}
|
|
130
75
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
this.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
76
|
+
layer.addNode(g, node.id);
|
|
77
|
+
g.nodes.set(node.id, node);
|
|
78
|
+
g.dirtyNodes.add(node.id);
|
|
79
|
+
return node;
|
|
80
|
+
}
|
|
81
|
+
mut(g) {
|
|
82
|
+
if (this.mutable) return this;
|
|
83
|
+
return g.mutateNode(this);
|
|
84
|
+
}
|
|
85
|
+
final() {
|
|
86
|
+
if (!this.mutable) return this;
|
|
87
|
+
return this.merge({
|
|
88
|
+
edges: { in: this.edges.in.asImmutable(), out: this.edges.out.asImmutable() },
|
|
89
|
+
segs: { in: this.segs.in.asImmutable(), out: this.segs.out.asImmutable() },
|
|
90
|
+
mutable: false
|
|
91
|
+
}).asImmutable();
|
|
92
|
+
}
|
|
93
|
+
dirty(g) {
|
|
94
|
+
g.dirtyNodes.add(this.id);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
cur(g) {
|
|
98
|
+
return g.getNode(this.id);
|
|
99
|
+
}
|
|
100
|
+
isUnlinked() {
|
|
101
|
+
return this.edges.in.size == 0 && this.edges.out.size == 0 && this.segs.in.size == 0 && this.segs.out.size == 0;
|
|
102
|
+
}
|
|
103
|
+
layerIndex(g) {
|
|
104
|
+
return this.getLayer(g).index;
|
|
105
|
+
}
|
|
106
|
+
getLayer(g) {
|
|
107
|
+
return g.getLayer(this.layerId);
|
|
108
|
+
}
|
|
109
|
+
setIndex(g, index) {
|
|
110
|
+
if (this.index == index) return this;
|
|
111
|
+
console.log(`set index of ${this.id} to ${index}`);
|
|
112
|
+
return this.mut(g).set("index", index);
|
|
113
|
+
}
|
|
114
|
+
setLayerPos(g, lpos) {
|
|
115
|
+
console.log("setLayerPos", this.id, lpos);
|
|
116
|
+
if (this.lpos == lpos) return this;
|
|
117
|
+
return this.mut(g).set("lpos", lpos);
|
|
118
|
+
}
|
|
119
|
+
setLayer(g, layerId) {
|
|
120
|
+
if (this.layerId == layerId) return this;
|
|
121
|
+
return this.mut(g).set("layerId", layerId);
|
|
122
|
+
}
|
|
123
|
+
setPos(g, pos) {
|
|
124
|
+
if (!this.pos || this.pos.x != pos.x || this.pos.y != pos.y)
|
|
125
|
+
return this.mut(g).set("pos", pos);
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
moveToLayer(g, layer) {
|
|
129
|
+
console.log("moveToLayer", this, this.getLayer(g), layer);
|
|
130
|
+
this.getLayer(g).delNode(g, this.id);
|
|
131
|
+
layer.addNode(g, this.id);
|
|
132
|
+
return this.setLayer(g, layer.id);
|
|
133
|
+
}
|
|
134
|
+
moveToLayerIndex(g, index) {
|
|
135
|
+
return this.moveToLayer(g, g.layerAt(index));
|
|
136
|
+
}
|
|
137
|
+
setAligned(g, dir, nodeId) {
|
|
138
|
+
if (this.aligned[dir] === nodeId) return this;
|
|
139
|
+
return this.mut(g).set("aligned", { ...this.aligned, [dir]: nodeId });
|
|
140
|
+
}
|
|
141
|
+
addRel(g, type, dir, relId) {
|
|
142
|
+
const sets = this.get(type);
|
|
143
|
+
const set = sets[dir];
|
|
144
|
+
if (set.has(relId)) return this;
|
|
145
|
+
return this.mut(g).set(type, { ...sets, [dir]: set.asMutable().add(relId) });
|
|
146
|
+
}
|
|
147
|
+
delRel(g, type, dir, relId) {
|
|
148
|
+
let sets = this.get(type);
|
|
149
|
+
const set = sets[dir];
|
|
150
|
+
if (!set.has(relId)) return this;
|
|
151
|
+
sets = { ...sets, [dir]: set.asMutable().remove(relId) };
|
|
152
|
+
const node = this.mut(g).set(type, sets);
|
|
153
|
+
if (node.isDummy && node.isUnlinked())
|
|
154
|
+
return node.delSelf(g);
|
|
155
|
+
return node;
|
|
156
|
+
}
|
|
157
|
+
delSelf(g) {
|
|
158
|
+
this.getLayer(g).delNode(g, this.id);
|
|
159
|
+
for (const rel of this.rels(g))
|
|
160
|
+
rel.delSelf(g);
|
|
161
|
+
g.nodes.delete(this.id);
|
|
162
|
+
g.dirtyNodes.delete(this.id);
|
|
163
|
+
g.delNodes.add(this.id);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
addInEdge(g, edgeId) {
|
|
167
|
+
return this.addRel(g, "edges", "in", edgeId);
|
|
168
|
+
}
|
|
169
|
+
addOutEdge(g, edgeId) {
|
|
170
|
+
return this.addRel(g, "edges", "out", edgeId);
|
|
171
|
+
}
|
|
172
|
+
addInSeg(g, segId) {
|
|
173
|
+
return this.addRel(g, "segs", "in", segId);
|
|
174
|
+
}
|
|
175
|
+
addOutSeg(g, segId) {
|
|
176
|
+
return this.addRel(g, "segs", "out", segId);
|
|
177
|
+
}
|
|
178
|
+
delInEdge(g, edgeId) {
|
|
179
|
+
return this.delRel(g, "edges", "in", edgeId);
|
|
180
|
+
}
|
|
181
|
+
delOutEdge(g, edgeId) {
|
|
182
|
+
return this.delRel(g, "edges", "out", edgeId);
|
|
183
|
+
}
|
|
184
|
+
delInSeg(g, segId) {
|
|
185
|
+
return this.delRel(g, "segs", "in", segId);
|
|
186
|
+
}
|
|
187
|
+
delOutSeg(g, segId) {
|
|
188
|
+
return this.delRel(g, "segs", "out", segId);
|
|
189
|
+
}
|
|
190
|
+
*relIds(type = "both", dir = "both") {
|
|
191
|
+
const types = type == "both" ? ["edges", "segs"] : [type];
|
|
192
|
+
const dirs = dir == "both" ? ["in", "out"] : [dir];
|
|
193
|
+
for (const type2 of types)
|
|
194
|
+
for (const dir2 of dirs)
|
|
195
|
+
yield* this.get(type2)[dir2];
|
|
196
|
+
}
|
|
197
|
+
*rels(g, type = "both", dir = "both") {
|
|
198
|
+
for (const relId of this.relIds(type, dir))
|
|
199
|
+
yield g.getRel(relId);
|
|
200
|
+
}
|
|
201
|
+
*adjIds(g, type = "both", dir = "both") {
|
|
202
|
+
const dirs = dir == "both" ? ["in", "out"] : [dir];
|
|
203
|
+
for (const dir2 of dirs) {
|
|
204
|
+
const side = dir2 == "in" ? "source" : "target";
|
|
205
|
+
for (const rel of this.rels(g, type, dir2))
|
|
206
|
+
yield rel[side].id;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
*adjs(g, type = "both", dir = "both") {
|
|
210
|
+
for (const nodeId of this.adjIds(g, type, dir))
|
|
211
|
+
yield g.getNode(nodeId);
|
|
212
|
+
}
|
|
213
|
+
*inEdgeIds() {
|
|
214
|
+
yield* this.relIds("edges", "in");
|
|
215
|
+
}
|
|
216
|
+
*outEdgeIds() {
|
|
217
|
+
yield* this.relIds("edges", "out");
|
|
218
|
+
}
|
|
219
|
+
*inSegIds() {
|
|
220
|
+
yield* this.relIds("segs", "in");
|
|
221
|
+
}
|
|
222
|
+
*outSegIds() {
|
|
223
|
+
yield* this.relIds("segs", "out");
|
|
224
|
+
}
|
|
225
|
+
*inEdges(g) {
|
|
226
|
+
yield* this.rels(g, "edges", "in");
|
|
227
|
+
}
|
|
228
|
+
*outEdges(g) {
|
|
229
|
+
yield* this.rels(g, "edges", "out");
|
|
230
|
+
}
|
|
231
|
+
*inSegs(g) {
|
|
232
|
+
yield* this.rels(g, "segs", "in");
|
|
233
|
+
}
|
|
234
|
+
*outSegs(g) {
|
|
235
|
+
yield* this.rels(g, "segs", "out");
|
|
236
|
+
}
|
|
237
|
+
*inNodeIds(g) {
|
|
238
|
+
yield* this.adjIds(g, "edges", "in");
|
|
239
|
+
}
|
|
240
|
+
*outNodeIds(g) {
|
|
241
|
+
yield* this.adjIds(g, "edges", "out");
|
|
242
|
+
}
|
|
243
|
+
*inNodes(g) {
|
|
244
|
+
yield* this.adjs(g, "edges", "in");
|
|
245
|
+
}
|
|
246
|
+
*outNodes(g) {
|
|
247
|
+
yield* this.adjs(g, "edges", "out");
|
|
181
248
|
}
|
|
182
249
|
};
|
|
183
250
|
|
|
184
|
-
// src/graph
|
|
185
|
-
|
|
186
|
-
var
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
251
|
+
// src/graph/types/edge.ts
|
|
252
|
+
import { Record as Record2 } from "immutable";
|
|
253
|
+
var defaultEdgeProps = {
|
|
254
|
+
id: "",
|
|
255
|
+
source: { id: "" },
|
|
256
|
+
target: { id: "" },
|
|
257
|
+
mutable: false,
|
|
258
|
+
segIds: []
|
|
259
|
+
};
|
|
260
|
+
var Edge = class _Edge extends Record2(defaultEdgeProps) {
|
|
261
|
+
static prefix = "e:";
|
|
262
|
+
mut(g) {
|
|
263
|
+
if (this.mutable) return this;
|
|
264
|
+
return g.mutateEdge(this);
|
|
265
|
+
}
|
|
266
|
+
final() {
|
|
267
|
+
if (!this.mutable) return this;
|
|
268
|
+
return this.merge({
|
|
269
|
+
mutable: false
|
|
270
|
+
}).asImmutable();
|
|
271
|
+
}
|
|
272
|
+
link(g) {
|
|
273
|
+
this.sourceNode(g).addOutEdge(g, this.id);
|
|
274
|
+
this.targetNode(g).addInEdge(g, this.id);
|
|
275
|
+
return this;
|
|
276
|
+
}
|
|
277
|
+
unlink(g) {
|
|
278
|
+
this.sourceNode(g).delOutEdge(g, this.id);
|
|
279
|
+
this.targetNode(g).delInEdge(g, this.id);
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
delSelf(g) {
|
|
283
|
+
for (const seg of this.segs(g))
|
|
284
|
+
seg.delEdgeId(g, this.id);
|
|
285
|
+
this.unlink(g);
|
|
286
|
+
g.edges.delete(this.id);
|
|
287
|
+
g.dirtyEdges.delete(this.id);
|
|
288
|
+
g.delEdges.add(this.id);
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
delSegId(g, segId) {
|
|
292
|
+
return this.setSegIds(g, this.segIds.filter((id) => id != segId));
|
|
293
|
+
}
|
|
294
|
+
replaceSegId(g, oldId, newId) {
|
|
295
|
+
return this.setSegIds(g, this.segIds.map((id) => id == oldId ? newId : id));
|
|
296
|
+
}
|
|
297
|
+
setSegIds(g, segIds) {
|
|
298
|
+
if (segIds.join(",") == this.segIds.join(",")) return this;
|
|
299
|
+
return this.mut(g).set("segIds", segIds);
|
|
300
|
+
}
|
|
301
|
+
*segs(g) {
|
|
302
|
+
for (const segId of this.segIds)
|
|
303
|
+
yield g.getSeg(segId);
|
|
304
|
+
}
|
|
305
|
+
node(g, side) {
|
|
306
|
+
return g.getNode(this[side].id);
|
|
307
|
+
}
|
|
308
|
+
sourceNode(g) {
|
|
309
|
+
return this.node(g, "source");
|
|
310
|
+
}
|
|
311
|
+
targetNode(g) {
|
|
312
|
+
return this.node(g, "target");
|
|
313
|
+
}
|
|
314
|
+
get str() {
|
|
315
|
+
return _Edge.str(this);
|
|
316
|
+
}
|
|
317
|
+
static str(edge) {
|
|
318
|
+
let source = edge.source.id;
|
|
319
|
+
if (edge.source.port)
|
|
320
|
+
source = `${source} (port ${edge.source.port})`;
|
|
321
|
+
let target = edge.target.id;
|
|
322
|
+
if (edge.target.port)
|
|
323
|
+
target = `${target} (port ${edge.target.port})`;
|
|
324
|
+
let str = `edge from ${source} to ${target}`;
|
|
325
|
+
if (edge.type) str += ` of type ${edge.type}`;
|
|
326
|
+
return str;
|
|
327
|
+
}
|
|
328
|
+
static id(edge, prefix = _Edge.prefix, side = "both") {
|
|
214
329
|
let source = "", target = "";
|
|
215
330
|
if (side == "source" || side == "both") {
|
|
216
|
-
source =
|
|
217
|
-
if (
|
|
331
|
+
source = edge.source.id;
|
|
332
|
+
if (edge.source.port)
|
|
333
|
+
source = `${source}.${edge.source.port}`;
|
|
218
334
|
source += "-";
|
|
219
335
|
}
|
|
220
336
|
if (side == "target" || side == "both") {
|
|
221
|
-
target =
|
|
222
|
-
if (
|
|
337
|
+
target = edge.target.id;
|
|
338
|
+
if (edge.target.port)
|
|
339
|
+
target = `${target}.${edge.target.port}`;
|
|
340
|
+
target = "-" + target;
|
|
223
341
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
* @param {Object} edge - Edge
|
|
230
|
-
* @returns {string} The edge ID
|
|
231
|
-
*/
|
|
232
|
-
_edgeId(edge) {
|
|
233
|
-
return this._edgeSegId(edge, "e");
|
|
234
|
-
},
|
|
235
|
-
/**
|
|
236
|
-
* Generate the ID of a segment
|
|
237
|
-
*
|
|
238
|
-
* @param {Object} seg - Segment
|
|
239
|
-
* @returns {string} The segment ID
|
|
240
|
-
*/
|
|
241
|
-
_segId(seg) {
|
|
242
|
-
return this._edgeSegId(seg, "s");
|
|
243
|
-
},
|
|
244
|
-
/**
|
|
245
|
-
* Generate a new layer ID
|
|
246
|
-
*
|
|
247
|
-
* @returns {string} A new layer ID
|
|
248
|
-
*/
|
|
249
|
-
_newLayerId() {
|
|
250
|
-
return `l:${this.nextLayerId++}`;
|
|
251
|
-
},
|
|
252
|
-
/**
|
|
253
|
-
* Link a segment to its source and target nodes
|
|
254
|
-
*
|
|
255
|
-
* @param {Object} seg - Segment
|
|
256
|
-
*/
|
|
257
|
-
_linkSeg(seg) {
|
|
258
|
-
this._linkObj(seg, "segs");
|
|
259
|
-
},
|
|
260
|
-
/**
|
|
261
|
-
* Link an edge to its source and target nodes
|
|
262
|
-
*
|
|
263
|
-
* @param {Object} edge - Edge
|
|
264
|
-
*/
|
|
265
|
-
_linkEdge(edge) {
|
|
266
|
-
this._linkObj(edge, "edges");
|
|
267
|
-
},
|
|
268
|
-
/**
|
|
269
|
-
* Unlink a segment from its source and target nodes
|
|
270
|
-
*
|
|
271
|
-
* @param {Object} seg - Segment
|
|
272
|
-
*/
|
|
273
|
-
_unlinkSeg(seg) {
|
|
274
|
-
this._unlinkRel(seg, "segs");
|
|
275
|
-
},
|
|
276
|
-
/**
|
|
277
|
-
* Unlink an edge from its source and target nodes
|
|
278
|
-
*
|
|
279
|
-
* @param {Object} edge - Edge
|
|
280
|
-
*/
|
|
281
|
-
_unlinkEdge(edge) {
|
|
282
|
-
this._unlinkRel(edge, "edges");
|
|
283
|
-
},
|
|
284
|
-
/**
|
|
285
|
-
* Link a relationship (edge or segment) to its source and target nodes
|
|
286
|
-
*
|
|
287
|
-
* @param {Object} rel - Relationship
|
|
288
|
-
* @param {string} type - Type of relationship
|
|
289
|
-
*/
|
|
290
|
-
_linkObj(rel, type) {
|
|
291
|
-
this._addRel(rel.source.id, rel.id, type, "out");
|
|
292
|
-
this._addRel(rel.target.id, rel.id, type, "in");
|
|
293
|
-
},
|
|
294
|
-
/**
|
|
295
|
-
* Unlink a relationship (edge or segment) from its source and target nodes
|
|
296
|
-
*
|
|
297
|
-
* @param {Object} rel - Relationship
|
|
298
|
-
* @param {string} type - Type of relationship
|
|
299
|
-
*/
|
|
300
|
-
_unlinkRel(rel, type) {
|
|
301
|
-
log3.debug(`unlinking rel ${rel.id} from ${rel.source.id} and ${rel.target.id}`);
|
|
302
|
-
this._deleteRel(rel.source.id, rel.id, type, "out");
|
|
303
|
-
this._deleteRel(rel.target.id, rel.id, type, "in");
|
|
304
|
-
},
|
|
305
|
-
/**
|
|
306
|
-
* Modify a relationship (edge or segment) in the graph.
|
|
307
|
-
* Either adds or deletes the relation from the appropriate
|
|
308
|
-
* immutable set on the node.
|
|
309
|
-
*
|
|
310
|
-
* @param {string} nodeId - Node ID
|
|
311
|
-
* @param {string} relId - Relationship ID
|
|
312
|
-
* @param {string} type - Type of relationship
|
|
313
|
-
* @param {string} dir - Direction of relationship
|
|
314
|
-
* @param {string} op - Operation (add or delete)
|
|
315
|
-
*/
|
|
316
|
-
_modRel(nodeId, relId, type, dir, op) {
|
|
317
|
-
log3.debug(`${op} rel ${relId} on ${nodeId} ${type} ${dir}`);
|
|
318
|
-
let node = this.getNode(nodeId);
|
|
319
|
-
let sets = node[type];
|
|
320
|
-
let set = sets[dir];
|
|
321
|
-
const exists = set.has(relId);
|
|
322
|
-
if (op == "add" && exists) return;
|
|
323
|
-
else if (op == "delete" && !exists) return;
|
|
324
|
-
set = set[op](relId);
|
|
325
|
-
sets = { ...sets, [dir]: set };
|
|
326
|
-
node = { ...node, [type]: sets };
|
|
327
|
-
this.nodes.set(nodeId, node);
|
|
328
|
-
this._markDirty(nodeId);
|
|
329
|
-
},
|
|
330
|
-
/**
|
|
331
|
-
* Add a relationship (edge or segment) to its source and target nodes
|
|
332
|
-
*
|
|
333
|
-
* @param {string} nodeId - Node ID
|
|
334
|
-
* @param {string} relId - Relationship ID
|
|
335
|
-
* @param {string} type - Type of relationship
|
|
336
|
-
* @param {string} dir - Direction of relationship
|
|
337
|
-
*/
|
|
338
|
-
_addRel(nodeId, relId, type, dir) {
|
|
339
|
-
this._modRel(nodeId, relId, type, dir, "add");
|
|
340
|
-
},
|
|
341
|
-
/**
|
|
342
|
-
* Delete a relationship (edge or segment) from its source and target nodes
|
|
343
|
-
*
|
|
344
|
-
* @param {string} nodeId - Node ID
|
|
345
|
-
* @param {string} relId - Relationship ID
|
|
346
|
-
* @param {string} type - Type of relationship
|
|
347
|
-
* @param {string} dir - Direction of relationship
|
|
348
|
-
*/
|
|
349
|
-
_deleteRel(nodeId, relId, type, dir) {
|
|
350
|
-
this._modRel(nodeId, relId, type, dir, "delete");
|
|
351
|
-
},
|
|
352
|
-
/**
|
|
353
|
-
* Add a new edge to the graph and link it to its source and target nodes
|
|
354
|
-
*
|
|
355
|
-
* @param {Object} props - Edge properties
|
|
356
|
-
* @returns {Object} The edge object
|
|
357
|
-
*/
|
|
358
|
-
_addEdge(props) {
|
|
359
|
-
const edge = {
|
|
342
|
+
const type = edge.type || "";
|
|
343
|
+
return `${prefix}${source}${type}${target}`;
|
|
344
|
+
}
|
|
345
|
+
static add(g, props) {
|
|
346
|
+
const edge = new _Edge({
|
|
360
347
|
...props,
|
|
361
|
-
id:
|
|
362
|
-
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
|
|
348
|
+
id: _Edge.id(props),
|
|
349
|
+
segIds: []
|
|
350
|
+
});
|
|
351
|
+
edge.link(g);
|
|
352
|
+
g.edges.set(edge.id, edge);
|
|
353
|
+
g.dirtyEdges.add(edge.id);
|
|
366
354
|
return edge;
|
|
367
|
-
},
|
|
368
|
-
/**
|
|
369
|
-
* Remove an edge from the graph and unlink it from its source and target nodes.
|
|
370
|
-
* Also remove it from the edge list of any segments it includes. Any segments
|
|
371
|
-
* that become unused are deleted.
|
|
372
|
-
*
|
|
373
|
-
* @param {string} edgeId - Edge ID
|
|
374
|
-
*/
|
|
375
|
-
_deleteEdge(edgeId) {
|
|
376
|
-
log3.debug(`deleting edge ${edgeId}`);
|
|
377
|
-
const edge = this.edges.get(edgeId);
|
|
378
|
-
if (!edge) return;
|
|
379
|
-
log3.debug(`unlinking edge ${edgeId}`);
|
|
380
|
-
this._unlinkEdge(edge);
|
|
381
|
-
for (const segId of edge.segs)
|
|
382
|
-
this._segDeleteEdge(segId, edgeId);
|
|
383
|
-
this.edges.delete(edgeId);
|
|
384
|
-
},
|
|
385
|
-
/**
|
|
386
|
-
* Add a new segment to the graph and link it to its source and target nodes
|
|
387
|
-
*
|
|
388
|
-
* @param {Object} props - Segment properties
|
|
389
|
-
* @returns {Object} The segment object
|
|
390
|
-
*/
|
|
391
|
-
_addSeg(props) {
|
|
392
|
-
const seg = {
|
|
393
|
-
...props,
|
|
394
|
-
id: this._segId(props)
|
|
395
|
-
};
|
|
396
|
-
this.segs.set(seg.id, seg);
|
|
397
|
-
this._linkSeg(seg);
|
|
398
|
-
return seg;
|
|
399
|
-
},
|
|
400
|
-
/**
|
|
401
|
-
* Remove a segment from the graph and unlink it from its source and target nodes.
|
|
402
|
-
* If a source or target is a dummy node and becomes unlinked (no segments), delete it.
|
|
403
|
-
*
|
|
404
|
-
* @param {string} segId - Segment ID
|
|
405
|
-
*/
|
|
406
|
-
_deleteSeg(segId) {
|
|
407
|
-
const seg = this.segs.get(segId);
|
|
408
|
-
if (!seg) return;
|
|
409
|
-
this._unlinkSeg(seg);
|
|
410
|
-
this.segs.delete(segId);
|
|
411
|
-
for (const side of ["source", "target"]) {
|
|
412
|
-
const node = this.getNode(seg[side].id);
|
|
413
|
-
if (node.isDummy && this._nodeIsUnlinked(node.id))
|
|
414
|
-
this._deleteNode(node.id);
|
|
415
|
-
}
|
|
416
|
-
},
|
|
417
|
-
/**
|
|
418
|
-
* Remove a relationship (edge or segment) from the graph and unlink it from its source and target nodes
|
|
419
|
-
*
|
|
420
|
-
* @param {string} relId - Relationship ID
|
|
421
|
-
*/
|
|
422
|
-
_deleteRelById(relId) {
|
|
423
|
-
if (relId.startsWith("e:"))
|
|
424
|
-
this._deleteEdge(relId);
|
|
425
|
-
else
|
|
426
|
-
this._deleteSeg(relId);
|
|
427
|
-
},
|
|
428
|
-
/**
|
|
429
|
-
* Return an iterator over the relationships (edges and segments) of a node.
|
|
430
|
-
*
|
|
431
|
-
* @param {string} nodeId - Node ID
|
|
432
|
-
* @param {string} type - Type of relationship (defaults to 'both')
|
|
433
|
-
* @param {string} dir - Direction of relationship (defaults to 'both')
|
|
434
|
-
* @returns {Iterator} Iterator over the relationships
|
|
435
|
-
*/
|
|
436
|
-
*_relIds(nodeId, type = "both", dir = "both") {
|
|
437
|
-
const node = this.getNode(nodeId);
|
|
438
|
-
const types = type == "both" ? ["edges", "segs"] : [type];
|
|
439
|
-
const dirs = dir == "both" ? ["in", "out"] : [dir];
|
|
440
|
-
for (const type2 of types)
|
|
441
|
-
for (const dir2 of dirs)
|
|
442
|
-
yield* node[type2][dir2];
|
|
443
|
-
},
|
|
444
|
-
/**
|
|
445
|
-
* Return an iterator over the relationships (edges and segments) of a node.
|
|
446
|
-
*
|
|
447
|
-
* @param {string} nodeId - Node ID
|
|
448
|
-
* @param {string} type - Type of relationship (defaults to 'both')
|
|
449
|
-
* @param {string} dir - Direction of relationship (defaults to 'both')
|
|
450
|
-
* @returns {Iterator} Iterator over the relationships
|
|
451
|
-
*/
|
|
452
|
-
*_rels(nodeId, type = "both", dir = "both") {
|
|
453
|
-
for (const relId of this._relIds(nodeId, type, dir))
|
|
454
|
-
yield this._getRel(relId);
|
|
455
|
-
},
|
|
456
|
-
/**
|
|
457
|
-
* Return an iterator over the neighbors of a node.
|
|
458
|
-
*
|
|
459
|
-
* @param {string} nodeId - Node ID
|
|
460
|
-
* @param {string} type - Type of relationship (defaults to 'both')
|
|
461
|
-
* @param {string} dir - Direction of relationship (defaults to 'both')
|
|
462
|
-
* @returns {Iterator} Iterator over the neighbors
|
|
463
|
-
*/
|
|
464
|
-
*_adjIds(nodeId, type = "both", dir = "both") {
|
|
465
|
-
const nodeIds = /* @__PURE__ */ new Set();
|
|
466
|
-
if (dir == "both" || dir == "in")
|
|
467
|
-
for (const rel of this._rels(nodeId, type, "in"))
|
|
468
|
-
nodeIds.add(rel.source.id);
|
|
469
|
-
if (dir == "both" || dir == "out")
|
|
470
|
-
for (const rel of this._rels(nodeId, type, "out"))
|
|
471
|
-
nodeIds.add(rel.target.id);
|
|
472
|
-
yield* nodeIds;
|
|
473
|
-
},
|
|
474
|
-
/**
|
|
475
|
-
* Return an iterator over the neighbors of a node.
|
|
476
|
-
*
|
|
477
|
-
* @param {string} nodeId - Node ID
|
|
478
|
-
* @param {string} type - Type of relationship (defaults to 'both')
|
|
479
|
-
* @param {string} dir - Direction of relationship (defaults to 'both')
|
|
480
|
-
*/
|
|
481
|
-
*_adjs(nodeId, type = "both", dir = "both") {
|
|
482
|
-
for (const adjId of this._adjIds(nodeId, type, dir))
|
|
483
|
-
yield this.getNode(adjId);
|
|
484
|
-
},
|
|
485
|
-
/**
|
|
486
|
-
* Remove a segment from an edge
|
|
487
|
-
*
|
|
488
|
-
* @param {string} edgeId - Edge ID
|
|
489
|
-
* @param {string} segId - Segment ID
|
|
490
|
-
*/
|
|
491
|
-
_edgeDeleteSeg(edgeId, segId) {
|
|
492
|
-
const edge = this.getEdge(edgeId);
|
|
493
|
-
const segs = edge.segs.filter((id) => id == segId);
|
|
494
|
-
this.edges.set(edgeId, { ...edge, segs });
|
|
495
|
-
},
|
|
496
|
-
/**
|
|
497
|
-
* Remove an edge from a segment and delete the segment if it becomes empty.
|
|
498
|
-
*
|
|
499
|
-
* @param {string} segId - Segment ID
|
|
500
|
-
* @param {string} edgeId - Edge ID
|
|
501
|
-
*/
|
|
502
|
-
_segDeleteEdge(segId, edgeId) {
|
|
503
|
-
const seg = this.getSeg(segId);
|
|
504
|
-
const edges = seg.edges.remove(edgeId);
|
|
505
|
-
if (edges.size == 0)
|
|
506
|
-
this._deleteSeg(segId);
|
|
507
|
-
else
|
|
508
|
-
this.segs.set(segId, { ...seg, edges });
|
|
509
|
-
},
|
|
510
|
-
/**
|
|
511
|
-
* Replace a segment in an edge
|
|
512
|
-
*
|
|
513
|
-
* @param {string} edgeId - Edge ID
|
|
514
|
-
* @param {string} oldSegId - Old segment ID
|
|
515
|
-
* @param {string} newSegId - New segment ID
|
|
516
|
-
*/
|
|
517
|
-
_edgeReplaceSeg(edgeId, oldSegId, newSegId) {
|
|
518
|
-
log3.debug(`edge ${edgeId}: replacing segment ${oldSegId} with ${newSegId}`);
|
|
519
|
-
this._segDeleteEdge(oldSegId, edgeId);
|
|
520
|
-
const edge = this.getEdge(edgeId);
|
|
521
|
-
const segs = edge.segs.map((id) => id == oldSegId ? newSegId : id);
|
|
522
|
-
this.edges.set(edgeId, { ...edge, segs });
|
|
523
355
|
}
|
|
524
356
|
};
|
|
525
357
|
|
|
526
|
-
// src/graph
|
|
527
|
-
import { Set as ISet2
|
|
528
|
-
var
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const node = this.getNode(sorted[i]);
|
|
567
|
-
this.nodes.set(sorted[i], { ...node, index: i });
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
this.layers.set(layerId, {
|
|
572
|
-
...layer,
|
|
573
|
-
nodes: layer.nodes.delete(nodeId),
|
|
574
|
-
sorted
|
|
575
|
-
});
|
|
576
|
-
if (this._layerIsEmpty(layerId))
|
|
577
|
-
this._deleteLayer(layerId);
|
|
578
|
-
},
|
|
579
|
-
/**
|
|
580
|
-
* Update layers in two passes:
|
|
581
|
-
*
|
|
582
|
-
* - Move children up or down to just below lowest parent
|
|
583
|
-
* - Move parents down to just above highest child
|
|
584
|
-
*
|
|
585
|
-
* While moving nodes between layers, if any layer becomes empty,
|
|
586
|
-
* remove it from the list; at the end, renumber the remaining layers
|
|
587
|
-
*/
|
|
588
|
-
_updateLayers() {
|
|
589
|
-
const stack = [...this._dirtyNodes].filter((id) => {
|
|
590
|
-
const node = this.nodes.get(id);
|
|
591
|
-
if (!node || node.isDummy) return false;
|
|
592
|
-
return true;
|
|
358
|
+
// src/graph/types/seg.ts
|
|
359
|
+
import { Record as Record3, Set as ISet2 } from "immutable";
|
|
360
|
+
var defaultSegProps = {
|
|
361
|
+
id: "",
|
|
362
|
+
source: { id: "" },
|
|
363
|
+
target: { id: "" },
|
|
364
|
+
edgeIds: ISet2(),
|
|
365
|
+
mutable: false
|
|
366
|
+
};
|
|
367
|
+
var Seg = class _Seg extends Record3(defaultSegProps) {
|
|
368
|
+
static prefix = "s:";
|
|
369
|
+
mut(g) {
|
|
370
|
+
if (this.mutable) return this;
|
|
371
|
+
return g.mutateSeg(this);
|
|
372
|
+
}
|
|
373
|
+
final() {
|
|
374
|
+
if (!this.mutable) return this;
|
|
375
|
+
return this.merge({
|
|
376
|
+
edgeIds: this.edgeIds.asImmutable(),
|
|
377
|
+
mutable: false
|
|
378
|
+
}).asImmutable();
|
|
379
|
+
}
|
|
380
|
+
get p1() {
|
|
381
|
+
return this.source.pos;
|
|
382
|
+
}
|
|
383
|
+
get p2() {
|
|
384
|
+
return this.target.pos;
|
|
385
|
+
}
|
|
386
|
+
anySameEnd(other) {
|
|
387
|
+
return this.sameEnd(other, "source") || this.sameEnd(other, "target");
|
|
388
|
+
}
|
|
389
|
+
sameEnd(other, side) {
|
|
390
|
+
const mine = this[side];
|
|
391
|
+
const yours = other[side];
|
|
392
|
+
return mine.id === yours.id && mine.port === yours.port;
|
|
393
|
+
}
|
|
394
|
+
setPos(g, source, target) {
|
|
395
|
+
return this.mut(g).merge({
|
|
396
|
+
source: { ...this.source, pos: source },
|
|
397
|
+
target: { ...this.target, pos: target }
|
|
593
398
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
);
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
for (const grandParentId of this._adjIds(parentId, "edges", "in"))
|
|
643
|
-
addParent(grandParentId);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
399
|
+
}
|
|
400
|
+
setTrackPos(g, trackPos) {
|
|
401
|
+
if (this.trackPos == trackPos) return this;
|
|
402
|
+
return this.mut(g).set("trackPos", trackPos);
|
|
403
|
+
}
|
|
404
|
+
setSVG(g, svg) {
|
|
405
|
+
if (this.svg == svg) return this;
|
|
406
|
+
return this.mut(g).set("svg", svg);
|
|
407
|
+
}
|
|
408
|
+
link(g) {
|
|
409
|
+
this.sourceNode(g).addOutSeg(g, this.id);
|
|
410
|
+
this.targetNode(g).addInSeg(g, this.id);
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
unlink(g) {
|
|
414
|
+
this.sourceNode(g).delOutSeg(g, this.id);
|
|
415
|
+
this.targetNode(g).delInSeg(g, this.id);
|
|
416
|
+
return this;
|
|
417
|
+
}
|
|
418
|
+
delSelf(g) {
|
|
419
|
+
this.unlink(g);
|
|
420
|
+
g.segs.delete(this.id);
|
|
421
|
+
g.dirtySegs.delete(this.id);
|
|
422
|
+
g.delSegs.add(this.id);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
*edges(g) {
|
|
426
|
+
for (const edgeId of this.edgeIds)
|
|
427
|
+
yield g.getEdge(edgeId);
|
|
428
|
+
}
|
|
429
|
+
node(g, side) {
|
|
430
|
+
return g.getNode(this[side].id);
|
|
431
|
+
}
|
|
432
|
+
sourceNode(g) {
|
|
433
|
+
return this.node(g, "source");
|
|
434
|
+
}
|
|
435
|
+
targetNode(g) {
|
|
436
|
+
return this.node(g, "target");
|
|
437
|
+
}
|
|
438
|
+
addEdgeId(g, edgeId) {
|
|
439
|
+
if (this.edgeIds.has(edgeId)) return this;
|
|
440
|
+
return this.mut(g).set("edgeIds", this.edgeIds.asMutable().add(edgeId));
|
|
441
|
+
}
|
|
442
|
+
delEdgeId(g, edgeId) {
|
|
443
|
+
if (!this.edgeIds.has(edgeId)) return this;
|
|
444
|
+
if (this.edgeIds.size == 1) {
|
|
445
|
+
this.delSelf(g);
|
|
446
|
+
return null;
|
|
646
447
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Move the node to a new layer, crushing the original layer
|
|
655
|
-
* if it becomes empty
|
|
656
|
-
*
|
|
657
|
-
* @param {string} nodeId - Node ID
|
|
658
|
-
* @param {number} newIndex - New layer index
|
|
659
|
-
*/
|
|
660
|
-
_moveNodeLayer(nodeId, newIndex) {
|
|
661
|
-
log4.debug(`moving node ${nodeId} to layer ${newIndex}`);
|
|
662
|
-
const node = this.getNode(nodeId);
|
|
663
|
-
const oldLayerId = node.layerId;
|
|
664
|
-
const newLayerId = this._layerAtIndex(newIndex).id;
|
|
665
|
-
this._layerDeleteNode(oldLayerId, nodeId);
|
|
666
|
-
this._layerAddNode(newLayerId, nodeId);
|
|
667
|
-
this.nodes.set(nodeId, { ...node, layerId: newLayerId });
|
|
668
|
-
},
|
|
669
|
-
/**
|
|
670
|
-
* Get the layer at the given index, creating it if necessary
|
|
671
|
-
*
|
|
672
|
-
* @param {number} index - Layer index
|
|
673
|
-
* @returns {Object} The layer
|
|
674
|
-
*/
|
|
675
|
-
_layerAtIndex(index) {
|
|
676
|
-
while (index >= this.layerList.size)
|
|
677
|
-
this._addLayer();
|
|
678
|
-
const layerId = this.layerList.get(index);
|
|
679
|
-
return this.layers.get(layerId);
|
|
680
|
-
},
|
|
681
|
-
/**
|
|
682
|
-
* Add a new layer. The caller should add a node to it so that
|
|
683
|
-
* it's not empty.
|
|
684
|
-
*/
|
|
685
|
-
_addLayer() {
|
|
686
|
-
const id = `l:${this.nextLayerId++}`;
|
|
687
|
-
this.layers.set(id, {
|
|
688
|
-
id,
|
|
689
|
-
index: this.layerList.size,
|
|
690
|
-
nodes: ISet2()
|
|
448
|
+
return this.mut(g).set("edgeIds", this.edgeIds.asMutable().remove(edgeId));
|
|
449
|
+
}
|
|
450
|
+
static add(g, props) {
|
|
451
|
+
const seg = new _Seg({
|
|
452
|
+
...props,
|
|
453
|
+
id: Edge.id(props, _Seg.prefix)
|
|
691
454
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
455
|
+
seg.link(g);
|
|
456
|
+
g.segs.set(seg.id, seg);
|
|
457
|
+
g.dirtySegs.add(seg.id);
|
|
458
|
+
return seg;
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// src/graph/types/layer.ts
|
|
463
|
+
import { Record as Record4, Set as ISet3 } from "immutable";
|
|
464
|
+
|
|
465
|
+
// src/log.ts
|
|
466
|
+
var levels = {
|
|
467
|
+
error: 0,
|
|
468
|
+
warn: 1,
|
|
469
|
+
info: 2,
|
|
470
|
+
debug: 3
|
|
471
|
+
};
|
|
472
|
+
var currentLevel = "debug";
|
|
473
|
+
function shouldLog(level) {
|
|
474
|
+
return levels[level] <= levels[currentLevel];
|
|
475
|
+
}
|
|
476
|
+
function logger(module) {
|
|
477
|
+
return {
|
|
478
|
+
error: (msg, ...args) => shouldLog("error") && console.error(`[${module}] ${msg}`, ...args),
|
|
479
|
+
warn: (msg, ...args) => shouldLog("warn") && console.warn(`[${module}] ${msg}`, ...args),
|
|
480
|
+
info: (msg, ...args) => shouldLog("info") && console.info(`[${module}] ${msg}`, ...args),
|
|
481
|
+
debug: (msg, ...args) => shouldLog("debug") && console.debug(`[${module}] ${msg}`, ...args)
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
var log = logger("core");
|
|
485
|
+
|
|
486
|
+
// src/graph/types/layer.ts
|
|
487
|
+
var log2 = logger("layer");
|
|
488
|
+
var defaultLayerProps = {
|
|
489
|
+
id: "",
|
|
490
|
+
index: 0,
|
|
491
|
+
nodeIds: ISet3(),
|
|
492
|
+
sorted: [],
|
|
493
|
+
tracks: [],
|
|
494
|
+
size: 0,
|
|
495
|
+
pos: 0,
|
|
496
|
+
isSorted: false,
|
|
497
|
+
mutable: false
|
|
498
|
+
};
|
|
499
|
+
var Layer = class extends Record4(defaultLayerProps) {
|
|
500
|
+
static prefix = "l:";
|
|
501
|
+
mut(g) {
|
|
502
|
+
if (this.mutable) return this;
|
|
503
|
+
return g.mutateLayer(this);
|
|
504
|
+
}
|
|
505
|
+
final() {
|
|
506
|
+
if (!this.mutable) return this;
|
|
507
|
+
return this.merge({
|
|
508
|
+
nodeIds: this.nodeIds.asImmutable(),
|
|
509
|
+
mutable: false
|
|
510
|
+
}).asImmutable();
|
|
511
|
+
}
|
|
512
|
+
get size() {
|
|
513
|
+
return this.nodeIds.size;
|
|
514
|
+
}
|
|
515
|
+
*nodes(g) {
|
|
516
|
+
for (const nodeId of this.nodeIds.values())
|
|
517
|
+
yield g.getNode(nodeId);
|
|
518
|
+
}
|
|
519
|
+
hasSortOrder(order) {
|
|
520
|
+
return order.length == this.sorted.length && this.sorted.every((nodeId, i) => order[i] == nodeId);
|
|
521
|
+
}
|
|
522
|
+
canCrush(g) {
|
|
523
|
+
for (const node of this.nodes(g))
|
|
524
|
+
if (!node.isDummy)
|
|
525
|
+
return false;
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
crush(g) {
|
|
529
|
+
g.layerList.remove(this.index);
|
|
530
|
+
g.layers.delete(this.id);
|
|
531
|
+
g.dirtyLayers.delete(this.id);
|
|
532
|
+
for (let i = this.index; i < g.layerList.size; i++)
|
|
533
|
+
g.getLayer(g.layerList.get(i)).setIndex(g, i);
|
|
534
|
+
for (const node of this.nodes(g))
|
|
535
|
+
if (node.isDummy) node.delSelf(g);
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
setIndex(g, index) {
|
|
539
|
+
if (this.index == index) return this;
|
|
540
|
+
return this.mut(g).set("index", index);
|
|
541
|
+
}
|
|
542
|
+
setTracks(g, tracks) {
|
|
543
|
+
if (this.tracks == tracks) return this;
|
|
544
|
+
return this.mut(g).set("tracks", tracks);
|
|
545
|
+
}
|
|
546
|
+
setSize(g, size) {
|
|
547
|
+
if (this.size == size) return this;
|
|
548
|
+
return this.mut(g).set("size", size);
|
|
549
|
+
}
|
|
550
|
+
setPos(g, pos) {
|
|
551
|
+
if (this.pos == pos) return this;
|
|
552
|
+
return this.mut(g).set("pos", pos);
|
|
553
|
+
}
|
|
554
|
+
addNode(g, nodeId) {
|
|
555
|
+
if (this.nodeIds.has(nodeId)) return this;
|
|
556
|
+
return this.mut(g).set("nodeIds", this.nodeIds.asMutable().add(nodeId));
|
|
557
|
+
}
|
|
558
|
+
willCrush(g, nodeId) {
|
|
559
|
+
for (const node of this.nodes(g))
|
|
560
|
+
if (!node.isDummy && node.id != nodeId)
|
|
561
|
+
return false;
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
reindex(g, nodeId) {
|
|
565
|
+
if (!this.isSorted) return void 0;
|
|
566
|
+
const sorted = this.sorted.filter((id) => id != nodeId);
|
|
567
|
+
const idx = this.sorted.findIndex((id) => id == nodeId);
|
|
568
|
+
for (let i = idx; i < sorted.length; i++)
|
|
569
|
+
g.getNode(sorted[i]).setIndex(g, i);
|
|
570
|
+
return sorted;
|
|
571
|
+
}
|
|
572
|
+
delNode(g, nodeId) {
|
|
573
|
+
if (!this.nodeIds.has(nodeId)) return this;
|
|
574
|
+
if (this.willCrush(g, nodeId)) return this.crush(g);
|
|
575
|
+
const nodeIds = this.nodeIds.asMutable().remove(nodeId);
|
|
576
|
+
const sorted = this.reindex(g, nodeId);
|
|
577
|
+
return this.mut(g).merge({ nodeIds, sorted });
|
|
578
|
+
}
|
|
579
|
+
setSorted(g, nodeIds) {
|
|
580
|
+
if (this.hasSortOrder(nodeIds)) return this;
|
|
581
|
+
console.log(`setting sorted for layer ${this.id}`);
|
|
582
|
+
nodeIds.forEach((nodeId, i) => g.getNode(nodeId).setIndex(g, i));
|
|
583
|
+
return this.mut(g).merge({ sorted: nodeIds, isSorted: true });
|
|
584
|
+
}
|
|
585
|
+
*outEdges(g) {
|
|
586
|
+
for (const node of this.nodes(g))
|
|
587
|
+
yield* node.outEdges(g);
|
|
722
588
|
}
|
|
723
589
|
};
|
|
724
590
|
|
|
725
|
-
// src/mutator.
|
|
591
|
+
// src/graph/types/mutator.ts
|
|
726
592
|
var Mutator = class {
|
|
593
|
+
changes;
|
|
727
594
|
constructor() {
|
|
728
595
|
this.changes = {
|
|
729
596
|
addedNodes: [],
|
|
730
597
|
removedNodes: [],
|
|
598
|
+
updatedNodes: [],
|
|
731
599
|
addedEdges: [],
|
|
732
600
|
removedEdges: []
|
|
733
601
|
};
|
|
@@ -738,6 +606,15 @@ var Mutator = class {
|
|
|
738
606
|
addNodes(...nodes) {
|
|
739
607
|
nodes.forEach((node) => this.addNode(node));
|
|
740
608
|
}
|
|
609
|
+
updateNode(node) {
|
|
610
|
+
if (typeof node === "string")
|
|
611
|
+
this.changes.updatedNodes.push({ id: node });
|
|
612
|
+
else
|
|
613
|
+
this.changes.updatedNodes.push(node);
|
|
614
|
+
}
|
|
615
|
+
updateNodes(...nodes) {
|
|
616
|
+
nodes.forEach((node) => this.updateNode(node));
|
|
617
|
+
}
|
|
741
618
|
addEdge(edge) {
|
|
742
619
|
this.changes.addedEdges.push(edge);
|
|
743
620
|
}
|
|
@@ -745,7 +622,7 @@ var Mutator = class {
|
|
|
745
622
|
edges.forEach((edge) => this.addEdge(edge));
|
|
746
623
|
}
|
|
747
624
|
removeNode(node) {
|
|
748
|
-
if (typeof node
|
|
625
|
+
if (typeof node === "string")
|
|
749
626
|
this.changes.removedNodes.push({ id: node });
|
|
750
627
|
else
|
|
751
628
|
this.changes.removedNodes.push(node);
|
|
@@ -761,274 +638,94 @@ var Mutator = class {
|
|
|
761
638
|
}
|
|
762
639
|
};
|
|
763
640
|
|
|
764
|
-
// src/graph
|
|
765
|
-
var
|
|
766
|
-
|
|
767
|
-
return
|
|
768
|
-
},
|
|
769
|
-
numNodes() {
|
|
770
|
-
return this.nodes.size;
|
|
771
|
-
},
|
|
772
|
-
numEdges() {
|
|
773
|
-
return this.edges.size;
|
|
774
|
-
},
|
|
775
|
-
getNode(nodeId) {
|
|
776
|
-
const node = this.nodes.get(nodeId);
|
|
777
|
-
if (node) return node;
|
|
778
|
-
throw new Error(`cannot find node ${nodeId}`);
|
|
779
|
-
},
|
|
780
|
-
getEdge(edgeId) {
|
|
781
|
-
const edge = this.edges.get(edgeId);
|
|
782
|
-
if (edge) return edge;
|
|
783
|
-
throw new Error(`cannot find edge ${edgeId}`);
|
|
784
|
-
},
|
|
785
|
-
getSeg(segId) {
|
|
786
|
-
const seg = this.segs.get(segId);
|
|
787
|
-
if (seg) return seg;
|
|
788
|
-
throw new Error(`cannot find segment ${segId}`);
|
|
789
|
-
},
|
|
790
|
-
getLayer(layerId) {
|
|
791
|
-
const layer = this.layers.get(layerId);
|
|
792
|
-
if (layer) return layer;
|
|
793
|
-
throw new Error(`cannot find layer ${layerId}`);
|
|
794
|
-
},
|
|
795
|
-
hasNode(nodeId) {
|
|
796
|
-
return this.nodes.has(nodeId);
|
|
797
|
-
},
|
|
798
|
-
hasEdge(edgeId) {
|
|
799
|
-
return this.edges.has(edgeId);
|
|
800
|
-
},
|
|
801
|
-
withMutations(callback) {
|
|
802
|
-
const mut = new Mutator();
|
|
803
|
-
callback(mut);
|
|
804
|
-
return new Graph({ prior: this, changes: mut.changes });
|
|
805
|
-
},
|
|
806
|
-
addNodes(...nodes) {
|
|
807
|
-
return this.withMutations((mutator) => {
|
|
808
|
-
nodes.forEach((node) => mutator.addNode(node));
|
|
809
|
-
});
|
|
810
|
-
},
|
|
811
|
-
addNode(node) {
|
|
812
|
-
return this.withMutations((mutator) => {
|
|
813
|
-
mutator.addNode(node);
|
|
814
|
-
});
|
|
815
|
-
},
|
|
816
|
-
addEdges(...edges) {
|
|
817
|
-
return this.withMutations((mutator) => {
|
|
818
|
-
edges.forEach((edge) => mutator.addEdge(edge));
|
|
819
|
-
});
|
|
820
|
-
},
|
|
821
|
-
addEdge(edge) {
|
|
822
|
-
return this.withMutations((mutator) => {
|
|
823
|
-
mutator.addEdge(edge);
|
|
824
|
-
});
|
|
825
|
-
},
|
|
826
|
-
removeNodes(...nodes) {
|
|
827
|
-
return this.withMutations((mutator) => {
|
|
828
|
-
nodes.forEach((node) => mutator.removeNode(node));
|
|
829
|
-
});
|
|
830
|
-
},
|
|
831
|
-
removeNode(node) {
|
|
832
|
-
return this.withMutations((mutator) => {
|
|
833
|
-
mutator.removeNode(node);
|
|
834
|
-
});
|
|
835
|
-
},
|
|
836
|
-
removeEdges(...edges) {
|
|
837
|
-
return this.withMutations((mutator) => {
|
|
838
|
-
edges.forEach((edge) => mutator.removeEdge(edge));
|
|
839
|
-
});
|
|
840
|
-
},
|
|
841
|
-
removeEdge(edge) {
|
|
842
|
-
return this.withMutations((mutator) => {
|
|
843
|
-
mutator.removeEdge(edge);
|
|
844
|
-
});
|
|
641
|
+
// src/graph/services/cycles.ts
|
|
642
|
+
var Cycles = class _Cycles {
|
|
643
|
+
static info(g, node) {
|
|
644
|
+
return node.id;
|
|
845
645
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
var GraphMutate = {
|
|
850
|
-
/**
|
|
851
|
-
* Put the graph in mutate mode, where all the listed
|
|
852
|
-
* stateful (immutable) collections are also put in
|
|
853
|
-
* mutate mode. Within the callback, the collections
|
|
854
|
-
* are modified in place.
|
|
855
|
-
*
|
|
856
|
-
* @param {Function} callback - The callback to run
|
|
857
|
-
*/
|
|
858
|
-
_mutate(callback) {
|
|
859
|
-
const state = [
|
|
860
|
-
"nodes",
|
|
861
|
-
"edges",
|
|
862
|
-
"layers",
|
|
863
|
-
"layerList",
|
|
864
|
-
"segs"
|
|
865
|
-
];
|
|
866
|
-
const mut = () => {
|
|
867
|
-
if (state.length == 0) return callback();
|
|
868
|
-
const name = state.shift();
|
|
869
|
-
this[name] = this[name].withMutations((map) => {
|
|
870
|
-
this[name] = map;
|
|
871
|
-
mut();
|
|
872
|
-
});
|
|
873
|
-
};
|
|
874
|
-
mut();
|
|
875
|
-
},
|
|
876
|
-
/**
|
|
877
|
-
* Update the graph by applying changes and updating
|
|
878
|
-
* the computed graph state.
|
|
879
|
-
*/
|
|
880
|
-
_update() {
|
|
881
|
-
if (!this._dirty) return;
|
|
882
|
-
this._mutate(() => {
|
|
883
|
-
this._applyChanges();
|
|
884
|
-
this._checkCycles();
|
|
885
|
-
this._updateLayers();
|
|
886
|
-
this._updateDummies();
|
|
887
|
-
this._mergeDummies();
|
|
888
|
-
this._positionNodes();
|
|
889
|
-
this._alignAll();
|
|
890
|
-
});
|
|
891
|
-
this._dirty = false;
|
|
892
|
-
},
|
|
893
|
-
/**
|
|
894
|
-
* Mark a node as dirty if it exists in the graph.
|
|
895
|
-
*
|
|
896
|
-
* @param {string} nodeId - Node ID
|
|
897
|
-
*/
|
|
898
|
-
_markDirty(nodeId) {
|
|
899
|
-
if (this.nodes.has(nodeId))
|
|
900
|
-
this._dirtyNodes.add(nodeId);
|
|
901
|
-
},
|
|
902
|
-
/**
|
|
903
|
-
* Apply node and edge changes to the graph
|
|
904
|
-
*/
|
|
905
|
-
_applyChanges() {
|
|
906
|
-
for (const node of this.changes.addedNodes)
|
|
907
|
-
this._addNode(node);
|
|
908
|
-
for (const node of this.changes.removedNodes)
|
|
909
|
-
this._deleteNode(node.id);
|
|
910
|
-
for (const edge of this.changes.addedEdges)
|
|
911
|
-
this._addEdge(edge);
|
|
912
|
-
for (const edge of this.changes.removedEdges)
|
|
913
|
-
this._deleteEdge(edge.id ?? this._edgeId(edge));
|
|
914
|
-
}
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
// src/graph-cycle.js
|
|
918
|
-
var GraphCycle = {
|
|
919
|
-
/**
|
|
920
|
-
* Get the cycle info for a node
|
|
921
|
-
*
|
|
922
|
-
* @param {string} nodeId - Node ID
|
|
923
|
-
* @returns {string} The cycle info
|
|
924
|
-
*/
|
|
925
|
-
_cycleInfo(nodeId) {
|
|
926
|
-
return nodeId;
|
|
927
|
-
},
|
|
928
|
-
/**
|
|
929
|
-
* Check for cycles in the graph. If any are detected, throw an error.
|
|
930
|
-
* Depending on the size of the graph and the number of changes, use
|
|
931
|
-
* different algorithms.
|
|
932
|
-
*/
|
|
933
|
-
_checkCycles() {
|
|
934
|
-
const totalNodes = this.nodes.size;
|
|
935
|
-
const newStuff = this.changes.addedNodes.length + this.changes.addedEdges.length;
|
|
646
|
+
static checkCycles(g) {
|
|
647
|
+
const totalNodes = g.nodes.size;
|
|
648
|
+
const newStuff = g.changes.addedNodes.length + g.changes.addedEdges.length;
|
|
936
649
|
const changeRatio = newStuff / totalNodes;
|
|
937
650
|
if (changeRatio > 0.2 || totalNodes < 20)
|
|
938
|
-
|
|
651
|
+
_Cycles.checkCyclesFull(g);
|
|
939
652
|
else
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
* Use a graph traversal algorithm to check for cycles.
|
|
944
|
-
*/
|
|
945
|
-
_checkCyclesFull() {
|
|
653
|
+
_Cycles.checkCyclesIncremental(g);
|
|
654
|
+
}
|
|
655
|
+
static checkCyclesFull(g) {
|
|
946
656
|
const colorMap = /* @__PURE__ */ new Map();
|
|
947
657
|
const parentMap = /* @__PURE__ */ new Map();
|
|
948
|
-
const white = 0, gray = 1, black = 2;
|
|
949
658
|
let start, end;
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
659
|
+
const white = 0, gray = 1, black = 2;
|
|
660
|
+
const visit = (node2) => {
|
|
661
|
+
colorMap.set(node2, gray);
|
|
662
|
+
for (const next of node2.outNodes(g)) {
|
|
663
|
+
switch (colorMap.get(next) ?? white) {
|
|
954
664
|
case gray:
|
|
955
|
-
start =
|
|
956
|
-
end =
|
|
665
|
+
start = next;
|
|
666
|
+
end = node2;
|
|
957
667
|
return true;
|
|
958
668
|
case white:
|
|
959
|
-
parentMap.set(
|
|
960
|
-
if (visit(
|
|
669
|
+
parentMap.set(next, node2);
|
|
670
|
+
if (visit(next)) return true;
|
|
961
671
|
}
|
|
962
672
|
}
|
|
963
|
-
colorMap.set(
|
|
673
|
+
colorMap.set(node2, black);
|
|
964
674
|
return false;
|
|
965
675
|
};
|
|
966
|
-
for (const
|
|
967
|
-
if ((colorMap.get(
|
|
968
|
-
if (visit(
|
|
676
|
+
for (const node2 of g.getNodes())
|
|
677
|
+
if ((colorMap.get(node2) ?? white) == white) {
|
|
678
|
+
if (visit(node2)) break;
|
|
969
679
|
}
|
|
970
|
-
if (!start) return;
|
|
971
|
-
const cycle = [
|
|
972
|
-
let
|
|
973
|
-
while (
|
|
974
|
-
cycle.push(
|
|
975
|
-
|
|
680
|
+
if (!start || !end) return;
|
|
681
|
+
const cycle = [start];
|
|
682
|
+
let node = end;
|
|
683
|
+
while (node != start) {
|
|
684
|
+
cycle.push(node);
|
|
685
|
+
node = parentMap.get(node);
|
|
976
686
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
* new edge, if the source is < the target, there won't be a cycle.
|
|
986
|
-
* Otherwise, check if there is a route from the target to the source;
|
|
987
|
-
* if so, throw an error.
|
|
988
|
-
*/
|
|
989
|
-
_checkCyclesIncremental() {
|
|
990
|
-
for (const edge of this.changes.addedEdges) {
|
|
991
|
-
const layer1 = this._nodeLayerIndex(edge.source.id);
|
|
992
|
-
const layer2 = this._nodeLayerIndex(edge.target.id);
|
|
687
|
+
_Cycles.throwCycle(g, cycle);
|
|
688
|
+
}
|
|
689
|
+
static checkCyclesIncremental(g) {
|
|
690
|
+
for (const edge of g.changes.addedEdges) {
|
|
691
|
+
const source = g.getNode(edge.source.id);
|
|
692
|
+
const target = g.getNode(edge.target.id);
|
|
693
|
+
const layer1 = source.layerIndex(g);
|
|
694
|
+
const layer2 = target.layerIndex(g);
|
|
993
695
|
if (layer1 < layer2) continue;
|
|
994
|
-
const route =
|
|
696
|
+
const route = _Cycles.findRoute(g, target, source);
|
|
995
697
|
if (!route) continue;
|
|
996
|
-
|
|
997
|
-
cycle.reverse();
|
|
998
|
-
const error = new Error(`Cycle detected: ${cycle.join(" \u2192 ")}`);
|
|
999
|
-
error.cycle = cycle;
|
|
1000
|
-
throw error;
|
|
698
|
+
_Cycles.throwCycle(g, route);
|
|
1001
699
|
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
_findRoute(sourceId, targetId) {
|
|
700
|
+
}
|
|
701
|
+
static throwCycle(g, cycle) {
|
|
702
|
+
cycle.push(cycle[0]);
|
|
703
|
+
cycle.reverse();
|
|
704
|
+
const info = cycle.map((node) => _Cycles.info(g, node));
|
|
705
|
+
throw new Error(`Cycle detected: ${info.join(" \u2192 ")}`);
|
|
706
|
+
}
|
|
707
|
+
static findRoute(g, source, target) {
|
|
1011
708
|
const parentMap = /* @__PURE__ */ new Map();
|
|
1012
|
-
const queue = [
|
|
1013
|
-
const visited = /* @__PURE__ */ new Set([
|
|
709
|
+
const queue = [source];
|
|
710
|
+
const visited = /* @__PURE__ */ new Set([source]);
|
|
1014
711
|
while (queue.length > 0) {
|
|
1015
|
-
const
|
|
1016
|
-
if (
|
|
712
|
+
const node = queue.shift();
|
|
713
|
+
if (node == target) {
|
|
1017
714
|
const route = [];
|
|
1018
|
-
let
|
|
1019
|
-
while (
|
|
1020
|
-
route.push(
|
|
1021
|
-
|
|
715
|
+
let currNode = target;
|
|
716
|
+
while (currNode != source) {
|
|
717
|
+
route.push(currNode);
|
|
718
|
+
currNode = parentMap.get(currNode);
|
|
1022
719
|
}
|
|
1023
|
-
route.push(
|
|
720
|
+
route.push(source);
|
|
1024
721
|
route.reverse();
|
|
1025
722
|
return route;
|
|
1026
723
|
}
|
|
1027
|
-
for (const
|
|
1028
|
-
if (!visited.has(
|
|
1029
|
-
visited.add(
|
|
1030
|
-
parentMap.set(
|
|
1031
|
-
queue.push(
|
|
724
|
+
for (const next of node.outNodes(g)) {
|
|
725
|
+
if (!visited.has(next)) {
|
|
726
|
+
visited.add(next);
|
|
727
|
+
parentMap.set(next, node);
|
|
728
|
+
queue.push(next);
|
|
1032
729
|
}
|
|
1033
730
|
}
|
|
1034
731
|
}
|
|
@@ -1036,51 +733,46 @@ var GraphCycle = {
|
|
|
1036
733
|
}
|
|
1037
734
|
};
|
|
1038
735
|
|
|
1039
|
-
// src/graph
|
|
1040
|
-
import { Set as
|
|
1041
|
-
var
|
|
1042
|
-
var
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
* also store a set of the edges that use them.
|
|
1049
|
-
*/
|
|
1050
|
-
_updateDummies() {
|
|
1051
|
-
for (const edgeId of this._dirtyEdges) {
|
|
1052
|
-
const edge = this.getEdge(edgeId);
|
|
736
|
+
// src/graph/services/dummy.ts
|
|
737
|
+
import { Set as ISet4 } from "immutable";
|
|
738
|
+
var log3 = logger("dummy");
|
|
739
|
+
var Dummy = class _Dummy {
|
|
740
|
+
static updateDummies(g) {
|
|
741
|
+
log3.debug(`updating dummies:`, [...g.dirtyEdges]);
|
|
742
|
+
for (const edgeId of g.dirtyEdges) {
|
|
743
|
+
log3.debug(`updating dummies of edge ${edgeId}`);
|
|
744
|
+
const edge = g.getEdge(edgeId);
|
|
1053
745
|
const { type } = edge;
|
|
1054
|
-
const sourceLayer =
|
|
1055
|
-
const targetLayer =
|
|
746
|
+
const sourceLayer = edge.sourceNode(g).layerIndex(g);
|
|
747
|
+
const targetLayer = edge.targetNode(g).layerIndex(g);
|
|
1056
748
|
let segIndex = 0;
|
|
1057
749
|
let changed = false;
|
|
1058
750
|
let source = edge.source;
|
|
1059
|
-
const segs = edge.
|
|
751
|
+
const segs = edge.segIds;
|
|
1060
752
|
for (let layerIndex = sourceLayer + 1; layerIndex <= targetLayer; layerIndex++) {
|
|
1061
|
-
const layer =
|
|
753
|
+
const layer = g.layerAt(layerIndex);
|
|
1062
754
|
while (true) {
|
|
1063
755
|
const segId = segs[segIndex];
|
|
1064
|
-
let seg = segId ?
|
|
1065
|
-
const segLayer = seg ?
|
|
756
|
+
let seg = segId ? g.getSeg(segId) : null;
|
|
757
|
+
const segLayer = seg ? seg.targetNode(g).layerIndex(g) : null;
|
|
1066
758
|
if (segIndex == segs.length || segLayer > layerIndex) {
|
|
1067
759
|
let target;
|
|
1068
760
|
if (layerIndex == targetLayer) {
|
|
1069
761
|
target = edge.target;
|
|
1070
762
|
} else {
|
|
1071
|
-
const dummy =
|
|
1072
|
-
edgeId,
|
|
763
|
+
const dummy = Node.addDummy(g, {
|
|
764
|
+
edgeIds: [edgeId],
|
|
1073
765
|
layerId: layer.id
|
|
1074
766
|
});
|
|
1075
|
-
target = {
|
|
767
|
+
target = { id: dummy.id };
|
|
1076
768
|
}
|
|
1077
|
-
seg =
|
|
1078
|
-
|
|
769
|
+
seg = Seg.add(g, { source, target, type, edgeIds: ISet4([edgeId]) });
|
|
770
|
+
log3.debug(`edge ${edgeId}: adding segment ${seg.id} from ${source.id} at layer ${layerIndex - 1} to ${target.id} at layer ${layerIndex}`);
|
|
1079
771
|
segs.splice(segIndex, 0, seg.id);
|
|
1080
772
|
changed = true;
|
|
1081
773
|
} else if (segLayer < layerIndex || seg.source.id != source.id || seg.source.port != source.port || layerIndex == targetLayer && (seg.target.id != edge.target.id || seg.target.port != edge.target.port)) {
|
|
1082
|
-
|
|
1083
|
-
|
|
774
|
+
log3.debug(`edge ${edgeId}: removing segment ${seg.id} from layer ${layerIndex - 1} to layer ${layerIndex}`);
|
|
775
|
+
seg = seg.delEdgeId(g, edgeId);
|
|
1084
776
|
segs.splice(segIndex, 1);
|
|
1085
777
|
changed = true;
|
|
1086
778
|
continue;
|
|
@@ -1091,68 +783,70 @@ var GraphDummy = {
|
|
|
1091
783
|
}
|
|
1092
784
|
}
|
|
1093
785
|
while (segIndex < segs.length) {
|
|
1094
|
-
|
|
1095
|
-
|
|
786
|
+
log3.debug(`edge ${edgeId}: removing trailing segment ${segs[segIndex]}`);
|
|
787
|
+
g.getSeg(segs[segIndex]).delEdgeId(g, edgeId);
|
|
1096
788
|
segs.splice(segIndex, 1);
|
|
1097
789
|
changed = true;
|
|
1098
790
|
segIndex++;
|
|
1099
791
|
}
|
|
1100
792
|
if (changed) {
|
|
1101
|
-
|
|
1102
|
-
|
|
793
|
+
log3.debug(`edge ${edgeId}: updated segments to ${segs.join(", ")}`);
|
|
794
|
+
edge.setSegIds(g, segs);
|
|
1103
795
|
}
|
|
1104
796
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
for (const side of
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
let
|
|
1112
|
-
if (side == "target")
|
|
797
|
+
}
|
|
798
|
+
static mergeDummies(g) {
|
|
799
|
+
for (const side of g.options.mergeOrder)
|
|
800
|
+
_Dummy.mergeScan(g, side);
|
|
801
|
+
}
|
|
802
|
+
static mergeScan(g, side) {
|
|
803
|
+
let layerIds = [...g.layerList].filter((layerId) => g.dirtyLayers.has(layerId));
|
|
804
|
+
if (side == "target") layerIds.reverse();
|
|
1113
805
|
const dir = side == "source" ? "in" : "out";
|
|
1114
806
|
const altSide = side == "source" ? "target" : "source";
|
|
1115
807
|
const altDir = altSide == "source" ? "in" : "out";
|
|
1116
|
-
|
|
1117
|
-
for (const layerId of
|
|
1118
|
-
let layer =
|
|
808
|
+
log3.debug(`merging dummies by ${side}`);
|
|
809
|
+
for (const layerId of layerIds) {
|
|
810
|
+
let layer = g.getLayer(layerId);
|
|
1119
811
|
const groups = /* @__PURE__ */ new Map();
|
|
1120
|
-
for (const nodeId of layer.
|
|
1121
|
-
if (!
|
|
1122
|
-
const node =
|
|
1123
|
-
if (node.
|
|
1124
|
-
const edge =
|
|
1125
|
-
const key =
|
|
812
|
+
for (const nodeId of layer.nodeIds) {
|
|
813
|
+
if (!Node.isDummyId(nodeId)) continue;
|
|
814
|
+
const node = g.getNode(nodeId);
|
|
815
|
+
if (node.isMerged) continue;
|
|
816
|
+
const edge = g.getEdge(node.edgeIds[0]);
|
|
817
|
+
const key = Edge.id(edge, "k:", side);
|
|
1126
818
|
if (!groups.has(key)) groups.set(key, /* @__PURE__ */ new Set());
|
|
1127
819
|
groups.get(key).add(node);
|
|
1128
820
|
}
|
|
1129
821
|
for (const [key, group] of groups) {
|
|
1130
822
|
if (group.size == 1) continue;
|
|
1131
823
|
const edgeIds = [...group].map((node) => node.edgeId);
|
|
1132
|
-
const dummy =
|
|
824
|
+
const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
|
|
1133
825
|
let seg;
|
|
1134
826
|
for (const old of group) {
|
|
1135
|
-
|
|
827
|
+
let edge = g.getEdge(old.edgeIds[0]);
|
|
828
|
+
for (const segId of old.relIds("segs", dir)) {
|
|
1136
829
|
if (!seg) {
|
|
1137
|
-
const example =
|
|
1138
|
-
seg =
|
|
830
|
+
const example = g.getSeg(segId);
|
|
831
|
+
seg = Seg.add(g, {
|
|
1139
832
|
...example,
|
|
1140
|
-
|
|
833
|
+
edgeIds: ISet4([old.edgeId]),
|
|
1141
834
|
[altSide]: { ...example[altSide], id: dummy.id }
|
|
1142
835
|
});
|
|
1143
836
|
}
|
|
1144
|
-
|
|
837
|
+
edge = edge.replaceSegId(g, segId, seg.id);
|
|
1145
838
|
}
|
|
1146
839
|
}
|
|
1147
840
|
for (const old of group) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const
|
|
841
|
+
let edge = g.getEdge(old.edgeIds[0]);
|
|
842
|
+
for (const segId of old.relIds("segs", altDir)) {
|
|
843
|
+
const example = g.getSeg(segId);
|
|
844
|
+
const seg2 = Seg.add(g, {
|
|
1151
845
|
...example,
|
|
1152
|
-
|
|
846
|
+
edgeIds: ISet4([old.edgeId]),
|
|
1153
847
|
[side]: { ...example[side], id: dummy.id }
|
|
1154
848
|
});
|
|
1155
|
-
|
|
849
|
+
edge = edge.replaceSegId(g, segId, seg2.id);
|
|
1156
850
|
}
|
|
1157
851
|
}
|
|
1158
852
|
}
|
|
@@ -1160,209 +854,193 @@ var GraphDummy = {
|
|
|
1160
854
|
}
|
|
1161
855
|
};
|
|
1162
856
|
|
|
1163
|
-
// src/graph
|
|
857
|
+
// src/graph/services/layers.ts
|
|
858
|
+
import { Seq } from "immutable";
|
|
859
|
+
var log4 = logger("layers");
|
|
860
|
+
var Layers = class {
|
|
861
|
+
static updateLayers(g) {
|
|
862
|
+
const stack = [...g.dirtyNodes].map((id) => g.getNode(id)).filter((node) => !node.isDummy).sort((a, b) => b.layerIndex(g) - a.layerIndex(g)).map((node) => node.id);
|
|
863
|
+
const phase2 = new Set(stack);
|
|
864
|
+
const moved = /* @__PURE__ */ new Set();
|
|
865
|
+
while (stack.length > 0) {
|
|
866
|
+
let node = g.getNode(stack.pop());
|
|
867
|
+
const curLayer = node.layerIndex(g);
|
|
868
|
+
let correctLayer = 0;
|
|
869
|
+
const parents = node.inNodes(g);
|
|
870
|
+
for (const parent of parents) {
|
|
871
|
+
const pidx = parent.layerIndex(g);
|
|
872
|
+
if (pidx >= correctLayer) correctLayer = pidx + 1;
|
|
873
|
+
}
|
|
874
|
+
if (curLayer != correctLayer) {
|
|
875
|
+
node = node.moveToLayerIndex(g, correctLayer);
|
|
876
|
+
stack.push(...node.outNodeIds(g));
|
|
877
|
+
moved.add(node.id);
|
|
878
|
+
for (const parent of parents)
|
|
879
|
+
phase2.add(parent.id);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
883
|
+
const addParent = (nodeId) => {
|
|
884
|
+
let set;
|
|
885
|
+
const layerId = g.getNode(nodeId).layerId;
|
|
886
|
+
if (!byLayer.has(layerId)) {
|
|
887
|
+
set = /* @__PURE__ */ new Set();
|
|
888
|
+
byLayer.set(layerId, set);
|
|
889
|
+
} else {
|
|
890
|
+
set = byLayer.get(layerId);
|
|
891
|
+
}
|
|
892
|
+
set.add(nodeId);
|
|
893
|
+
};
|
|
894
|
+
for (const id of phase2) addParent(id);
|
|
895
|
+
const layerIds = [...byLayer.keys()].sort(
|
|
896
|
+
(a, b) => g.getLayer(b).index - g.getLayer(a).index
|
|
897
|
+
);
|
|
898
|
+
for (const layerId of layerIds) {
|
|
899
|
+
const curLayer = g.getLayer(layerId).index;
|
|
900
|
+
for (const parentId of byLayer.get(layerId)) {
|
|
901
|
+
let parent = g.getNode(parentId);
|
|
902
|
+
const children = [...parent.outNodes(g)];
|
|
903
|
+
if (children.length == 0) continue;
|
|
904
|
+
const minChild = Seq(children).map((node) => node.layerIndex(g)).min();
|
|
905
|
+
const correctLayer = minChild - 1;
|
|
906
|
+
if (curLayer != correctLayer) {
|
|
907
|
+
moved.add(parentId);
|
|
908
|
+
parent = parent.moveToLayerIndex(g, correctLayer);
|
|
909
|
+
for (const gpId of parent.inNodeIds(g))
|
|
910
|
+
addParent(gpId);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
for (const id of moved)
|
|
915
|
+
for (const edgeId of g.getNode(id).relIds("edges"))
|
|
916
|
+
g.dirtyEdges.add(edgeId);
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// src/graph/services/layout.ts
|
|
1164
921
|
import { Seq as Seq2 } from "immutable";
|
|
1165
|
-
var
|
|
1166
|
-
var
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
* @param {Object} node - Node
|
|
1171
|
-
* @returns {number} The minimum index of incoming edges
|
|
1172
|
-
*/
|
|
1173
|
-
_parentIndex(node) {
|
|
1174
|
-
const parents = Seq2(this._adjs(node.id, "segs", "in"));
|
|
922
|
+
var log5 = logger("layout");
|
|
923
|
+
var Layout = class _Layout {
|
|
924
|
+
static parentIndex(g, node) {
|
|
925
|
+
const parents = Seq2([...node.adjs(g, "segs", "in")]);
|
|
926
|
+
console.log(`parents of ${node.id}:`, [...parents], [...parents.map((p) => p.index)]);
|
|
1175
927
|
const pidx = parents.map((p) => p.index).min();
|
|
1176
|
-
|
|
928
|
+
log5.debug(`node ${node.id}: parent index ${pidx}`);
|
|
1177
929
|
if (pidx !== void 0) return pidx;
|
|
1178
930
|
return node.isDummy ? -Infinity : Infinity;
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
* Compare two nodes based on their parent index and natural ordering
|
|
1182
|
-
*
|
|
1183
|
-
* @param {Object} aId - First node ID
|
|
1184
|
-
* @param {Object} bId - Second node ID
|
|
1185
|
-
* @returns {number} -1, 0, or 1
|
|
1186
|
-
*/
|
|
1187
|
-
_compareNodes(aId, bId, pidxs) {
|
|
931
|
+
}
|
|
932
|
+
static compareNodes(g, aId, bId, pidxs) {
|
|
1188
933
|
const ai = pidxs.get(aId);
|
|
1189
934
|
const bi = pidxs.get(bId);
|
|
1190
935
|
if (ai !== bi) return ai - bi;
|
|
1191
|
-
const a =
|
|
1192
|
-
const b =
|
|
936
|
+
const a = g.getNode(aId);
|
|
937
|
+
const b = g.getNode(bId);
|
|
1193
938
|
if (a.isDummy && !b.isDummy) return -1;
|
|
1194
939
|
if (!a.isDummy && b.isDummy) return 1;
|
|
1195
940
|
if (!a.isDummy) return a.id.localeCompare(b.id);
|
|
1196
941
|
const minA = a.edgeId ?? Seq2(a.edgeIds).min();
|
|
1197
942
|
const minB = b.edgeId ?? Seq2(b.edgeIds).min();
|
|
1198
943
|
return minA.localeCompare(minB);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
* on the layer index, and the X position is assigned based on the node index
|
|
1205
|
-
* within the layer.
|
|
1206
|
-
*/
|
|
1207
|
-
_positionNodes() {
|
|
1208
|
-
var _a;
|
|
1209
|
-
for (const nodeId of this._dirtyNodes) {
|
|
1210
|
-
const node = this.nodes.get(nodeId);
|
|
1211
|
-
if (!node) continue;
|
|
1212
|
-
const layerId = node.layerId;
|
|
1213
|
-
this.dirtyLayers.add(layerId);
|
|
1214
|
-
}
|
|
944
|
+
}
|
|
945
|
+
static positionNodes(g) {
|
|
946
|
+
console.log("positionNodes", g.dirtyNodes);
|
|
947
|
+
for (const nodeId of g.dirtyNodes)
|
|
948
|
+
g.dirtyLayers.add(g.getNode(nodeId).layerId);
|
|
1215
949
|
let adjustNext = false;
|
|
1216
|
-
for (const layerId of
|
|
1217
|
-
if (!adjustNext && !
|
|
950
|
+
for (const layerId of g.layerList) {
|
|
951
|
+
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
1218
952
|
adjustNext = false;
|
|
1219
|
-
|
|
953
|
+
let layer = g.getLayer(layerId);
|
|
954
|
+
console.log(`positioning layer ${layerId} at ${layer.index}`);
|
|
1220
955
|
const pidxs = /* @__PURE__ */ new Map();
|
|
1221
|
-
for (const nodeId of layer.
|
|
1222
|
-
pidxs.set(nodeId,
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
956
|
+
for (const nodeId of layer.nodeIds)
|
|
957
|
+
pidxs.set(nodeId, _Layout.parentIndex(g, g.getNode(nodeId)));
|
|
958
|
+
console.log("pidxs", pidxs);
|
|
959
|
+
const sorted = [...layer.nodeIds].sort(
|
|
960
|
+
(aId, bId) => _Layout.compareNodes(g, aId, bId, pidxs)
|
|
961
|
+
);
|
|
962
|
+
console.log(`sorted:`, sorted);
|
|
963
|
+
if (layer.hasSortOrder(sorted)) continue;
|
|
964
|
+
g.dirtyLayers.add(layerId);
|
|
965
|
+
layer = layer.setSorted(g, sorted);
|
|
1227
966
|
adjustNext = true;
|
|
1228
967
|
let lpos = 0;
|
|
1229
968
|
for (let i = 0; i < sorted.length; i++) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
const size =
|
|
1234
|
-
lpos += size +
|
|
969
|
+
let node = g.getNode(sorted[i]);
|
|
970
|
+
log5.debug(`node ${node.id}: final index ${i}`);
|
|
971
|
+
node = node.setIndex(g, i).setLayerPos(g, lpos);
|
|
972
|
+
const size = node.dims?.[g.w] ?? 0;
|
|
973
|
+
lpos += size + g.options.nodeMargin;
|
|
1235
974
|
}
|
|
1236
975
|
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
* - Align children to parents
|
|
1243
|
-
* - Align parents to children
|
|
1244
|
-
* - Compact layout
|
|
1245
|
-
*/
|
|
1246
|
-
_alignAll() {
|
|
1247
|
-
if (this.options.layoutSteps !== void 0) {
|
|
1248
|
-
for (const step of this.options.layoutSteps)
|
|
1249
|
-
this[`_${step}`]();
|
|
976
|
+
}
|
|
977
|
+
static alignAll(g) {
|
|
978
|
+
if (g.options.layoutSteps) {
|
|
979
|
+
for (const step of g.options.layoutSteps)
|
|
980
|
+
_Layout[step](g);
|
|
1250
981
|
} else {
|
|
1251
|
-
for (let i = 0; i <
|
|
1252
|
-
let anyChanged =
|
|
982
|
+
for (let i = 0; i < g.options.alignIterations; i++) {
|
|
983
|
+
let anyChanged = _Layout.alignChildren(g) || _Layout.alignParents(g) || _Layout.compact(g);
|
|
1253
984
|
if (!anyChanged) break;
|
|
1254
985
|
}
|
|
1255
986
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
return this._alignNodes(false, false, false, "in", false);
|
|
1266
|
-
},
|
|
1267
|
-
// Align parents to their children.
|
|
1268
|
-
//
|
|
1269
|
-
// - Sweep layers last to first
|
|
1270
|
-
// - Sweep nodes right to left
|
|
1271
|
-
// - Move nodes only to the left
|
|
1272
|
-
// - On overlap, abort the shift
|
|
1273
|
-
_alignParents() {
|
|
1274
|
-
return this._alignNodes(true, true, false, "out", true);
|
|
1275
|
-
},
|
|
1276
|
-
/**
|
|
1277
|
-
* Aligns nodes in each layer, attempting to align child nodes
|
|
1278
|
-
* with their parents (or vice versa). If this causes nodes to overlap as
|
|
1279
|
-
* a result, they are pushed to the right (or left, depending on reverseMove).
|
|
1280
|
-
* However, if conservative is true, nodes will only be moved if they would
|
|
1281
|
-
* not cause a collision with another node.
|
|
1282
|
-
*
|
|
1283
|
-
* "Aligned" means that the edge between the nodes is straight. This could mean
|
|
1284
|
-
* the nodes themselves are not aligned, if they have different anchor positions.
|
|
1285
|
-
*
|
|
1286
|
-
* @param {boolean} reverseLayers - Whether to reverse the order of layers
|
|
1287
|
-
* @param {boolean} reverseNodes - Whether to reverse the order of nodes within each layer
|
|
1288
|
-
* @param {boolean} reverseMove - Whether to move nodes to the left or right
|
|
1289
|
-
* @param {'in' | 'out'} dir - Whether to align nodes based on incoming or outgoing edges
|
|
1290
|
-
* @param {boolean} conservative - Whether to move nodes only if they would not cause a collision
|
|
1291
|
-
*/
|
|
1292
|
-
_alignNodes(reverseLayers, reverseNodes, reverseMove, dir, conservative) {
|
|
1293
|
-
let layerIds = [...this.layerList];
|
|
987
|
+
}
|
|
988
|
+
static alignChildren(g) {
|
|
989
|
+
return _Layout.alignNodes(g, false, false, false, "in", false);
|
|
990
|
+
}
|
|
991
|
+
static alignParents(g) {
|
|
992
|
+
return _Layout.alignNodes(g, true, true, false, "out", true);
|
|
993
|
+
}
|
|
994
|
+
static alignNodes(g, reverseLayers, reverseNodes, reverseMove, dir, conservative) {
|
|
995
|
+
let layerIds = [...g.layerList];
|
|
1294
996
|
let anyChanged = false;
|
|
1295
997
|
if (reverseLayers) layerIds.reverse();
|
|
1296
998
|
let adjustNext = false;
|
|
1297
999
|
for (const layerId of layerIds) {
|
|
1298
|
-
if (!adjustNext && !
|
|
1000
|
+
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
1299
1001
|
adjustNext = false;
|
|
1300
1002
|
while (true) {
|
|
1301
1003
|
let changed = false;
|
|
1302
|
-
const nodeIds =
|
|
1004
|
+
const nodeIds = _Layout.sortLayer(g, layerId, reverseNodes);
|
|
1303
1005
|
for (const nodeId of nodeIds) {
|
|
1304
|
-
const {
|
|
1006
|
+
const {
|
|
1007
|
+
isAligned,
|
|
1008
|
+
pos: newPos,
|
|
1009
|
+
nodeId: otherId
|
|
1010
|
+
} = _Layout.nearestNode(g, nodeId, dir, reverseMove, !reverseMove);
|
|
1305
1011
|
if (isAligned || newPos === void 0) continue;
|
|
1306
|
-
if (
|
|
1012
|
+
if (_Layout.shiftNode(g, nodeId, otherId, dir, newPos, reverseMove, conservative)) {
|
|
1307
1013
|
changed = true;
|
|
1308
1014
|
anyChanged = true;
|
|
1309
1015
|
break;
|
|
1310
1016
|
}
|
|
1311
1017
|
}
|
|
1312
1018
|
if (!changed) break;
|
|
1313
|
-
|
|
1019
|
+
g.dirtyLayers.add(layerId);
|
|
1314
1020
|
adjustNext = true;
|
|
1315
1021
|
}
|
|
1316
1022
|
}
|
|
1317
1023
|
return anyChanged;
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
* @param {boolean} reverseNodes - Whether to reverse the order of nodes within the layer
|
|
1325
|
-
* @returns {string[]} The sorted array of node IDs
|
|
1326
|
-
*/
|
|
1327
|
-
_sortLayer(layerId, reverseNodes) {
|
|
1328
|
-
const layer = this.getLayer(layerId);
|
|
1329
|
-
const sorted = [...layer.nodes];
|
|
1330
|
-
sorted.sort((a, b) => this.getNode(a).lpos - this.getNode(b).lpos);
|
|
1331
|
-
if (!sorted.every((nodeId, i) => layer.sorted[i] == nodeId)) {
|
|
1332
|
-
this.dirtyLayers.add(layerId);
|
|
1333
|
-
this.layers.set(layerId, { ...layer, sorted });
|
|
1334
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
1335
|
-
const node = this.getNode(sorted[i]);
|
|
1336
|
-
if (node.index !== i)
|
|
1337
|
-
this.nodes.set(sorted[i], { ...node, index: i });
|
|
1338
|
-
}
|
|
1339
|
-
}
|
|
1024
|
+
}
|
|
1025
|
+
static sortLayer(g, layerId, reverseNodes) {
|
|
1026
|
+
const layer = g.getLayer(layerId);
|
|
1027
|
+
const sorted = [...layer.nodeIds];
|
|
1028
|
+
sorted.sort((a, b) => g.getNode(a).lpos - g.getNode(b).lpos);
|
|
1029
|
+
layer.setSorted(g, sorted);
|
|
1340
1030
|
if (reverseNodes)
|
|
1341
1031
|
return sorted.toReversed();
|
|
1342
1032
|
return sorted;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
* If the nearest is already aligned, return isAligned: true. Nearest means that the anchor
|
|
1347
|
-
* positions are close and in the right direction. Returns both the near node
|
|
1348
|
-
* and the position to which the given node should move in order to be aligned.
|
|
1349
|
-
*
|
|
1350
|
-
* @param {string} nodeId - The ID of the node to find the nearest node for
|
|
1351
|
-
* @param {'in' | 'out'} dir - The direction to find the nearest node in
|
|
1352
|
-
* @param {boolean} allowLeft - Whether to allow the nearest node to be to the left of the given node
|
|
1353
|
-
* @param {boolean} allowRight - Whether to allow the nearest node to be to the right of the given node
|
|
1354
|
-
* @returns {{ nodeId: string, pos: number, isAligned: boolean }} The nearest node and the position to which the given node should move
|
|
1355
|
-
*/
|
|
1356
|
-
_nearestNode(nodeId, dir, allowLeft, allowRight) {
|
|
1357
|
-
const node = this.getNode(nodeId);
|
|
1033
|
+
}
|
|
1034
|
+
static nearestNode(g, nodeId, dir, allowLeft, allowRight) {
|
|
1035
|
+
const node = g.getNode(nodeId);
|
|
1358
1036
|
let minDist = Infinity;
|
|
1359
1037
|
let bestPos, bestNodeId;
|
|
1360
1038
|
const mySide = dir == "in" ? "target" : "source";
|
|
1361
1039
|
const altSide = dir == "in" ? "source" : "target";
|
|
1362
|
-
for (const seg of
|
|
1040
|
+
for (const seg of node.rels(g, "segs", dir)) {
|
|
1363
1041
|
const altId = seg[altSide].id;
|
|
1364
|
-
const myPos =
|
|
1365
|
-
const altPos =
|
|
1042
|
+
const myPos = _Layout.anchorPos(g, seg, mySide)[g.x];
|
|
1043
|
+
const altPos = _Layout.anchorPos(g, seg, altSide)[g.x];
|
|
1366
1044
|
const diff = altPos - myPos;
|
|
1367
1045
|
if (diff == 0) return { nodeId: altId, isAligned: true };
|
|
1368
1046
|
if (diff < 0 && !allowLeft) continue;
|
|
@@ -1375,117 +1053,70 @@ var GraphPos = {
|
|
|
1375
1053
|
}
|
|
1376
1054
|
}
|
|
1377
1055
|
return { nodeId: bestNodeId, pos: bestPos, isAligned: false };
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
* Get the anchor point for an edge connection on a node
|
|
1381
|
-
*
|
|
1382
|
-
* @param {Object} seg - The segment to get the anchor point for
|
|
1383
|
-
* @param {'source' | 'target'} side - The side of the segment to get the anchor point for
|
|
1384
|
-
* @returns {{ x: number, y: number }} The anchor point
|
|
1385
|
-
*/
|
|
1386
|
-
_anchorPos(seg, side) {
|
|
1387
|
-
var _a, _b;
|
|
1388
|
-
const { _x, _y } = this;
|
|
1056
|
+
}
|
|
1057
|
+
static anchorPos(g, seg, side) {
|
|
1389
1058
|
const nodeId = seg[side].id;
|
|
1390
|
-
const node =
|
|
1391
|
-
let p = { [
|
|
1392
|
-
let w =
|
|
1393
|
-
let h =
|
|
1059
|
+
const node = g.getNode(nodeId);
|
|
1060
|
+
let p = { x: 0, y: 0, [g.x]: node.lpos, ...node.pos || {} };
|
|
1061
|
+
let w = node.dims?.[g.w] ?? 0;
|
|
1062
|
+
let h = node.dims?.[g.h] ?? 0;
|
|
1394
1063
|
if (node.isDummy)
|
|
1395
|
-
return {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1064
|
+
return {
|
|
1065
|
+
[g.x]: p[g.x] + w / 2,
|
|
1066
|
+
[g.y]: p[g.y] + h / 2
|
|
1067
|
+
};
|
|
1068
|
+
p[g.x] += _Layout.nodePortOffset(g, nodeId, seg[side].port);
|
|
1069
|
+
if (side == "source" == g.r)
|
|
1070
|
+
p[g.y] += h;
|
|
1399
1071
|
return p;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
_nodePortOffset(nodeId, port) {
|
|
1408
|
-
if (!port) return this.options.defaultPortOffset;
|
|
1409
|
-
return this.options.defaultPortOffset;
|
|
1410
|
-
},
|
|
1411
|
-
/**
|
|
1412
|
-
* Shift the node to the given x position, pushing it to the right (or left, depending on reverseMove).
|
|
1413
|
-
* If conservative is true, nodes will only be moved if they would not cause a collision with another node.
|
|
1414
|
-
* If a collision does occur, recursively move collided nodes to find a valid position.
|
|
1415
|
-
* If the shift is successful, this node and the aligned node are linked explicitly; if the aligned
|
|
1416
|
-
* node was already set, it is unlinked first.
|
|
1417
|
-
*
|
|
1418
|
-
* @param {string} nodeId - ID of node to shift
|
|
1419
|
-
* @param {string} alignId - ID of node we're aligning to
|
|
1420
|
-
* @param {'in' | 'out'} dir - Direction of aligned node from this node
|
|
1421
|
-
* @param {number} lpos - Position within layer to shift node to
|
|
1422
|
-
* @param {boolean} reverseMove - Whether to move nodes to the left or right
|
|
1423
|
-
* @param {boolean} conservative - Whether to move nodes only if they would not cause a collision
|
|
1424
|
-
*/
|
|
1425
|
-
_shiftNode(nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1426
|
-
var _a, _b;
|
|
1427
|
-
const node = this.getNode(nodeId);
|
|
1072
|
+
}
|
|
1073
|
+
static nodePortOffset(g, nodeId, port) {
|
|
1074
|
+
if (!port) return g.options.defaultPortOffset;
|
|
1075
|
+
return g.options.defaultPortOffset;
|
|
1076
|
+
}
|
|
1077
|
+
static shiftNode(g, nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1078
|
+
const node = g.getNode(nodeId);
|
|
1428
1079
|
if (!conservative)
|
|
1429
|
-
|
|
1430
|
-
const space =
|
|
1431
|
-
const nodeWidth =
|
|
1080
|
+
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1081
|
+
const space = g.options.nodeMargin;
|
|
1082
|
+
const nodeWidth = node.dims?.[g.w] ?? 0;
|
|
1432
1083
|
const aMin = lpos - space, aMax = lpos + nodeWidth + space;
|
|
1433
1084
|
repeat:
|
|
1434
|
-
for (const otherId of
|
|
1085
|
+
for (const otherId of node.getLayer(g).nodeIds) {
|
|
1435
1086
|
if (otherId == nodeId) continue;
|
|
1436
|
-
const other =
|
|
1087
|
+
const other = g.getNode(otherId);
|
|
1437
1088
|
const opos = other.lpos;
|
|
1438
|
-
const otherWidth =
|
|
1089
|
+
const otherWidth = other.dims?.[g.w] ?? 0;
|
|
1439
1090
|
const bMin = opos, bMax = opos + otherWidth;
|
|
1440
1091
|
if (aMin < bMax && bMin < aMax) {
|
|
1441
1092
|
if (conservative) return false;
|
|
1442
1093
|
const safePos = reverseMove ? aMin - otherWidth : aMax;
|
|
1443
|
-
|
|
1094
|
+
_Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
|
|
1444
1095
|
continue repeat;
|
|
1445
1096
|
}
|
|
1446
1097
|
}
|
|
1447
1098
|
if (conservative)
|
|
1448
|
-
|
|
1099
|
+
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1449
1100
|
return true;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
*
|
|
1454
|
-
* @param {string} nodeId - Node being aligned
|
|
1455
|
-
* @param {string} otherId - Node we're aligning to
|
|
1456
|
-
* @param {'in' | 'out'} dir - direction of other from node
|
|
1457
|
-
* @param {number} lpos - new layer position
|
|
1458
|
-
*/
|
|
1459
|
-
_markAligned(nodeId, otherId, dir, lpos) {
|
|
1460
|
-
const node = this.getNode(nodeId);
|
|
1101
|
+
}
|
|
1102
|
+
static markAligned(g, nodeId, otherId, dir, lpos) {
|
|
1103
|
+
const node = g.getNode(nodeId);
|
|
1461
1104
|
const alt = dir == "in" ? "out" : "in";
|
|
1462
|
-
if (node.aligned[dir])
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1470
|
-
this.nodes.set(nodeId, { ...node, lpos, aligned: { [dir]: otherId, [alt]: void 0 } });
|
|
1471
|
-
},
|
|
1472
|
-
/**
|
|
1473
|
-
* Iterate over all nodes aligned with the given node, including itself,
|
|
1474
|
-
* exactly once.
|
|
1475
|
-
*
|
|
1476
|
-
* @param {string} nodeId - Node ID
|
|
1477
|
-
* @param {'in' | 'out' | 'both'} dir - direction of alignment
|
|
1478
|
-
* @returns {Iterator<Object>} Iterator over aligned nodes
|
|
1479
|
-
*/
|
|
1480
|
-
*_aligned(nodeId, dir) {
|
|
1105
|
+
if (node.aligned[dir])
|
|
1106
|
+
g.getNode(node.aligned[dir]).setAligned(g, alt, void 0);
|
|
1107
|
+
if (otherId)
|
|
1108
|
+
g.getNode(otherId).setAligned(g, alt, nodeId);
|
|
1109
|
+
node.setAligned(g, dir, otherId);
|
|
1110
|
+
}
|
|
1111
|
+
static *aligned(g, nodeId, dir) {
|
|
1481
1112
|
const visit = function* (node2, dir2) {
|
|
1482
1113
|
const otherId = node2.aligned[dir2];
|
|
1483
1114
|
if (!otherId) return;
|
|
1484
|
-
const other =
|
|
1115
|
+
const other = g.getNode(otherId);
|
|
1485
1116
|
yield other;
|
|
1486
|
-
yield* visit
|
|
1487
|
-
}
|
|
1488
|
-
const node =
|
|
1117
|
+
yield* visit(other, dir2);
|
|
1118
|
+
};
|
|
1119
|
+
const node = g.getNode(nodeId);
|
|
1489
1120
|
yield node;
|
|
1490
1121
|
if (dir == "both") {
|
|
1491
1122
|
yield* visit(node, "in");
|
|
@@ -1493,120 +1124,1374 @@ var GraphPos = {
|
|
|
1493
1124
|
} else {
|
|
1494
1125
|
yield* visit(node, dir);
|
|
1495
1126
|
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
* Get the node ID immediately to the left of the given node in the same layer
|
|
1499
|
-
*
|
|
1500
|
-
* @param {Object} node - Node to get left of
|
|
1501
|
-
* @returns {string | null} Node ID to the left of the given node, or null if there is none
|
|
1502
|
-
*/
|
|
1503
|
-
_leftOf(node) {
|
|
1127
|
+
}
|
|
1128
|
+
static leftOf(g, node) {
|
|
1504
1129
|
if (node.index == 0) return null;
|
|
1505
|
-
return
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
*
|
|
1510
|
-
* @param {Object} node - Node to get right of
|
|
1511
|
-
* @returns {string | null} Node ID to the right of the given node, or null if there is none
|
|
1512
|
-
*/
|
|
1513
|
-
_rightOf(node) {
|
|
1514
|
-
const layer = this.getLayer(node.layerId);
|
|
1130
|
+
return node.getLayer(g).sorted[node.index - 1];
|
|
1131
|
+
}
|
|
1132
|
+
static rightOf(g, node) {
|
|
1133
|
+
const layer = node.getLayer(g);
|
|
1515
1134
|
if (node.index == layer.sorted.length - 1) return null;
|
|
1516
1135
|
return layer.sorted[node.index + 1];
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
* Compact tries to eliminate empty space between nodes
|
|
1520
|
-
*/
|
|
1521
|
-
_compact() {
|
|
1522
|
-
var _a;
|
|
1136
|
+
}
|
|
1137
|
+
static compact(g) {
|
|
1523
1138
|
let anyChanged = false;
|
|
1524
|
-
for (const layerId of
|
|
1525
|
-
const layer =
|
|
1139
|
+
for (const layerId of g.layerList) {
|
|
1140
|
+
const layer = g.getLayer(layerId);
|
|
1526
1141
|
if (layer.sorted.length < 2) continue;
|
|
1527
1142
|
for (const nodeId of layer.sorted) {
|
|
1528
|
-
const node =
|
|
1143
|
+
const node = g.getNode(nodeId);
|
|
1529
1144
|
if (node.index == 0) continue;
|
|
1530
1145
|
let minGap = Infinity;
|
|
1531
1146
|
const stack = [];
|
|
1532
|
-
for (const right of
|
|
1147
|
+
for (const right of _Layout.aligned(g, nodeId, "both")) {
|
|
1533
1148
|
stack.push(right);
|
|
1534
|
-
const leftId =
|
|
1149
|
+
const leftId = _Layout.leftOf(g, right);
|
|
1535
1150
|
if (!leftId) return;
|
|
1536
|
-
const left =
|
|
1537
|
-
const leftWidth =
|
|
1151
|
+
const left = g.getNode(leftId);
|
|
1152
|
+
const leftWidth = left.dims?.[g.w] ?? 0;
|
|
1538
1153
|
const gap = right.lpos - left.lpos - leftWidth;
|
|
1539
1154
|
if (gap < minGap) minGap = gap;
|
|
1540
1155
|
}
|
|
1541
|
-
const delta = minGap -
|
|
1156
|
+
const delta = minGap - g.options.nodeMargin;
|
|
1542
1157
|
if (delta <= 0) continue;
|
|
1543
1158
|
anyChanged = true;
|
|
1544
1159
|
for (const right of stack)
|
|
1545
|
-
|
|
1160
|
+
right.setLayerPos(g, right.lpos - delta);
|
|
1546
1161
|
}
|
|
1547
1162
|
}
|
|
1548
1163
|
return anyChanged;
|
|
1549
1164
|
}
|
|
1165
|
+
static getCoords(g) {
|
|
1166
|
+
let pos = 0;
|
|
1167
|
+
const dir = g.r ? -1 : 1;
|
|
1168
|
+
const trackSep = Math.max(
|
|
1169
|
+
g.options.layerMargin,
|
|
1170
|
+
g.options.edgeSpacing,
|
|
1171
|
+
g.options.turnRadius
|
|
1172
|
+
);
|
|
1173
|
+
for (const layerId of g.layerList) {
|
|
1174
|
+
let layer = g.getLayer(layerId);
|
|
1175
|
+
let height;
|
|
1176
|
+
console.log(`getCoords: layer = ${layerId} at ${layer.index}`);
|
|
1177
|
+
if (g.dirtyLayers.has(layerId)) {
|
|
1178
|
+
height = Seq2(layer.nodes(g)).map((node) => node.dims?.[g.h] ?? 0).max() ?? 0;
|
|
1179
|
+
layer = layer.setSize(g, height);
|
|
1180
|
+
} else height = layer.size;
|
|
1181
|
+
console.log(`getCoords: layer = ${layerId}: pos = ${pos}, height = ${height}`);
|
|
1182
|
+
for (const node of layer.nodes(g)) {
|
|
1183
|
+
if (!g.dirtyNodes.has(node.id) && pos == layer.pos) continue;
|
|
1184
|
+
const npos = { [g.x]: node.lpos, [g.y]: pos };
|
|
1185
|
+
if (!g.n) npos[g.y] += dir * height;
|
|
1186
|
+
if (g.r == g.n) npos[g.y] -= node.dims?.[g.h] ?? 0;
|
|
1187
|
+
console.log(`getCoords: node = ${node.id}: pos:`, npos);
|
|
1188
|
+
node.setPos(g, npos);
|
|
1189
|
+
}
|
|
1190
|
+
layer = layer.setPos(g, pos);
|
|
1191
|
+
pos += dir * (height + trackSep);
|
|
1192
|
+
for (const track of layer.tracks) {
|
|
1193
|
+
for (const segId of track)
|
|
1194
|
+
g.getSeg(segId).setTrackPos(g, pos);
|
|
1195
|
+
pos += dir * g.options.edgeSpacing;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1550
1199
|
};
|
|
1551
1200
|
|
|
1552
|
-
// src/graph.
|
|
1553
|
-
var
|
|
1201
|
+
// src/graph/services/lines.ts
|
|
1202
|
+
var Lines = class _Lines {
|
|
1203
|
+
static layoutSeg(g, seg) {
|
|
1204
|
+
const sourcePos = Layout.anchorPos(g, seg, "source");
|
|
1205
|
+
const targetPos = Layout.anchorPos(g, seg, "target");
|
|
1206
|
+
return seg.setPos(g, sourcePos[g.x], targetPos[g.x]);
|
|
1207
|
+
}
|
|
1208
|
+
static layerSegs(g, layer) {
|
|
1209
|
+
const segs = [];
|
|
1210
|
+
for (const node of layer.nodes(g))
|
|
1211
|
+
for (const seg of node.outSegs(g))
|
|
1212
|
+
segs.push(_Lines.layoutSeg(g, seg));
|
|
1213
|
+
return segs;
|
|
1214
|
+
}
|
|
1215
|
+
static trackEdges(g) {
|
|
1216
|
+
for (const segId of g.dirtySegs.values())
|
|
1217
|
+
g.dirtyLayers.add(g.getSeg(segId).sourceNode(g).layerId);
|
|
1218
|
+
const minLength = g.options.turnRadius * 2;
|
|
1219
|
+
for (const layerId of g.dirtyLayers.values()) {
|
|
1220
|
+
const layer = g.getLayer(layerId);
|
|
1221
|
+
const leftTracks = [];
|
|
1222
|
+
const rightTracks = [];
|
|
1223
|
+
const allTracks = [];
|
|
1224
|
+
const segs = _Lines.layerSegs(g, layer).sort((a, b) => a.p1 - b.p1);
|
|
1225
|
+
for (const seg of segs) {
|
|
1226
|
+
if (Math.abs(seg.p1 - seg.p2) < minLength) {
|
|
1227
|
+
seg.setTrackPos(g, void 0);
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
let trackSet;
|
|
1231
|
+
if (!g.options.separateTrackSets)
|
|
1232
|
+
trackSet = allTracks;
|
|
1233
|
+
else if (seg.p1 < seg.p2)
|
|
1234
|
+
trackSet = rightTracks;
|
|
1235
|
+
else
|
|
1236
|
+
trackSet = leftTracks;
|
|
1237
|
+
let validTrack;
|
|
1238
|
+
for (let i = trackSet.length - 1; i >= 0; i--) {
|
|
1239
|
+
const track = trackSet[i];
|
|
1240
|
+
let overlap = false;
|
|
1241
|
+
for (const other of track) {
|
|
1242
|
+
if (seg.anySameEnd(other)) {
|
|
1243
|
+
track.push(seg);
|
|
1244
|
+
validTrack = track;
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
if (other.p1 < seg.p2 && seg.p1 < other.p2) {
|
|
1248
|
+
overlap = true;
|
|
1249
|
+
break;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (!overlap) {
|
|
1253
|
+
validTrack = track;
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
if (validTrack)
|
|
1258
|
+
validTrack.push(seg);
|
|
1259
|
+
else
|
|
1260
|
+
trackSet.push([seg]);
|
|
1261
|
+
}
|
|
1262
|
+
const tracks = [];
|
|
1263
|
+
const all = leftTracks.concat(rightTracks).concat(allTracks);
|
|
1264
|
+
for (const track of all)
|
|
1265
|
+
tracks.push(track.map((seg) => seg.id));
|
|
1266
|
+
layer.setTracks(g, tracks);
|
|
1267
|
+
}
|
|
1268
|
+
return this;
|
|
1269
|
+
}
|
|
1270
|
+
static pathEdges(g) {
|
|
1271
|
+
for (const seg of g.segs.values()) {
|
|
1272
|
+
if (!g.dirtySegs.has(seg.id)) continue;
|
|
1273
|
+
const radius = g.options.turnRadius;
|
|
1274
|
+
const p1 = Layout.anchorPos(g, seg, "source");
|
|
1275
|
+
const p2 = Layout.anchorPos(g, seg, "target");
|
|
1276
|
+
const path = seg.trackPos !== void 0 ? _Lines.createRailroadPath(g, p1, p2, seg.trackPos, radius) : _Lines.createDirectPath(g, p1, p2, radius);
|
|
1277
|
+
const svg = _Lines.pathToSVG(path);
|
|
1278
|
+
seg.setSVG(g, svg);
|
|
1279
|
+
}
|
|
1280
|
+
return this;
|
|
1281
|
+
}
|
|
1282
|
+
static pathLine(p1, p2, type, radius) {
|
|
1283
|
+
if (p2.x === void 0) p2.x = p1.x;
|
|
1284
|
+
if (p2.y === void 0) p2.y = p1.y;
|
|
1285
|
+
if (p2.s === void 0) p2.s = p1.s;
|
|
1286
|
+
const line = {
|
|
1287
|
+
type,
|
|
1288
|
+
x1: p1.x,
|
|
1289
|
+
y1: p1.y,
|
|
1290
|
+
x2: p2.x,
|
|
1291
|
+
y2: p2.y
|
|
1292
|
+
};
|
|
1293
|
+
p1.x = p2.x;
|
|
1294
|
+
p1.y = p2.y;
|
|
1295
|
+
p1.s = p2.s;
|
|
1296
|
+
if (type == "arc") {
|
|
1297
|
+
line.radius = radius;
|
|
1298
|
+
line.sweep = p1.s;
|
|
1299
|
+
}
|
|
1300
|
+
return line;
|
|
1301
|
+
}
|
|
1302
|
+
static pathBuilder(g, start, end, trackPos, radius) {
|
|
1303
|
+
const { x, y } = g;
|
|
1304
|
+
const lr = end[x] > start[x];
|
|
1305
|
+
const d = lr ? 1 : -1;
|
|
1306
|
+
const o = g.r ? -1 : 1;
|
|
1307
|
+
const rd = radius * d;
|
|
1308
|
+
const ro = radius * o;
|
|
1309
|
+
const t = trackPos;
|
|
1310
|
+
let s = 0;
|
|
1311
|
+
if (g.r) s = 1 - s;
|
|
1312
|
+
if (!lr) s = 1 - s;
|
|
1313
|
+
if (!g.v) s = 1 - s;
|
|
1314
|
+
const p = { ...start, s };
|
|
1315
|
+
const path = [];
|
|
1316
|
+
const advance = (p2, type) => {
|
|
1317
|
+
path.push(this.pathLine(p, p2, type, radius));
|
|
1318
|
+
};
|
|
1319
|
+
return { x, y, lr, d, o, rd, ro, t, s, p, path, advance };
|
|
1320
|
+
}
|
|
1321
|
+
// Create a railroad-style path with two 90-degree turns
|
|
1322
|
+
static createRailroadPath(g, start, end, trackPos, radius) {
|
|
1323
|
+
const { x, y, rd, ro, t, s, p, path, advance } = this.pathBuilder(g, start, end, trackPos, radius);
|
|
1324
|
+
advance({ [y]: t - ro }, "line");
|
|
1325
|
+
advance({ [x]: p[x] + rd, [y]: t }, "arc");
|
|
1326
|
+
advance({ [x]: end[x] - rd }, "line");
|
|
1327
|
+
advance({ [x]: end[x], [y]: t + ro, s: 1 - s }, "arc");
|
|
1328
|
+
advance({ [y]: end[y] }, "line");
|
|
1329
|
+
return path;
|
|
1330
|
+
}
|
|
1331
|
+
// Create a mostly-vertical path with optional S-curve
|
|
1332
|
+
static createDirectPath(g, start, end, radius) {
|
|
1333
|
+
const { x, y, d, o, s, p, path, advance } = this.pathBuilder(g, start, end, 0, radius);
|
|
1334
|
+
const dx = Math.abs(end.x - start.x);
|
|
1335
|
+
const dy = Math.abs(end.y - start.y);
|
|
1336
|
+
const d_ = { x: dx, y: dy };
|
|
1337
|
+
if (dx < 0.1) {
|
|
1338
|
+
advance({ ...end }, "line");
|
|
1339
|
+
return path;
|
|
1340
|
+
}
|
|
1341
|
+
const curve = _Lines.calculateSCurve(d_[x], d_[y], radius);
|
|
1342
|
+
if (!curve || curve.Ly == 0) {
|
|
1343
|
+
advance({ ...end }, "line");
|
|
1344
|
+
return path;
|
|
1345
|
+
}
|
|
1346
|
+
const m = {
|
|
1347
|
+
[x]: d * (d_[x] - curve.Lx) / 2,
|
|
1348
|
+
[y]: o * (d_[y] - curve.Ly) / 2
|
|
1349
|
+
};
|
|
1350
|
+
advance({ [x]: p[x] + m[x], [y]: p[y] + m[y] }, "arc");
|
|
1351
|
+
advance({ [x]: end[x] - m[x], [y]: end[y] - m[y] }, "line");
|
|
1352
|
+
advance({ [x]: end[x], [y]: end[y], s: 1 - s }, "arc");
|
|
1353
|
+
return path;
|
|
1354
|
+
}
|
|
1355
|
+
static solveSCurveAngle(dx, dy, r) {
|
|
1356
|
+
const f = (alpha2) => {
|
|
1357
|
+
return dx * Math.cos(alpha2) - dy * Math.sin(alpha2) - 2 * r * Math.cos(alpha2) + 2 * r;
|
|
1358
|
+
};
|
|
1359
|
+
const fPrime = (alpha2) => {
|
|
1360
|
+
return -dx * Math.sin(alpha2) - dy * Math.cos(alpha2) + 2 * r * Math.sin(alpha2);
|
|
1361
|
+
};
|
|
1362
|
+
let alpha = Math.min(0.1, Math.abs(dx) / dy);
|
|
1363
|
+
const maxIterations = 20;
|
|
1364
|
+
const tolerance = 1e-6;
|
|
1365
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
1366
|
+
const fVal = f(alpha);
|
|
1367
|
+
const fPrimeVal = fPrime(alpha);
|
|
1368
|
+
if (Math.abs(fVal) < tolerance) {
|
|
1369
|
+
const Ly = dy - 2 * r * Math.sin(alpha);
|
|
1370
|
+
if (Ly >= 0 && alpha > 0 && alpha < Math.PI / 2) {
|
|
1371
|
+
return alpha;
|
|
1372
|
+
}
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
if (Math.abs(fPrimeVal) < 1e-10) {
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
const nextAlpha = alpha - fVal / fPrimeVal;
|
|
1379
|
+
if (nextAlpha < 0 || nextAlpha > Math.PI / 2) {
|
|
1380
|
+
break;
|
|
1381
|
+
}
|
|
1382
|
+
alpha = nextAlpha;
|
|
1383
|
+
}
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
static calculateSCurve(dx, dy, r) {
|
|
1387
|
+
const alpha = _Lines.solveSCurveAngle(dx, dy, r);
|
|
1388
|
+
if (alpha === null) {
|
|
1389
|
+
return null;
|
|
1390
|
+
}
|
|
1391
|
+
const Ly = dy - 2 * r * Math.sin(alpha);
|
|
1392
|
+
const Lx = Ly * Math.tan(alpha);
|
|
1393
|
+
const L = Ly / Math.cos(alpha);
|
|
1394
|
+
return {
|
|
1395
|
+
alpha,
|
|
1396
|
+
// Angle of each arc (radians)
|
|
1397
|
+
Lx,
|
|
1398
|
+
// Horizontal component of straight section
|
|
1399
|
+
Ly,
|
|
1400
|
+
// Vertical component of straight section
|
|
1401
|
+
L
|
|
1402
|
+
// Length of straight section
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
static pathToSVG(path) {
|
|
1406
|
+
if (!path || path.length === 0) return "";
|
|
1407
|
+
const lines = [];
|
|
1408
|
+
const first = path[0];
|
|
1409
|
+
lines.push(`M ${first.x1},${first.y1}`);
|
|
1410
|
+
for (const line of path) {
|
|
1411
|
+
if (line.type === "line") {
|
|
1412
|
+
lines.push(`L ${line.x2},${line.y2}`);
|
|
1413
|
+
} else if (line.type === "arc") {
|
|
1414
|
+
const r = line.radius;
|
|
1415
|
+
const largeArc = 0;
|
|
1416
|
+
const sweep = line.sweep || 0;
|
|
1417
|
+
lines.push(`A ${r},${r} 0 ${largeArc} ${sweep} ${line.x2},${line.y2}`);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return lines.join(" ");
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
// src/graph/types/graph.ts
|
|
1425
|
+
var defaultOptions = {
|
|
1426
|
+
mergeOrder: ["target", "source"],
|
|
1427
|
+
nodeMargin: 15,
|
|
1428
|
+
dummyNodeSize: 15,
|
|
1429
|
+
defaultPortOffset: 20,
|
|
1430
|
+
nodeAlign: "natural",
|
|
1431
|
+
edgeSpacing: 10,
|
|
1432
|
+
turnRadius: 10,
|
|
1433
|
+
orientation: "TB",
|
|
1434
|
+
layerMargin: 5,
|
|
1435
|
+
alignIterations: 5,
|
|
1436
|
+
alignThreshold: 10,
|
|
1437
|
+
separateTrackSets: true,
|
|
1438
|
+
layoutSteps: null
|
|
1439
|
+
};
|
|
1440
|
+
var Graph = class _Graph {
|
|
1441
|
+
prior;
|
|
1442
|
+
nodes;
|
|
1443
|
+
edges;
|
|
1444
|
+
segs;
|
|
1445
|
+
layers;
|
|
1446
|
+
layerList;
|
|
1447
|
+
nextLayerId;
|
|
1448
|
+
nextDummyId;
|
|
1449
|
+
options;
|
|
1450
|
+
changes;
|
|
1451
|
+
dirtyNodes;
|
|
1452
|
+
dirtyEdges;
|
|
1453
|
+
dirtyLayers;
|
|
1454
|
+
dirtySegs;
|
|
1455
|
+
dirty;
|
|
1456
|
+
delNodes;
|
|
1457
|
+
delEdges;
|
|
1458
|
+
delSegs;
|
|
1459
|
+
r;
|
|
1460
|
+
v;
|
|
1461
|
+
n;
|
|
1462
|
+
h;
|
|
1463
|
+
w;
|
|
1464
|
+
x;
|
|
1465
|
+
y;
|
|
1466
|
+
d;
|
|
1554
1467
|
constructor({ prior, changes, options, nodes, edges } = {}) {
|
|
1555
|
-
this.
|
|
1556
|
-
this.
|
|
1557
|
-
this.
|
|
1558
|
-
this.layerList = (prior == null ? void 0 : prior.layerList) ?? IList();
|
|
1559
|
-
this.segs = (prior == null ? void 0 : prior.segs) ?? IMap();
|
|
1560
|
-
this.nextLayerId = (prior == null ? void 0 : prior.nextLayerId) ?? 0;
|
|
1561
|
-
this.nextDummyId = (prior == null ? void 0 : prior.nextDummyId) ?? 0;
|
|
1562
|
-
this._dirtyNodes = /* @__PURE__ */ new Set();
|
|
1563
|
-
this._dirtyEdges = /* @__PURE__ */ new Set();
|
|
1468
|
+
this.initFromPrior(prior);
|
|
1469
|
+
this.dirtyNodes = /* @__PURE__ */ new Set();
|
|
1470
|
+
this.dirtyEdges = /* @__PURE__ */ new Set();
|
|
1564
1471
|
this.dirtyLayers = /* @__PURE__ */ new Set();
|
|
1565
|
-
this.
|
|
1472
|
+
this.dirtySegs = /* @__PURE__ */ new Set();
|
|
1473
|
+
this.delNodes = /* @__PURE__ */ new Set();
|
|
1474
|
+
this.delEdges = /* @__PURE__ */ new Set();
|
|
1475
|
+
this.delSegs = /* @__PURE__ */ new Set();
|
|
1566
1476
|
this.options = {
|
|
1567
1477
|
...defaultOptions,
|
|
1568
|
-
...
|
|
1569
|
-
...options
|
|
1478
|
+
...prior?.options,
|
|
1479
|
+
...options
|
|
1570
1480
|
};
|
|
1571
1481
|
this.changes = changes ?? {
|
|
1572
1482
|
addedNodes: [],
|
|
1573
1483
|
removedNodes: [],
|
|
1484
|
+
updatedNodes: [],
|
|
1574
1485
|
addedEdges: [],
|
|
1575
1486
|
removedEdges: []
|
|
1576
1487
|
};
|
|
1577
1488
|
this.changes.addedNodes.push(...nodes || []);
|
|
1578
1489
|
this.changes.addedEdges.push(...edges || []);
|
|
1579
|
-
this.
|
|
1580
|
-
this.
|
|
1581
|
-
this.
|
|
1582
|
-
this.
|
|
1583
|
-
this.
|
|
1584
|
-
this.
|
|
1585
|
-
this.
|
|
1586
|
-
this.
|
|
1587
|
-
x: this.
|
|
1588
|
-
y: this.
|
|
1490
|
+
this.dirty = this.changes.addedNodes.length > 0 || this.changes.removedNodes.length > 0 || this.changes.addedEdges.length > 0 || this.changes.removedEdges.length > 0;
|
|
1491
|
+
this.r = this.options.orientation === "BT" || this.options.orientation === "RL";
|
|
1492
|
+
this.v = this.options.orientation === "TB" || this.options.orientation === "BT";
|
|
1493
|
+
this.h = this.v ? "h" : "w";
|
|
1494
|
+
this.w = this.v ? "w" : "h";
|
|
1495
|
+
this.x = this.v ? "x" : "y";
|
|
1496
|
+
this.y = this.v ? "y" : "x";
|
|
1497
|
+
this.d = {
|
|
1498
|
+
x: this.v ? 0 : this.r ? -1 : 1,
|
|
1499
|
+
y: this.v ? this.r ? -1 : 1 : 0
|
|
1589
1500
|
};
|
|
1590
1501
|
const natAligns = { TB: "top", BT: "bottom", LR: "left", RL: "right" };
|
|
1591
1502
|
if (this.options.nodeAlign == "natural")
|
|
1592
|
-
this.
|
|
1503
|
+
this.n = true;
|
|
1593
1504
|
else
|
|
1594
|
-
this.
|
|
1595
|
-
if (this.
|
|
1505
|
+
this.n = natAligns[this.options.orientation] == this.options.nodeAlign;
|
|
1506
|
+
if (this.dirty) {
|
|
1507
|
+
try {
|
|
1508
|
+
this.beginMutate();
|
|
1509
|
+
this.applyChanges();
|
|
1510
|
+
Cycles.checkCycles(this);
|
|
1511
|
+
Layers.updateLayers(this);
|
|
1512
|
+
Dummy.updateDummies(this);
|
|
1513
|
+
Dummy.mergeDummies(this);
|
|
1514
|
+
Layout.positionNodes(this);
|
|
1515
|
+
Layout.alignAll(this);
|
|
1516
|
+
Lines.trackEdges(this);
|
|
1517
|
+
Layout.getCoords(this);
|
|
1518
|
+
Lines.pathEdges(this);
|
|
1519
|
+
} catch (e) {
|
|
1520
|
+
this.initFromPrior(this.prior);
|
|
1521
|
+
throw e;
|
|
1522
|
+
} finally {
|
|
1523
|
+
this.endMutate();
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
applyChanges() {
|
|
1528
|
+
for (const edge of this.changes.removedEdges)
|
|
1529
|
+
this.getEdge(Edge.id(edge)).delSelf(this);
|
|
1530
|
+
for (const node of this.changes.removedNodes)
|
|
1531
|
+
this.getNode(node.id).delSelf(this);
|
|
1532
|
+
for (const node of this.changes.addedNodes)
|
|
1533
|
+
Node.addNormal(this, node);
|
|
1534
|
+
for (const edge of this.changes.addedEdges)
|
|
1535
|
+
Edge.add(this, edge);
|
|
1536
|
+
for (const node of this.changes.updatedNodes)
|
|
1537
|
+
this.dirtyNodes.add(node.id);
|
|
1538
|
+
}
|
|
1539
|
+
layerAt(index) {
|
|
1540
|
+
while (index >= this.layerList.size)
|
|
1541
|
+
this.addLayer();
|
|
1542
|
+
const layerId = this.layerList.get(index);
|
|
1543
|
+
return this.getLayer(layerId);
|
|
1544
|
+
}
|
|
1545
|
+
addLayer() {
|
|
1546
|
+
const id = `${Layer.prefix}${this.nextLayerId++}`;
|
|
1547
|
+
this.layers.set(id, new Layer({
|
|
1548
|
+
id,
|
|
1549
|
+
index: this.layerList.size,
|
|
1550
|
+
nodeIds: ISet6()
|
|
1551
|
+
}));
|
|
1552
|
+
this.layerList.push(id);
|
|
1553
|
+
this.dirtyLayers.add(id);
|
|
1554
|
+
}
|
|
1555
|
+
isEdgeId(id) {
|
|
1556
|
+
return id.startsWith(Edge.prefix);
|
|
1557
|
+
}
|
|
1558
|
+
isSegId(id) {
|
|
1559
|
+
return id.startsWith(Seg.prefix);
|
|
1560
|
+
}
|
|
1561
|
+
getNode(nodeId) {
|
|
1562
|
+
const node = this.nodes.get(nodeId);
|
|
1563
|
+
if (!node) throw new Error(`cannot find node ${nodeId}`);
|
|
1564
|
+
return node;
|
|
1565
|
+
}
|
|
1566
|
+
getEdge(edgeId) {
|
|
1567
|
+
const edge = this.edges.get(edgeId);
|
|
1568
|
+
if (!edge) throw new Error(`cannot find edge ${edgeId}`);
|
|
1569
|
+
return edge;
|
|
1570
|
+
}
|
|
1571
|
+
getSeg(segId) {
|
|
1572
|
+
const seg = this.segs.get(segId);
|
|
1573
|
+
if (!seg) throw new Error(`cannot find seg ${segId}`);
|
|
1574
|
+
return seg;
|
|
1575
|
+
}
|
|
1576
|
+
getLayer(layerId) {
|
|
1577
|
+
const layer = this.layers.get(layerId);
|
|
1578
|
+
if (!layer) throw new Error(`cannot find layer ${layerId}`);
|
|
1579
|
+
return layer;
|
|
1580
|
+
}
|
|
1581
|
+
layerIndex(nodeId) {
|
|
1582
|
+
return this.getNode(nodeId).layerIndex(this);
|
|
1583
|
+
}
|
|
1584
|
+
getRel(relId) {
|
|
1585
|
+
return this.isSegId(relId) ? this.getSeg(relId) : this.getEdge(relId);
|
|
1586
|
+
}
|
|
1587
|
+
*getNodes(includeDummy = false) {
|
|
1588
|
+
const gen = this.nodes.values();
|
|
1589
|
+
for (const node of this.nodes.values())
|
|
1590
|
+
if (includeDummy || !node.isDummy)
|
|
1591
|
+
yield node;
|
|
1592
|
+
}
|
|
1593
|
+
*getEdges() {
|
|
1594
|
+
yield* this.edges.values();
|
|
1595
|
+
}
|
|
1596
|
+
*getSegs() {
|
|
1597
|
+
yield* this.segs.values();
|
|
1598
|
+
}
|
|
1599
|
+
withMutations(callback) {
|
|
1600
|
+
const mut = new Mutator();
|
|
1601
|
+
callback(mut);
|
|
1602
|
+
return new _Graph({ prior: this, changes: mut.changes });
|
|
1603
|
+
}
|
|
1604
|
+
addNode(node) {
|
|
1605
|
+
return this.withMutations((mutator) => {
|
|
1606
|
+
mutator.addNode(node);
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
addNodes(...nodes) {
|
|
1610
|
+
return this.withMutations((mutator) => {
|
|
1611
|
+
nodes.forEach((node) => mutator.addNode(node));
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
addEdges(...edges) {
|
|
1615
|
+
return this.withMutations((mutator) => {
|
|
1616
|
+
edges.forEach((edge) => mutator.addEdge(edge));
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1619
|
+
addEdge(edge) {
|
|
1620
|
+
return this.withMutations((mutator) => {
|
|
1621
|
+
mutator.addEdge(edge);
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
removeNodes(...nodes) {
|
|
1625
|
+
return this.withMutations((mutator) => {
|
|
1626
|
+
nodes.forEach((node) => mutator.removeNode(node));
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
removeNode(node) {
|
|
1630
|
+
return this.withMutations((mutator) => {
|
|
1631
|
+
mutator.removeNode(node);
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
removeEdges(...edges) {
|
|
1635
|
+
return this.withMutations((mutator) => {
|
|
1636
|
+
edges.forEach((edge) => mutator.removeEdge(edge));
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
removeEdge(edge) {
|
|
1640
|
+
return this.withMutations((mutator) => {
|
|
1641
|
+
mutator.removeEdge(edge);
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
mutateNode(node) {
|
|
1645
|
+
if (node.mutable) return node;
|
|
1646
|
+
node = node.asMutable().set("mutable", true);
|
|
1647
|
+
this.nodes.set(node.id, node);
|
|
1648
|
+
this.dirtyNodes.add(node.id);
|
|
1649
|
+
return node;
|
|
1650
|
+
}
|
|
1651
|
+
mutateEdge(edge) {
|
|
1652
|
+
if (edge.mutable) return edge;
|
|
1653
|
+
edge = edge.asMutable().set("mutable", true);
|
|
1654
|
+
this.edges.set(edge.id, edge);
|
|
1655
|
+
this.dirtyEdges.add(edge.id);
|
|
1656
|
+
return edge;
|
|
1657
|
+
}
|
|
1658
|
+
mutateLayer(layer) {
|
|
1659
|
+
if (layer.mutable) return layer;
|
|
1660
|
+
layer = layer.asMutable().set("mutable", true);
|
|
1661
|
+
this.layers.set(layer.id, layer);
|
|
1662
|
+
this.dirtyLayers.add(layer.id);
|
|
1663
|
+
return layer;
|
|
1664
|
+
}
|
|
1665
|
+
mutateSeg(seg) {
|
|
1666
|
+
if (seg.mutable) return seg;
|
|
1667
|
+
seg = seg.asMutable().set("mutable", true);
|
|
1668
|
+
this.segs.set(seg.id, seg);
|
|
1669
|
+
this.dirtySegs.add(seg.id);
|
|
1670
|
+
return seg;
|
|
1671
|
+
}
|
|
1672
|
+
initFromPrior(prior) {
|
|
1673
|
+
this.nodes = prior?.nodes ?? IMap();
|
|
1674
|
+
this.edges = prior?.edges ?? IMap();
|
|
1675
|
+
this.layers = prior?.layers ?? IMap();
|
|
1676
|
+
this.layerList = prior?.layerList ?? IList();
|
|
1677
|
+
this.segs = prior?.segs ?? IMap();
|
|
1678
|
+
this.nextLayerId = prior?.nextLayerId ?? 0;
|
|
1679
|
+
this.nextDummyId = prior?.nextDummyId ?? 0;
|
|
1680
|
+
this.prior = prior;
|
|
1681
|
+
}
|
|
1682
|
+
beginMutate() {
|
|
1683
|
+
this.nodes = this.nodes.asMutable();
|
|
1684
|
+
this.edges = this.edges.asMutable();
|
|
1685
|
+
this.layers = this.layers.asMutable();
|
|
1686
|
+
this.layerList = this.layerList.asMutable();
|
|
1687
|
+
this.segs = this.segs.asMutable();
|
|
1688
|
+
}
|
|
1689
|
+
endMutate() {
|
|
1690
|
+
for (const nodeId of this.dirtyNodes)
|
|
1691
|
+
if (this.nodes.has(nodeId))
|
|
1692
|
+
this.nodes.set(nodeId, this.nodes.get(nodeId).final());
|
|
1693
|
+
for (const edgeId of this.dirtyEdges)
|
|
1694
|
+
if (this.edges.has(edgeId))
|
|
1695
|
+
this.edges.set(edgeId, this.edges.get(edgeId).final());
|
|
1696
|
+
for (const segId of this.dirtySegs)
|
|
1697
|
+
if (this.segs.has(segId))
|
|
1698
|
+
this.segs.set(segId, this.segs.get(segId).final());
|
|
1699
|
+
for (const layerId of this.dirtyLayers)
|
|
1700
|
+
if (this.layers.has(layerId))
|
|
1701
|
+
this.layers.set(layerId, this.layers.get(layerId).final());
|
|
1702
|
+
this.nodes = this.nodes.asImmutable();
|
|
1703
|
+
this.edges = this.edges.asImmutable();
|
|
1704
|
+
this.layers = this.layers.asImmutable();
|
|
1705
|
+
this.layerList = this.layerList.asImmutable();
|
|
1706
|
+
this.segs = this.segs.asImmutable();
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
|
|
1710
|
+
// src/canvas/canvas.css
|
|
1711
|
+
var canvas_default = ".g3p-canvas-container {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.g3p-canvas-root {\n display: block;\n width: 100%;\n height: 100%;\n user-select: none;\n background: #fafafa;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n}";
|
|
1712
|
+
|
|
1713
|
+
// src/canvas/styler.ts
|
|
1714
|
+
var injected = {};
|
|
1715
|
+
function styler(name, styles, prefix) {
|
|
1716
|
+
if (prefix === "g3p" && !injected[name]) {
|
|
1717
|
+
const style = document.createElement("style");
|
|
1718
|
+
style.textContent = styles;
|
|
1719
|
+
document.head.appendChild(style);
|
|
1720
|
+
injected[name] = true;
|
|
1721
|
+
}
|
|
1722
|
+
return (str, condition) => {
|
|
1723
|
+
if (!(condition ?? true)) return "";
|
|
1724
|
+
const parts = str.split(/\s+/);
|
|
1725
|
+
const fixed = parts.map((p) => `${prefix}-${name}-${p}`);
|
|
1726
|
+
return fixed.join(" ");
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// src/canvas/node.css
|
|
1731
|
+
var node_default = ".g3p-node-container {\n transition: opacity 0.2s ease;\n}\n\n.g3p-node-background {\n fill: var(--graph-node-bg, #ffffff);\n filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));\n}\n\n.g3p-node-border {\n stroke: var(--graph-node-border, #cbd5e1);\n transition: stroke 0.2s ease, stroke-width 0.2s ease;\n}\n\n.g3p-node-background.hovered {\n fill: var(--graph-node-hover, #f1f5f9);\n}\n\n.g3p-node-border.hovered {\n stroke: #94a3b8;\n}\n\n.g3p-node-border.selected {\n stroke: var(--graph-node-border-selected, #3b82f6);\n stroke-width: 3;\n}\n\n.g3p-node-content-wrapper {\n pointer-events: none;\n}\n\n.g3p-node-content {\n pointer-events: auto;\n box-sizing: border-box;\n}\n\n.g3p-node-content>div {\n width: 100%;\n height: 100%;\n}\n\n/* Dummy node styles */\n.g3p-node-dummy .g3p-node-background {\n fill: var(--graph-dummy-node-bg, #f8fafc);\n opacity: 0.8;\n}\n\n.g3p-node-dummy .g3p-node-border {\n stroke: var(--graph-dummy-node-border, #cbd5e1);\n stroke-dasharray: 3, 3;\n}";
|
|
1732
|
+
|
|
1733
|
+
// src/canvas/node.tsx
|
|
1734
|
+
import { Fragment, jsx as jsx2, jsxs } from "jsx-dom/jsx-runtime";
|
|
1735
|
+
var Node2 = class {
|
|
1736
|
+
selected;
|
|
1737
|
+
hovered;
|
|
1738
|
+
container;
|
|
1739
|
+
dims;
|
|
1740
|
+
content;
|
|
1741
|
+
measured;
|
|
1742
|
+
isDummy;
|
|
1743
|
+
constructor(options) {
|
|
1744
|
+
this.isDummy = false;
|
|
1745
|
+
Object.assign(this, {
|
|
1746
|
+
selected: false,
|
|
1747
|
+
hovered: false,
|
|
1748
|
+
renderNode: () => null,
|
|
1749
|
+
onClick: () => null,
|
|
1750
|
+
onMouseEnter: () => null,
|
|
1751
|
+
onMouseLeave: () => null,
|
|
1752
|
+
onContextMenu: () => null,
|
|
1753
|
+
onMouseDown: () => null,
|
|
1754
|
+
onMouseUp: () => null,
|
|
1755
|
+
classPrefix: "g3p",
|
|
1756
|
+
...options
|
|
1757
|
+
});
|
|
1758
|
+
if (!this.isDummy) {
|
|
1759
|
+
this.content = this.renderNode(this.data);
|
|
1760
|
+
this.measured = false;
|
|
1761
|
+
} else {
|
|
1762
|
+
this.measured = true;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
getSize() {
|
|
1766
|
+
const rect = this.content.getBoundingClientRect();
|
|
1767
|
+
this.dims = { w: rect.width, h: rect.height };
|
|
1768
|
+
this.measured = true;
|
|
1769
|
+
}
|
|
1770
|
+
handleClick(e) {
|
|
1771
|
+
e.stopPropagation();
|
|
1772
|
+
this.onClick?.(this.data, e);
|
|
1773
|
+
}
|
|
1774
|
+
handleMouseEnter(e) {
|
|
1775
|
+
this.onMouseEnter?.(this.data, e);
|
|
1776
|
+
}
|
|
1777
|
+
handleMouseLeave(e) {
|
|
1778
|
+
this.onMouseLeave?.(this.data, e);
|
|
1779
|
+
}
|
|
1780
|
+
handleContextMenu(e) {
|
|
1781
|
+
if (this.onContextMenu) {
|
|
1782
|
+
e.stopPropagation();
|
|
1783
|
+
this.onContextMenu(this.data, e);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
handleMouseDown(e) {
|
|
1787
|
+
this.onMouseDown?.(this.data, e);
|
|
1788
|
+
}
|
|
1789
|
+
handleMouseUp(e) {
|
|
1790
|
+
this.onMouseUp?.(this.data, e);
|
|
1791
|
+
}
|
|
1792
|
+
setPos(pos) {
|
|
1793
|
+
console.log(`setPos:`, this, pos);
|
|
1794
|
+
this.pos = pos;
|
|
1795
|
+
this.container.setAttribute("transform", `translate(${this.pos.x}, ${this.pos.y})`);
|
|
1796
|
+
}
|
|
1797
|
+
// render will be called once the node is measured
|
|
1798
|
+
render() {
|
|
1799
|
+
const c = styler("node", node_default, this.classPrefix);
|
|
1800
|
+
return /* @__PURE__ */ jsx2(
|
|
1801
|
+
"g",
|
|
1802
|
+
{
|
|
1803
|
+
ref: (el) => this.container = el,
|
|
1804
|
+
className: `${c("container")} ${c("dummy", this.isDummy)}`,
|
|
1805
|
+
onClick: this.handleClick.bind(this),
|
|
1806
|
+
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
1807
|
+
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
1808
|
+
onContextMenu: this.handleContextMenu.bind(this),
|
|
1809
|
+
onMouseDown: this.handleMouseDown.bind(this),
|
|
1810
|
+
onMouseUp: this.handleMouseUp.bind(this),
|
|
1811
|
+
style: { cursor: "pointer" },
|
|
1812
|
+
children: this.isDummy ? this.renderDummy() : this.renderContent()
|
|
1813
|
+
}
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
renderDummy() {
|
|
1817
|
+
const c = styler("node", node_default, this.classPrefix);
|
|
1818
|
+
let { w, h } = this.dims;
|
|
1819
|
+
w /= 2;
|
|
1820
|
+
h /= 2;
|
|
1821
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1822
|
+
/* @__PURE__ */ jsx2(
|
|
1823
|
+
"ellipse",
|
|
1824
|
+
{
|
|
1825
|
+
cx: w,
|
|
1826
|
+
cy: h,
|
|
1827
|
+
rx: w,
|
|
1828
|
+
ry: h,
|
|
1829
|
+
className: c("background")
|
|
1830
|
+
}
|
|
1831
|
+
),
|
|
1832
|
+
/* @__PURE__ */ jsx2(
|
|
1833
|
+
"ellipse",
|
|
1834
|
+
{
|
|
1835
|
+
cx: w,
|
|
1836
|
+
cy: h,
|
|
1837
|
+
rx: w,
|
|
1838
|
+
ry: h,
|
|
1839
|
+
fill: "none",
|
|
1840
|
+
className: c("border"),
|
|
1841
|
+
strokeWidth: "2"
|
|
1842
|
+
}
|
|
1843
|
+
)
|
|
1844
|
+
] });
|
|
1845
|
+
}
|
|
1846
|
+
renderContent() {
|
|
1847
|
+
const c = styler("node", node_default, this.classPrefix);
|
|
1848
|
+
const { w, h } = this.dims;
|
|
1849
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1850
|
+
/* @__PURE__ */ jsx2(
|
|
1851
|
+
"rect",
|
|
1852
|
+
{
|
|
1853
|
+
className: c("background"),
|
|
1854
|
+
width: w,
|
|
1855
|
+
height: h,
|
|
1856
|
+
rx: 8,
|
|
1857
|
+
ry: 8
|
|
1858
|
+
}
|
|
1859
|
+
),
|
|
1860
|
+
/* @__PURE__ */ jsx2(
|
|
1861
|
+
"rect",
|
|
1862
|
+
{
|
|
1863
|
+
className: c("border"),
|
|
1864
|
+
width: w,
|
|
1865
|
+
height: h,
|
|
1866
|
+
rx: 8,
|
|
1867
|
+
ry: 8,
|
|
1868
|
+
fill: "none",
|
|
1869
|
+
strokeWidth: "2"
|
|
1870
|
+
}
|
|
1871
|
+
),
|
|
1872
|
+
/* @__PURE__ */ jsx2(
|
|
1873
|
+
"foreignObject",
|
|
1874
|
+
{
|
|
1875
|
+
width: w,
|
|
1876
|
+
height: h,
|
|
1877
|
+
className: c("content-wrapper"),
|
|
1878
|
+
children: /* @__PURE__ */ jsx2(
|
|
1879
|
+
"div",
|
|
1880
|
+
{
|
|
1881
|
+
className: c("content"),
|
|
1882
|
+
style: {
|
|
1883
|
+
width: `${w}px`,
|
|
1884
|
+
height: `${h}px`,
|
|
1885
|
+
overflow: "hidden"
|
|
1886
|
+
},
|
|
1887
|
+
children: this.content
|
|
1888
|
+
}
|
|
1889
|
+
)
|
|
1890
|
+
}
|
|
1891
|
+
)
|
|
1892
|
+
] });
|
|
1596
1893
|
}
|
|
1597
1894
|
};
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1895
|
+
|
|
1896
|
+
// src/canvas/seg.css
|
|
1897
|
+
var seg_default = ".g3p-seg-container {\n transition: opacity 0.2s ease;\n}\n\n.g3p-seg-line {\n transition: stroke 0.2s ease, stroke-width 0.2s ease;\n}\n\n.g3p-seg-line.hovered {\n stroke-width: 4;\n opacity: 1;\n}\n\n.g3p-seg-line.selected {\n stroke: var(--graph-node-border-selected, #3b82f6);\n stroke-width: 3;\n}\n\n.g3p-seg-hitbox {\n cursor: pointer;\n}";
|
|
1898
|
+
|
|
1899
|
+
// src/canvas/seg.tsx
|
|
1900
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "jsx-dom/jsx-runtime";
|
|
1901
|
+
var Seg2 = class {
|
|
1902
|
+
selected;
|
|
1903
|
+
hovered;
|
|
1904
|
+
constructor(options) {
|
|
1905
|
+
this.selected = false;
|
|
1906
|
+
this.hovered = false;
|
|
1907
|
+
Object.assign(this, {
|
|
1908
|
+
onClick: () => {
|
|
1909
|
+
},
|
|
1910
|
+
onMouseEnter: () => {
|
|
1911
|
+
},
|
|
1912
|
+
onMouseLeave: () => {
|
|
1913
|
+
},
|
|
1914
|
+
onContextMenu: () => {
|
|
1915
|
+
},
|
|
1916
|
+
classPrefix: "g3p",
|
|
1917
|
+
...options
|
|
1918
|
+
});
|
|
1919
|
+
this.attrs ??= {};
|
|
1920
|
+
this.attrs.targetTerminal ??= "arrow";
|
|
1921
|
+
}
|
|
1922
|
+
handleClick(e) {
|
|
1923
|
+
e.stopPropagation();
|
|
1924
|
+
this.onClick?.(this.edgeData, e);
|
|
1925
|
+
}
|
|
1926
|
+
handleMouseEnter(e) {
|
|
1927
|
+
this.onMouseEnter?.(this.edgeData, e);
|
|
1928
|
+
}
|
|
1929
|
+
handleMouseLeave(e) {
|
|
1930
|
+
this.onMouseLeave?.(this.edgeData, e);
|
|
1931
|
+
}
|
|
1932
|
+
handleContextMenu(e) {
|
|
1933
|
+
if (this.onContextMenu) {
|
|
1934
|
+
e.stopPropagation();
|
|
1935
|
+
this.onContextMenu(this.edgeData, e);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
renderTerminals() {
|
|
1939
|
+
return {
|
|
1940
|
+
source: this.renderTerminal(this.attrs.sourceTerminal, "source"),
|
|
1941
|
+
target: this.renderTerminal(this.attrs.targetTerminal, "target")
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
setSVG(svg) {
|
|
1945
|
+
this.svg = svg;
|
|
1946
|
+
const n = this.el.childElementCount;
|
|
1947
|
+
this.el.childNodes[n - 2].setAttribute("d", svg);
|
|
1948
|
+
this.el.childNodes[n - 1].setAttribute("d", svg);
|
|
1949
|
+
}
|
|
1950
|
+
render() {
|
|
1951
|
+
const c = styler("edge", seg_default, this.classPrefix);
|
|
1952
|
+
const styleAttrs = {
|
|
1953
|
+
stroke: this.attrs.color,
|
|
1954
|
+
strokeWidth: this.attrs.width,
|
|
1955
|
+
strokeDasharray: this.attrs.style
|
|
1956
|
+
};
|
|
1957
|
+
const hoverAttrs = {
|
|
1958
|
+
...styleAttrs,
|
|
1959
|
+
strokeWidth: styleAttrs.strokeWidth ? Math.max(styleAttrs.strokeWidth * 3, 10) : void 0
|
|
1960
|
+
};
|
|
1961
|
+
const { source, target } = this.renderTerminals();
|
|
1962
|
+
return /* @__PURE__ */ jsxs2(
|
|
1963
|
+
"g",
|
|
1964
|
+
{
|
|
1965
|
+
ref: (el) => this.el = el,
|
|
1966
|
+
id: `g3p-seg-${this.segId}`,
|
|
1967
|
+
className: c("container"),
|
|
1968
|
+
onClick: this.handleClick.bind(this),
|
|
1969
|
+
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
1970
|
+
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
1971
|
+
onContextMenu: this.handleContextMenu.bind(this),
|
|
1972
|
+
children: [
|
|
1973
|
+
source?.defs,
|
|
1974
|
+
target?.defs,
|
|
1975
|
+
/* @__PURE__ */ jsx3(
|
|
1976
|
+
"path",
|
|
1977
|
+
{
|
|
1978
|
+
d: this.svg,
|
|
1979
|
+
...styleAttrs,
|
|
1980
|
+
fill: "none",
|
|
1981
|
+
className: c("line"),
|
|
1982
|
+
markerStart: source ? `url(#${source.id})` : void 0,
|
|
1983
|
+
markerEnd: target ? `url(#${target.id})` : void 0
|
|
1984
|
+
}
|
|
1985
|
+
),
|
|
1986
|
+
/* @__PURE__ */ jsx3(
|
|
1987
|
+
"path",
|
|
1988
|
+
{
|
|
1989
|
+
d: this.svg,
|
|
1990
|
+
...hoverAttrs,
|
|
1991
|
+
stroke: "transparent",
|
|
1992
|
+
fill: "none",
|
|
1993
|
+
className: c("hitbox"),
|
|
1994
|
+
style: { cursor: "pointer" }
|
|
1995
|
+
}
|
|
1996
|
+
)
|
|
1997
|
+
]
|
|
1998
|
+
}
|
|
1999
|
+
);
|
|
2000
|
+
}
|
|
2001
|
+
renderTerminal(type, side) {
|
|
2002
|
+
if (!type)
|
|
2003
|
+
return null;
|
|
2004
|
+
const id = `g3p-seg-${this.segId}-${side}-${type}`;
|
|
2005
|
+
const defs = /* @__PURE__ */ jsx3("defs", { children: /* @__PURE__ */ jsx3(
|
|
2006
|
+
"marker",
|
|
2007
|
+
{
|
|
2008
|
+
id,
|
|
2009
|
+
markerWidth: "10",
|
|
2010
|
+
markerHeight: "10",
|
|
2011
|
+
refX: "9",
|
|
2012
|
+
refY: "3",
|
|
2013
|
+
orient: "auto",
|
|
2014
|
+
markerUnits: "userSpaceOnUse",
|
|
2015
|
+
children: /* @__PURE__ */ jsx3("path", { d: "M0,0 L0,6 L9,3 z" })
|
|
2016
|
+
}
|
|
2017
|
+
) });
|
|
2018
|
+
return { id, defs };
|
|
2019
|
+
}
|
|
2020
|
+
};
|
|
2021
|
+
|
|
2022
|
+
// src/canvas/canvas.tsx
|
|
2023
|
+
import { jsx as jsx4 } from "jsx-dom/jsx-runtime";
|
|
2024
|
+
var Canvas = class {
|
|
2025
|
+
container;
|
|
2026
|
+
root;
|
|
2027
|
+
group;
|
|
2028
|
+
transform;
|
|
2029
|
+
bounds;
|
|
2030
|
+
measurement;
|
|
2031
|
+
nodes;
|
|
2032
|
+
segs;
|
|
2033
|
+
updating;
|
|
2034
|
+
constructor(options) {
|
|
2035
|
+
Object.assign(this, {
|
|
2036
|
+
renderNode,
|
|
2037
|
+
nodeStyle: () => ({}),
|
|
2038
|
+
edgeStyle: () => ({}),
|
|
2039
|
+
portStyle: "outside",
|
|
2040
|
+
classPrefix: "g3p",
|
|
2041
|
+
width: "100%",
|
|
2042
|
+
height: "100%",
|
|
2043
|
+
transform: { x: 0, y: 0, scale: 1 },
|
|
2044
|
+
bounds: { min: { x: 0, y: 0 }, max: { x: 1, y: 1 } },
|
|
2045
|
+
...options
|
|
2046
|
+
});
|
|
2047
|
+
this.nodes = /* @__PURE__ */ new Map();
|
|
2048
|
+
this.segs = /* @__PURE__ */ new Map();
|
|
2049
|
+
this.updating = false;
|
|
2050
|
+
this.createMeasurementContainer();
|
|
2051
|
+
}
|
|
2052
|
+
createMeasurementContainer() {
|
|
2053
|
+
this.measurement = document.createElement("div");
|
|
2054
|
+
this.measurement.style.cssText = `
|
|
2055
|
+
position: absolute;
|
|
2056
|
+
left: -9999px;
|
|
2057
|
+
top: -9999px;
|
|
2058
|
+
visibility: hidden;
|
|
2059
|
+
pointer-events: none;
|
|
2060
|
+
`;
|
|
2061
|
+
document.body.appendChild(this.measurement);
|
|
2062
|
+
}
|
|
2063
|
+
update(callback) {
|
|
2064
|
+
this.updating = true;
|
|
2065
|
+
callback();
|
|
2066
|
+
this.updating = false;
|
|
2067
|
+
let bx0 = Infinity, by0 = Infinity;
|
|
2068
|
+
let bx1 = -Infinity, by1 = -Infinity;
|
|
2069
|
+
for (const node of this.nodes.values()) {
|
|
2070
|
+
const nx0 = node.pos.x, nx1 = node.pos.x + node.dims.w;
|
|
2071
|
+
const ny0 = node.pos.y, ny1 = node.pos.y + node.dims.h;
|
|
2072
|
+
bx0 = Math.min(bx0, nx0);
|
|
2073
|
+
by0 = Math.min(by0, ny0);
|
|
2074
|
+
bx1 = Math.max(bx1, nx1);
|
|
2075
|
+
by1 = Math.max(by1, ny1);
|
|
2076
|
+
}
|
|
2077
|
+
this.bounds = { min: { x: bx0, y: by0 }, max: { x: bx1, y: by1 } };
|
|
2078
|
+
console.log("bounds", this.bounds);
|
|
2079
|
+
this.root.setAttribute("viewBox", this.viewBox());
|
|
2080
|
+
}
|
|
2081
|
+
addNode(opts) {
|
|
2082
|
+
const node = this.nodes.get(opts.data);
|
|
2083
|
+
if (!node) throw new Error("node not found");
|
|
2084
|
+
if (!node.container) node.render();
|
|
2085
|
+
node.setPos(opts.pos);
|
|
2086
|
+
this.group.appendChild(node.container);
|
|
2087
|
+
}
|
|
2088
|
+
updateNode(opts) {
|
|
2089
|
+
const node = this.nodes.get(opts.data);
|
|
2090
|
+
if (!node) throw new Error("node not found");
|
|
2091
|
+
node.setPos(opts.pos);
|
|
2092
|
+
}
|
|
2093
|
+
deleteNode(opts) {
|
|
2094
|
+
const node = this.nodes.get(opts.data);
|
|
2095
|
+
if (!node) throw new Error("node not found");
|
|
2096
|
+
node.container.remove();
|
|
2097
|
+
}
|
|
2098
|
+
addSeg(opts) {
|
|
2099
|
+
const seg = new Seg2(opts);
|
|
2100
|
+
this.segs.set(seg.segId, seg);
|
|
2101
|
+
seg.render();
|
|
2102
|
+
this.group.appendChild(seg.el);
|
|
2103
|
+
}
|
|
2104
|
+
updateSeg(opts) {
|
|
2105
|
+
const seg = this.segs.get(opts.segId);
|
|
2106
|
+
if (!seg) throw new Error("seg not found");
|
|
2107
|
+
seg.setSVG(opts.svg);
|
|
2108
|
+
}
|
|
2109
|
+
deleteSeg(opts) {
|
|
2110
|
+
const seg = this.segs.get(opts.segId);
|
|
2111
|
+
if (!seg) throw new Error("seg not found");
|
|
2112
|
+
seg.el.remove();
|
|
2113
|
+
this.segs.delete(seg.segId);
|
|
2114
|
+
}
|
|
2115
|
+
async measure(nodes) {
|
|
2116
|
+
const newNodes = [];
|
|
2117
|
+
for (const data of nodes) {
|
|
2118
|
+
if (this.nodes.has(data)) continue;
|
|
2119
|
+
const node = new Node2({
|
|
2120
|
+
data,
|
|
2121
|
+
renderNode: this.renderNode,
|
|
2122
|
+
classPrefix: this.classPrefix,
|
|
2123
|
+
isDummy: false
|
|
2124
|
+
});
|
|
2125
|
+
this.nodes.set(node.data, node);
|
|
2126
|
+
if (!node.measured) {
|
|
2127
|
+
this.measurement.appendChild(node.content);
|
|
2128
|
+
newNodes.push(node);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return new Promise((resolve) => {
|
|
2132
|
+
requestAnimationFrame(() => {
|
|
2133
|
+
for (const node of newNodes)
|
|
2134
|
+
node.getSize();
|
|
2135
|
+
this.measurement.textContent = "";
|
|
2136
|
+
resolve();
|
|
2137
|
+
});
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
getDims(node) {
|
|
2141
|
+
return this.nodes.get(node).dims;
|
|
2142
|
+
}
|
|
2143
|
+
onClick(e) {
|
|
2144
|
+
console.log("click", e);
|
|
2145
|
+
}
|
|
2146
|
+
onContextMenu(e) {
|
|
2147
|
+
console.log("context menu", e);
|
|
2148
|
+
}
|
|
2149
|
+
groupTransform() {
|
|
2150
|
+
return `translate(${this.transform.x}, ${this.transform.y}) scale(${this.transform.scale})`;
|
|
2151
|
+
}
|
|
2152
|
+
viewBox() {
|
|
2153
|
+
return `${this.bounds.min.x} ${this.bounds.min.y} ${this.bounds.max.x - this.bounds.min.x} ${this.bounds.max.y - this.bounds.min.y}`;
|
|
2154
|
+
}
|
|
2155
|
+
render() {
|
|
2156
|
+
const c = styler("canvas", canvas_default, this.classPrefix);
|
|
2157
|
+
return /* @__PURE__ */ jsx4(
|
|
2158
|
+
"div",
|
|
2159
|
+
{
|
|
2160
|
+
className: c("container"),
|
|
2161
|
+
ref: (el) => this.container = el,
|
|
2162
|
+
onContextMenu: this.onContextMenu.bind(this),
|
|
2163
|
+
children: /* @__PURE__ */ jsx4(
|
|
2164
|
+
"svg",
|
|
2165
|
+
{
|
|
2166
|
+
ref: (el) => this.root = el,
|
|
2167
|
+
className: c("root"),
|
|
2168
|
+
width: this.width,
|
|
2169
|
+
height: this.height,
|
|
2170
|
+
viewBox: this.viewBox(),
|
|
2171
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
2172
|
+
onClick: this.onClick.bind(this),
|
|
2173
|
+
children: /* @__PURE__ */ jsx4(
|
|
2174
|
+
"g",
|
|
2175
|
+
{
|
|
2176
|
+
ref: (el) => this.group = el,
|
|
2177
|
+
transform: this.groupTransform()
|
|
2178
|
+
}
|
|
2179
|
+
)
|
|
2180
|
+
}
|
|
2181
|
+
)
|
|
2182
|
+
}
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
};
|
|
2186
|
+
|
|
2187
|
+
// src/index.ts
|
|
2188
|
+
import { Map as IMap2 } from "immutable";
|
|
2189
|
+
var identity = (x) => x;
|
|
2190
|
+
var defaultOptions2 = () => ({
|
|
2191
|
+
...defaultOptions,
|
|
2192
|
+
nodeProps: identity,
|
|
2193
|
+
edgeProps: identity,
|
|
2194
|
+
portProps: identity,
|
|
2195
|
+
renderNode,
|
|
2196
|
+
nodeStyle: (() => ({})),
|
|
2197
|
+
edgeStyle: (() => ({})),
|
|
2198
|
+
portStyle: "outside",
|
|
2199
|
+
width: "100%",
|
|
2200
|
+
height: "100%",
|
|
2201
|
+
classPrefix: "g3p"
|
|
2202
|
+
});
|
|
2203
|
+
var API = class {
|
|
2204
|
+
state;
|
|
2205
|
+
seq;
|
|
2206
|
+
index;
|
|
2207
|
+
canvas;
|
|
2208
|
+
_options;
|
|
2209
|
+
options;
|
|
2210
|
+
constructor(options) {
|
|
2211
|
+
this._options = {
|
|
2212
|
+
...defaultOptions2(),
|
|
2213
|
+
...options
|
|
2214
|
+
};
|
|
2215
|
+
this.options = new Proxy(this._options, {
|
|
2216
|
+
set: (target, prop, value) => {
|
|
2217
|
+
target[prop] = value;
|
|
2218
|
+
this._onOptionChange(prop);
|
|
2219
|
+
return true;
|
|
2220
|
+
}
|
|
2221
|
+
});
|
|
2222
|
+
this.state = {
|
|
2223
|
+
nodes: IMap2(),
|
|
2224
|
+
edges: IMap2(),
|
|
2225
|
+
ports: IMap2(),
|
|
2226
|
+
segs: IMap2()
|
|
2227
|
+
};
|
|
2228
|
+
let graph2 = new Graph({ options: this._options });
|
|
2229
|
+
this.state.graph = graph2;
|
|
2230
|
+
this.seq = [this.state];
|
|
2231
|
+
this.index = 0;
|
|
2232
|
+
this.canvas = new Canvas(this._options);
|
|
2233
|
+
this.canvas.render();
|
|
2234
|
+
}
|
|
2235
|
+
render() {
|
|
2236
|
+
return this.canvas.container;
|
|
2237
|
+
}
|
|
2238
|
+
nav(nav) {
|
|
2239
|
+
let newIndex;
|
|
2240
|
+
switch (nav) {
|
|
2241
|
+
case "first":
|
|
2242
|
+
newIndex = 0;
|
|
2243
|
+
break;
|
|
2244
|
+
case "last":
|
|
2245
|
+
newIndex = this.seq.length - 1;
|
|
2246
|
+
break;
|
|
2247
|
+
case "prev":
|
|
2248
|
+
newIndex = this.index - 1;
|
|
2249
|
+
break;
|
|
2250
|
+
case "next":
|
|
2251
|
+
newIndex = this.index + 1;
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
if (newIndex < 0 || newIndex >= this.seq.length || newIndex == this.index)
|
|
2255
|
+
return;
|
|
2256
|
+
this.applyDiff(this.index, newIndex);
|
|
2257
|
+
this.index = newIndex;
|
|
2258
|
+
this.state = this.seq[this.index];
|
|
2259
|
+
}
|
|
2260
|
+
applyDiff(oldIndex, newIndex) {
|
|
2261
|
+
const oldState = this.seq[oldIndex];
|
|
2262
|
+
const newState = this.seq[newIndex];
|
|
2263
|
+
this.canvas.update(() => {
|
|
2264
|
+
for (const oldNode of oldState.nodes.values()) {
|
|
2265
|
+
const newNode = newState.nodes.get(oldNode.id);
|
|
2266
|
+
if (!newNode) {
|
|
2267
|
+
this.canvas.deleteNode(oldNode);
|
|
2268
|
+
} else if (oldNode.data !== newNode.data) {
|
|
2269
|
+
this.canvas.deleteNode(oldNode);
|
|
2270
|
+
this.canvas.addNode(newNode);
|
|
2271
|
+
} else if (oldNode.pos.x !== newNode.pos.x || oldNode.pos.y !== newNode.pos.y) {
|
|
2272
|
+
this.canvas.updateNode(newNode);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
for (const newNode of newState.nodes.values()) {
|
|
2276
|
+
if (!oldState.nodes.has(newNode.id))
|
|
2277
|
+
this.canvas.addNode(newNode);
|
|
2278
|
+
}
|
|
2279
|
+
for (const oldSeg of oldState.segs.values()) {
|
|
2280
|
+
const newSeg = newState.segs.get(oldSeg.segId);
|
|
2281
|
+
if (!newSeg)
|
|
2282
|
+
this.canvas.deleteSeg(oldSeg);
|
|
2283
|
+
else if (oldSeg.svg != newSeg.svg)
|
|
2284
|
+
this.canvas.updateSeg(newSeg);
|
|
2285
|
+
}
|
|
2286
|
+
for (const newSeg of newState.segs.values()) {
|
|
2287
|
+
if (!oldState.segs.has(newSeg.segId))
|
|
2288
|
+
this.canvas.addSeg(newSeg);
|
|
2289
|
+
}
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
async addNode(node) {
|
|
2293
|
+
await this.update((update) => update.addNode(node));
|
|
2294
|
+
}
|
|
2295
|
+
async deleteNode(node) {
|
|
2296
|
+
await this.update((update) => update.deleteNode(node));
|
|
2297
|
+
}
|
|
2298
|
+
async updateNode(node) {
|
|
2299
|
+
await this.update((update) => update.updateNode(node));
|
|
2300
|
+
}
|
|
2301
|
+
async addEdge(edge) {
|
|
2302
|
+
await this.update((update) => update.addEdge(edge));
|
|
2303
|
+
}
|
|
2304
|
+
async deleteEdge(edge) {
|
|
2305
|
+
await this.update((update) => update.deleteEdge(edge));
|
|
2306
|
+
}
|
|
2307
|
+
async update(callback) {
|
|
2308
|
+
const update = new Update();
|
|
2309
|
+
callback(update);
|
|
2310
|
+
await this.measureNodes(update);
|
|
2311
|
+
const newGraph = this.state.graph.withMutations((mut) => {
|
|
2312
|
+
this.state = {
|
|
2313
|
+
nodes: this.state.nodes.asMutable(),
|
|
2314
|
+
edges: this.state.edges.asMutable(),
|
|
2315
|
+
ports: this.state.ports.asMutable(),
|
|
2316
|
+
segs: this.state.segs.asMutable()
|
|
2317
|
+
};
|
|
2318
|
+
for (const node of update.updatedNodes)
|
|
2319
|
+
this._updateNode(node, mut);
|
|
2320
|
+
for (const edge of update.updatedEdges)
|
|
2321
|
+
this._updateEdge(edge, mut);
|
|
2322
|
+
for (const node of update.addedNodes)
|
|
2323
|
+
this._addNode(node, mut);
|
|
2324
|
+
for (const node of update.removedNodes)
|
|
2325
|
+
this._removeNode(node, mut);
|
|
2326
|
+
for (const edge of update.addedEdges)
|
|
2327
|
+
this._addEdge(edge, mut);
|
|
2328
|
+
for (const edge of update.removedEdges)
|
|
2329
|
+
this._removeEdge(edge, mut);
|
|
2330
|
+
});
|
|
2331
|
+
console.log("new graph:", newGraph);
|
|
2332
|
+
for (const nodeId of newGraph.dirtyNodes.values()) {
|
|
2333
|
+
const node = newGraph.getNode(nodeId);
|
|
2334
|
+
console.log(`got pos of node ${nodeId}:`, node.pos);
|
|
2335
|
+
if (node.isDummy) {
|
|
2336
|
+
this.state.nodes.set(nodeId, { id: nodeId, pos: node.pos, isDummy: true });
|
|
2337
|
+
} else {
|
|
2338
|
+
const myNode = this.state.nodes.get(nodeId);
|
|
2339
|
+
this.state.nodes.set(nodeId, { ...myNode, pos: node.pos });
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
for (const nodeId of newGraph.delNodes)
|
|
2343
|
+
this.state.nodes.delete(nodeId);
|
|
2344
|
+
for (const segId of newGraph.delSegs)
|
|
2345
|
+
this.state.segs.delete(segId);
|
|
2346
|
+
for (const segId of newGraph.dirtySegs) {
|
|
2347
|
+
const seg = newGraph.getSeg(segId);
|
|
2348
|
+
const edge = this.state.edges.get(seg.edgeIds.values().next().value);
|
|
2349
|
+
const target = seg.targetNode(newGraph);
|
|
2350
|
+
this.state.segs.set(seg.id, {
|
|
2351
|
+
segId: seg.id,
|
|
2352
|
+
edgeId: edge.id,
|
|
2353
|
+
svg: seg.svg,
|
|
2354
|
+
attrs: edge.attrs,
|
|
2355
|
+
targetDummy: target.isDummy,
|
|
2356
|
+
edgeData: edge.data
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
this.state = {
|
|
2360
|
+
nodes: this.state.nodes.asImmutable(),
|
|
2361
|
+
edges: this.state.edges.asImmutable(),
|
|
2362
|
+
ports: this.state.ports.asImmutable(),
|
|
2363
|
+
segs: this.state.segs.asImmutable(),
|
|
2364
|
+
graph: newGraph,
|
|
2365
|
+
update
|
|
2366
|
+
};
|
|
2367
|
+
this.seq.splice(this.index + 1);
|
|
2368
|
+
this.seq.push(this.state);
|
|
2369
|
+
this.nav("last");
|
|
2370
|
+
}
|
|
2371
|
+
async measureNodes(update) {
|
|
2372
|
+
const nodes = update.updatedNodes.concat(update.addedNodes);
|
|
2373
|
+
await this.canvas.measure(nodes);
|
|
2374
|
+
}
|
|
2375
|
+
getDims(node) {
|
|
2376
|
+
return this.canvas.getDims(node);
|
|
2377
|
+
}
|
|
2378
|
+
_updateNode(node, mut) {
|
|
2379
|
+
const props = this._options.nodeProps(node);
|
|
2380
|
+
const oldData = this.state.nodes.get(props.id);
|
|
2381
|
+
if (oldData === void 0)
|
|
2382
|
+
throw new Error(`updating node ${props.id} which does not exist`);
|
|
2383
|
+
const attrs = this._options.nodeStyle(node);
|
|
2384
|
+
mut.updateNode({ id: props.id, dims: this.getDims(node) });
|
|
2385
|
+
const data = { id: props.id, attrs, orig: node, node: props };
|
|
2386
|
+
this.state.nodes.set(props.id, data);
|
|
2387
|
+
}
|
|
2388
|
+
_updateEdge(edge, mut) {
|
|
2389
|
+
const props = this._options.edgeProps(edge);
|
|
2390
|
+
const id = Edge.id(props), str = Edge.str(props);
|
|
2391
|
+
const oldData = this.state.edges.get(id);
|
|
2392
|
+
if (oldData === void 0)
|
|
2393
|
+
throw new Error(`updating edge ${str} which does not exist`);
|
|
2394
|
+
const attrs = props.type ? this._options.edgeStyle(props.type) : void 0;
|
|
2395
|
+
const data = { id, attrs, data: edge, edge: props };
|
|
2396
|
+
this.state.edges.set(id, data);
|
|
2397
|
+
if (props.type !== oldData.edge.type) {
|
|
2398
|
+
mut.removeEdge(oldData.edge);
|
|
2399
|
+
mut.addEdge(props);
|
|
2400
|
+
} else {
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
_addNode(node, mut) {
|
|
2404
|
+
const props = this._options.nodeProps(node);
|
|
2405
|
+
if (this.state.nodes.has(props.id))
|
|
2406
|
+
throw new Error(`node with id ${props.id} already exists`);
|
|
2407
|
+
props.ports = { in: [], out: [], ...props.ports };
|
|
2408
|
+
const attrs = this._options.nodeStyle(node);
|
|
2409
|
+
const data = { id: props.id, attrs, data: node, node: props };
|
|
2410
|
+
this.state.nodes.set(props.id, data);
|
|
2411
|
+
console.log("adding node:", { ...props, dims: this.getDims(node) });
|
|
2412
|
+
mut.addNode({ ...props, dims: this.getDims(node) });
|
|
2413
|
+
}
|
|
2414
|
+
_removeNode(node, mut) {
|
|
2415
|
+
const props = this._options.nodeProps(node);
|
|
2416
|
+
if (!this.state.nodes.has(props.id))
|
|
2417
|
+
throw new Error(`removing node ${props.id} which does not exist`);
|
|
2418
|
+
this.state.nodes.delete(props.id);
|
|
2419
|
+
mut.removeNode(props);
|
|
2420
|
+
}
|
|
2421
|
+
_addEdge(edge, mut) {
|
|
2422
|
+
const props = this._options.edgeProps(edge);
|
|
2423
|
+
const id = Edge.id(props), str = Edge.str(props);
|
|
2424
|
+
if (this.state.edges.has(id))
|
|
2425
|
+
throw new Error(`edge ${str} already exists`);
|
|
2426
|
+
const attrs = props.type ? this._options.edgeStyle(props.type) : void 0;
|
|
2427
|
+
const data = { id, attrs, data: edge, edge: props };
|
|
2428
|
+
this.state.edges.set(id, data);
|
|
2429
|
+
mut.addEdge(props);
|
|
2430
|
+
}
|
|
2431
|
+
_removeEdge(edge, mut) {
|
|
2432
|
+
const props = this._options.edgeProps(edge);
|
|
2433
|
+
const id = Edge.id(props), str = Edge.str(props);
|
|
2434
|
+
if (!this.state.edges.has(id))
|
|
2435
|
+
throw new Error(`removing edge ${str} which does not exist`);
|
|
2436
|
+
this.state.edges.delete(id);
|
|
2437
|
+
mut.removeEdge(props);
|
|
2438
|
+
}
|
|
2439
|
+
_onOptionChange(prop) {
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
var Update = class {
|
|
2443
|
+
addedNodes;
|
|
2444
|
+
removedNodes;
|
|
2445
|
+
updatedNodes;
|
|
2446
|
+
addedEdges;
|
|
2447
|
+
removedEdges;
|
|
2448
|
+
updatedEdges;
|
|
2449
|
+
desc;
|
|
2450
|
+
constructor() {
|
|
2451
|
+
this.addedNodes = [];
|
|
2452
|
+
this.removedNodes = [];
|
|
2453
|
+
this.updatedNodes = [];
|
|
2454
|
+
this.addedEdges = [];
|
|
2455
|
+
this.removedEdges = [];
|
|
2456
|
+
this.updatedEdges = [];
|
|
2457
|
+
}
|
|
2458
|
+
describe(desc) {
|
|
2459
|
+
this.desc = desc;
|
|
2460
|
+
}
|
|
2461
|
+
addNode(node) {
|
|
2462
|
+
this.addedNodes.push(node);
|
|
2463
|
+
}
|
|
2464
|
+
deleteNode(node) {
|
|
2465
|
+
this.removedNodes.push(node);
|
|
2466
|
+
}
|
|
2467
|
+
updateNode(node) {
|
|
2468
|
+
this.updatedNodes.push(node);
|
|
2469
|
+
}
|
|
2470
|
+
addEdge(edge) {
|
|
2471
|
+
this.addedEdges.push(edge);
|
|
2472
|
+
}
|
|
2473
|
+
deleteEdge(edge) {
|
|
2474
|
+
this.removedEdges.push(edge);
|
|
2475
|
+
}
|
|
2476
|
+
updateEdge(edge) {
|
|
2477
|
+
this.updatedEdges.push(edge);
|
|
2478
|
+
}
|
|
2479
|
+
};
|
|
2480
|
+
async function graph(args = {}) {
|
|
2481
|
+
const { nodes = [], edges = [], ...options } = args;
|
|
2482
|
+
const api = new API(options);
|
|
2483
|
+
if (nodes.length > 0 || edges.length > 0) {
|
|
2484
|
+
await api.update((update) => {
|
|
2485
|
+
for (const node of nodes)
|
|
2486
|
+
update.addNode(node);
|
|
2487
|
+
for (const edge of edges)
|
|
2488
|
+
update.addEdge(edge);
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
return api;
|
|
2492
|
+
}
|
|
2493
|
+
var index_default = graph;
|
|
1610
2494
|
export {
|
|
1611
|
-
|
|
2495
|
+
index_default as default,
|
|
2496
|
+
graph
|
|
1612
2497
|
};
|