@3plate/graph-core 0.1.0 → 0.1.4
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 +2717 -1352
- package/dist/index.d.cts +193 -0
- package/dist/index.d.ts +186 -44
- package/dist/index.js +2710 -1346
- package/package.json +10 -3
- package/dist/index.d.mts +0 -51
- package/dist/index.mjs +0 -323
package/dist/index.js
CHANGED
|
@@ -1,1034 +1,779 @@
|
|
|
1
|
-
// src/graph.
|
|
2
|
-
import { Map as IMap, List as IList, Set as
|
|
1
|
+
// src/graph/graph.ts
|
|
2
|
+
import { Map as IMap, List as IList, Set as ISet6 } from "immutable";
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
// src/graph/node.ts
|
|
5
|
+
import { Record, Set as ISet } from "immutable";
|
|
6
|
+
var defNodeData = {
|
|
7
|
+
id: "",
|
|
8
|
+
data: void 0,
|
|
9
|
+
version: 0,
|
|
10
|
+
title: void 0,
|
|
11
|
+
text: void 0,
|
|
12
|
+
type: void 0,
|
|
13
|
+
render: void 0,
|
|
14
|
+
ports: { in: null, out: null },
|
|
15
|
+
aligned: {},
|
|
16
|
+
edges: { in: ISet(), out: ISet() },
|
|
17
|
+
segs: { in: ISet(), out: ISet() },
|
|
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
|
|
20
27
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
switch (info.level) {
|
|
40
|
-
case "error":
|
|
41
|
-
console.error(message);
|
|
42
|
-
break;
|
|
43
|
-
case "warn":
|
|
44
|
-
console.warn(message);
|
|
45
|
-
break;
|
|
46
|
-
case "info":
|
|
47
|
-
console.info(message);
|
|
48
|
-
break;
|
|
49
|
-
default:
|
|
50
|
-
console.log(message);
|
|
51
|
-
}
|
|
52
|
-
callback();
|
|
28
|
+
var Node = class _Node extends Record(defNodeData) {
|
|
29
|
+
static dummyPrefix = "d:";
|
|
30
|
+
// get edgeId(): 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(): EdgeId[] {
|
|
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
|
+
get key() {
|
|
45
|
+
return this.isDummy ? this.id : _Node.key(this);
|
|
53
46
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
colorize(),
|
|
65
|
-
align(),
|
|
66
|
-
printf((info) => `${info.level}: ${info.module ?? "core"}: ${info.message}`)
|
|
67
|
-
);
|
|
68
|
-
transports = process.env.VITEST ? [new VitestTransport()] : [new winston.transports.Console()];
|
|
69
|
-
break;
|
|
70
|
-
default:
|
|
71
|
-
format = combine(
|
|
72
|
-
timestamp({
|
|
73
|
-
format: "YYYY-MM-DD hh:mm:ss.SSS A"
|
|
74
|
-
}),
|
|
75
|
-
json()
|
|
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 = {
|
|
102
|
-
...props,
|
|
47
|
+
static key(node) {
|
|
48
|
+
return `${node.id}:${node.version}`;
|
|
49
|
+
}
|
|
50
|
+
static isDummyId(nodeId) {
|
|
51
|
+
return nodeId.startsWith(_Node.dummyPrefix);
|
|
52
|
+
}
|
|
53
|
+
static addNormal(g, data) {
|
|
54
|
+
const layer = g.layerAt(0);
|
|
55
|
+
const node = new _Node({
|
|
56
|
+
...data,
|
|
103
57
|
edges: { in: ISet(), out: ISet() },
|
|
104
58
|
segs: { in: ISet(), out: ISet() },
|
|
105
|
-
aligned: {}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
59
|
+
aligned: {},
|
|
60
|
+
edgeIds: [],
|
|
61
|
+
layerId: layer.id
|
|
62
|
+
});
|
|
63
|
+
layer.addNode(g, node.id);
|
|
64
|
+
g.nodes.set(node.id, node);
|
|
65
|
+
g.dirtyNodes.add(node.id);
|
|
112
66
|
return node;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return this._addNode({
|
|
123
|
-
...props,
|
|
124
|
-
id: this._newDummyId(),
|
|
67
|
+
}
|
|
68
|
+
static addDummy(g, data) {
|
|
69
|
+
const layer = g.getLayer(data.layerId);
|
|
70
|
+
const node = new _Node({
|
|
71
|
+
...data,
|
|
72
|
+
id: `${_Node.dummyPrefix}${g.nextDummyId++}`,
|
|
73
|
+
edges: { in: ISet(), out: ISet() },
|
|
74
|
+
segs: { in: ISet(), out: ISet() },
|
|
75
|
+
aligned: {},
|
|
125
76
|
isDummy: true,
|
|
126
77
|
dims: {
|
|
127
|
-
|
|
128
|
-
|
|
78
|
+
w: g.options.dummyNodeSize,
|
|
79
|
+
h: g.options.dummyNodeSize
|
|
129
80
|
}
|
|
130
81
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
82
|
+
layer.addNode(g, node.id);
|
|
83
|
+
g.nodes.set(node.id, node);
|
|
84
|
+
g.dirtyNodes.add(node.id);
|
|
85
|
+
return node;
|
|
86
|
+
}
|
|
87
|
+
static del(g, node) {
|
|
88
|
+
return g.getNode(node.id).delSelf(g);
|
|
89
|
+
}
|
|
90
|
+
static update(g, data) {
|
|
91
|
+
return g.getNode(data.id).mut(g).merge(data);
|
|
92
|
+
}
|
|
93
|
+
mut(g) {
|
|
94
|
+
if (this.mutable) return this;
|
|
95
|
+
return g.mutateNode(this);
|
|
96
|
+
}
|
|
97
|
+
final() {
|
|
98
|
+
if (!this.mutable) return this;
|
|
99
|
+
return this.merge({
|
|
100
|
+
edges: { in: this.edges.in.asImmutable(), out: this.edges.out.asImmutable() },
|
|
101
|
+
segs: { in: this.segs.in.asImmutable(), out: this.segs.out.asImmutable() },
|
|
102
|
+
mutable: false
|
|
103
|
+
}).asImmutable();
|
|
104
|
+
}
|
|
105
|
+
dirty(g) {
|
|
106
|
+
g.dirtyNodes.add(this.id);
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
cur(g) {
|
|
110
|
+
return g.getNode(this.id);
|
|
111
|
+
}
|
|
112
|
+
isUnlinked() {
|
|
113
|
+
return this.edges.in.size == 0 && this.edges.out.size == 0 && this.segs.in.size == 0 && this.segs.out.size == 0;
|
|
114
|
+
}
|
|
115
|
+
hasPorts() {
|
|
116
|
+
return !!this.ports?.in?.length || !!this.ports?.out?.length;
|
|
117
|
+
}
|
|
118
|
+
layerIndex(g) {
|
|
119
|
+
return this.getLayer(g).index;
|
|
120
|
+
}
|
|
121
|
+
getLayer(g) {
|
|
122
|
+
return g.getLayer(this.layerId);
|
|
123
|
+
}
|
|
124
|
+
setIndex(g, index) {
|
|
125
|
+
if (this.index == index) return this;
|
|
126
|
+
return this.mut(g).set("index", index);
|
|
127
|
+
}
|
|
128
|
+
setLayerPos(g, lpos) {
|
|
129
|
+
if (this.lpos == lpos) return this;
|
|
130
|
+
return this.mut(g).set("lpos", lpos);
|
|
131
|
+
}
|
|
132
|
+
setLayer(g, layerId) {
|
|
133
|
+
if (this.layerId == layerId) return this;
|
|
134
|
+
return this.mut(g).set("layerId", layerId);
|
|
135
|
+
}
|
|
136
|
+
setPos(g, pos) {
|
|
137
|
+
if (!this.pos || this.pos.x != pos.x || this.pos.y != pos.y)
|
|
138
|
+
return this.mut(g).set("pos", pos);
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
moveToLayer(g, layer) {
|
|
142
|
+
this.getLayer(g).delNode(g, this.id);
|
|
143
|
+
layer.addNode(g, this.id);
|
|
144
|
+
return this.setLayer(g, layer.id);
|
|
145
|
+
}
|
|
146
|
+
moveToLayerIndex(g, index) {
|
|
147
|
+
return this.moveToLayer(g, g.layerAt(index));
|
|
148
|
+
}
|
|
149
|
+
setAligned(g, dir, nodeId) {
|
|
150
|
+
if (this.aligned[dir] === nodeId) return this;
|
|
151
|
+
return this.mut(g).set("aligned", { ...this.aligned, [dir]: nodeId });
|
|
152
|
+
}
|
|
153
|
+
addRel(g, type, dir, relId) {
|
|
154
|
+
const sets = this.get(type);
|
|
155
|
+
const set = sets[dir];
|
|
156
|
+
if (set.has(relId)) return this;
|
|
157
|
+
return this.mut(g).set(type, { ...sets, [dir]: set.asMutable().add(relId) });
|
|
158
|
+
}
|
|
159
|
+
delRel(g, type, dir, relId) {
|
|
160
|
+
let sets = this.get(type);
|
|
161
|
+
const set = sets[dir];
|
|
162
|
+
if (!set.has(relId)) return this;
|
|
163
|
+
sets = { ...sets, [dir]: set.asMutable().remove(relId) };
|
|
164
|
+
const node = this.mut(g).set(type, sets);
|
|
165
|
+
if (node.isDummy && node.isUnlinked())
|
|
166
|
+
return node.delSelf(g);
|
|
167
|
+
return node;
|
|
168
|
+
}
|
|
169
|
+
delSelf(g) {
|
|
170
|
+
this.getLayer(g).delNode(g, this.id);
|
|
171
|
+
for (const rel of this.rels(g))
|
|
172
|
+
rel.delSelf(g);
|
|
173
|
+
g.nodes.delete(this.id);
|
|
174
|
+
g.dirtyNodes.delete(this.id);
|
|
175
|
+
g.delNodes.add(this.id);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
addInEdge(g, edgeId) {
|
|
179
|
+
return this.addRel(g, "edges", "in", edgeId);
|
|
180
|
+
}
|
|
181
|
+
addOutEdge(g, edgeId) {
|
|
182
|
+
return this.addRel(g, "edges", "out", edgeId);
|
|
183
|
+
}
|
|
184
|
+
addInSeg(g, segId) {
|
|
185
|
+
return this.addRel(g, "segs", "in", segId);
|
|
186
|
+
}
|
|
187
|
+
addOutSeg(g, segId) {
|
|
188
|
+
return this.addRel(g, "segs", "out", segId);
|
|
189
|
+
}
|
|
190
|
+
delInEdge(g, edgeId) {
|
|
191
|
+
return this.delRel(g, "edges", "in", edgeId);
|
|
192
|
+
}
|
|
193
|
+
delOutEdge(g, edgeId) {
|
|
194
|
+
return this.delRel(g, "edges", "out", edgeId);
|
|
195
|
+
}
|
|
196
|
+
delInSeg(g, segId) {
|
|
197
|
+
return this.delRel(g, "segs", "in", segId);
|
|
198
|
+
}
|
|
199
|
+
delOutSeg(g, segId) {
|
|
200
|
+
return this.delRel(g, "segs", "out", segId);
|
|
201
|
+
}
|
|
202
|
+
*relIds(type = "both", dir = "both") {
|
|
203
|
+
const types = type == "both" ? ["edges", "segs"] : [type];
|
|
204
|
+
const dirs = dir == "both" ? ["in", "out"] : [dir];
|
|
205
|
+
for (const type2 of types)
|
|
206
|
+
for (const dir2 of dirs)
|
|
207
|
+
yield* this.get(type2)[dir2];
|
|
208
|
+
}
|
|
209
|
+
*rels(g, type = "both", dir = "both") {
|
|
210
|
+
for (const relId of this.relIds(type, dir))
|
|
211
|
+
yield g.getRel(relId);
|
|
212
|
+
}
|
|
213
|
+
*adjIds(g, type = "both", dir = "both") {
|
|
214
|
+
const dirs = dir == "both" ? ["in", "out"] : [dir];
|
|
215
|
+
for (const dir2 of dirs) {
|
|
216
|
+
const side = dir2 == "in" ? "source" : "target";
|
|
217
|
+
for (const rel of this.rels(g, type, dir2))
|
|
218
|
+
yield rel[side].id;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
*adjs(g, type = "both", dir = "both") {
|
|
222
|
+
for (const nodeId of this.adjIds(g, type, dir))
|
|
223
|
+
yield g.getNode(nodeId);
|
|
224
|
+
}
|
|
225
|
+
*inEdgeIds() {
|
|
226
|
+
yield* this.relIds("edges", "in");
|
|
227
|
+
}
|
|
228
|
+
*outEdgeIds() {
|
|
229
|
+
yield* this.relIds("edges", "out");
|
|
230
|
+
}
|
|
231
|
+
*inSegIds() {
|
|
232
|
+
yield* this.relIds("segs", "in");
|
|
233
|
+
}
|
|
234
|
+
*outSegIds() {
|
|
235
|
+
yield* this.relIds("segs", "out");
|
|
236
|
+
}
|
|
237
|
+
*inEdges(g) {
|
|
238
|
+
yield* this.rels(g, "edges", "in");
|
|
239
|
+
}
|
|
240
|
+
*outEdges(g) {
|
|
241
|
+
yield* this.rels(g, "edges", "out");
|
|
242
|
+
}
|
|
243
|
+
*inSegs(g) {
|
|
244
|
+
yield* this.rels(g, "segs", "in");
|
|
245
|
+
}
|
|
246
|
+
*outSegs(g) {
|
|
247
|
+
yield* this.rels(g, "segs", "out");
|
|
248
|
+
}
|
|
249
|
+
*inNodeIds(g) {
|
|
250
|
+
yield* this.adjIds(g, "edges", "in");
|
|
251
|
+
}
|
|
252
|
+
*outNodeIds(g) {
|
|
253
|
+
yield* this.adjIds(g, "edges", "out");
|
|
254
|
+
}
|
|
255
|
+
*inNodes(g) {
|
|
256
|
+
yield* this.adjs(g, "edges", "in");
|
|
257
|
+
}
|
|
258
|
+
*outNodes(g) {
|
|
259
|
+
yield* this.adjs(g, "edges", "out");
|
|
181
260
|
}
|
|
182
261
|
};
|
|
183
262
|
|
|
184
|
-
// src/graph
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
263
|
+
// src/graph/edge.ts
|
|
264
|
+
import { Record as Record2 } from "immutable";
|
|
265
|
+
|
|
266
|
+
// src/log.ts
|
|
267
|
+
var levels = {
|
|
268
|
+
error: 0,
|
|
269
|
+
warn: 1,
|
|
270
|
+
info: 2,
|
|
271
|
+
debug: 3
|
|
272
|
+
};
|
|
273
|
+
var currentLevel = "debug";
|
|
274
|
+
function shouldLog(level) {
|
|
275
|
+
return levels[level] <= levels[currentLevel];
|
|
276
|
+
}
|
|
277
|
+
function logger(module) {
|
|
278
|
+
return {
|
|
279
|
+
error: (msg, ...args) => shouldLog("error") && console.error(`[${module}] ${msg}`, ...args),
|
|
280
|
+
warn: (msg, ...args) => shouldLog("warn") && console.warn(`[${module}] ${msg}`, ...args),
|
|
281
|
+
info: (msg, ...args) => shouldLog("info") && console.info(`[${module}] ${msg}`, ...args),
|
|
282
|
+
debug: (msg, ...args) => shouldLog("debug") && console.debug(`[${module}] ${msg}`, ...args)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
var log = logger("core");
|
|
286
|
+
|
|
287
|
+
// src/graph/edge.ts
|
|
288
|
+
var log2 = logger("edge");
|
|
289
|
+
var defEdgeData = {
|
|
290
|
+
id: "",
|
|
291
|
+
data: null,
|
|
292
|
+
label: void 0,
|
|
293
|
+
source: { id: "" },
|
|
294
|
+
target: { id: "" },
|
|
295
|
+
type: void 0,
|
|
296
|
+
style: void 0,
|
|
297
|
+
mutable: false,
|
|
298
|
+
segIds: []
|
|
299
|
+
};
|
|
300
|
+
var Edge = class _Edge extends Record2(defEdgeData) {
|
|
301
|
+
static prefix = "e:";
|
|
302
|
+
mut(g) {
|
|
303
|
+
if (this.mutable) return this;
|
|
304
|
+
return g.mutateEdge(this);
|
|
305
|
+
}
|
|
306
|
+
final() {
|
|
307
|
+
if (!this.mutable) return this;
|
|
308
|
+
return this.merge({
|
|
309
|
+
mutable: false
|
|
310
|
+
}).asImmutable();
|
|
311
|
+
}
|
|
312
|
+
link(g) {
|
|
313
|
+
this.sourceNode(g).addOutEdge(g, this.id);
|
|
314
|
+
this.targetNode(g).addInEdge(g, this.id);
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
unlink(g) {
|
|
318
|
+
this.sourceNode(g).delOutEdge(g, this.id);
|
|
319
|
+
this.targetNode(g).delInEdge(g, this.id);
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
delSelf(g) {
|
|
323
|
+
for (const seg of this.segs(g))
|
|
324
|
+
seg.delEdgeId(g, this.id);
|
|
325
|
+
this.unlink(g);
|
|
326
|
+
g.edges.delete(this.id);
|
|
327
|
+
g.dirtyEdges.delete(this.id);
|
|
328
|
+
g.delEdges.add(this.id);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
delSegId(g, segId) {
|
|
332
|
+
return this.setSegIds(g, this.segIds.filter((id) => id != segId));
|
|
333
|
+
}
|
|
334
|
+
replaceSegId(g, oldId, newId) {
|
|
335
|
+
return this.setSegIds(g, this.segIds.map((id) => id == oldId ? newId : id));
|
|
336
|
+
}
|
|
337
|
+
setSegIds(g, segIds) {
|
|
338
|
+
if (segIds.join(",") == this.segIds.join(",")) return this;
|
|
339
|
+
return this.mut(g).set("segIds", segIds);
|
|
340
|
+
}
|
|
341
|
+
*segs(g) {
|
|
342
|
+
for (const segId of this.segIds)
|
|
343
|
+
yield g.getSeg(segId);
|
|
344
|
+
}
|
|
345
|
+
node(g, side) {
|
|
346
|
+
return g.getNode(this[side].id);
|
|
347
|
+
}
|
|
348
|
+
sourceNode(g) {
|
|
349
|
+
return this.node(g, "source");
|
|
350
|
+
}
|
|
351
|
+
targetNode(g) {
|
|
352
|
+
return this.node(g, "target");
|
|
353
|
+
}
|
|
354
|
+
get str() {
|
|
355
|
+
return _Edge.str(this);
|
|
356
|
+
}
|
|
357
|
+
static str(edge) {
|
|
358
|
+
let source = edge.source?.id;
|
|
359
|
+
if (!source) throw new Error("edge source is undefined");
|
|
360
|
+
if (edge.source?.port)
|
|
361
|
+
source = `${source} (port ${edge.source.port})`;
|
|
362
|
+
let target = edge.target?.id;
|
|
363
|
+
if (!target) throw new Error("edge target is undefined");
|
|
364
|
+
if (edge.target?.port)
|
|
365
|
+
target = `${target} (port ${edge.target.port})`;
|
|
366
|
+
let str = `edge from ${source} to ${target}`;
|
|
367
|
+
if (edge.type) str += ` of type ${edge.type}`;
|
|
368
|
+
return str;
|
|
369
|
+
}
|
|
370
|
+
static key(edge, prefix = _Edge.prefix, side = "both") {
|
|
214
371
|
let source = "", target = "";
|
|
215
372
|
if (side == "source" || side == "both") {
|
|
216
|
-
source
|
|
217
|
-
|
|
373
|
+
if (!edge.source?.id) throw new Error("edge source is undefined");
|
|
374
|
+
source = edge.source.id;
|
|
375
|
+
if (edge.source?.port)
|
|
376
|
+
source = `${source}.${edge.source.port}`;
|
|
377
|
+
const marker = edge.source?.marker ?? edge.style?.marker?.source;
|
|
378
|
+
if (marker && marker != "none") source += `[${marker}]`;
|
|
218
379
|
source += "-";
|
|
219
380
|
}
|
|
220
381
|
if (side == "target" || side == "both") {
|
|
221
|
-
target
|
|
222
|
-
|
|
382
|
+
if (!edge.target?.id) throw new Error("edge target is undefined");
|
|
383
|
+
target = edge.target.id;
|
|
384
|
+
if (edge.target.port)
|
|
385
|
+
target = `${target}.${edge.target.port}`;
|
|
386
|
+
target = "-" + target;
|
|
387
|
+
const marker = edge.target?.marker ?? edge.style?.marker?.target ?? "arrow";
|
|
388
|
+
if (marker && marker != "none") target += `[${marker}]`;
|
|
223
389
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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 = {
|
|
360
|
-
...props,
|
|
361
|
-
id: this._edgeId(props),
|
|
362
|
-
segs: []
|
|
363
|
-
};
|
|
364
|
-
this.edges.set(edge.id, edge);
|
|
365
|
-
this._linkEdge(edge);
|
|
390
|
+
const type = edge.type || "";
|
|
391
|
+
return `${prefix}${source}${type}${target}`;
|
|
392
|
+
}
|
|
393
|
+
static add(g, data) {
|
|
394
|
+
const edge = new _Edge({
|
|
395
|
+
...data,
|
|
396
|
+
segIds: []
|
|
397
|
+
});
|
|
398
|
+
edge.link(g);
|
|
399
|
+
g.edges.set(edge.id, edge);
|
|
400
|
+
g.dirtyEdges.add(edge.id);
|
|
366
401
|
return edge;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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);
|
|
402
|
+
}
|
|
403
|
+
static del(g, data) {
|
|
404
|
+
return g.getEdge(data.id).delSelf(g);
|
|
405
|
+
}
|
|
406
|
+
static update(g, data) {
|
|
407
|
+
let edge = g.getEdge(data.id);
|
|
408
|
+
let relink = false;
|
|
409
|
+
if (data.source.id !== edge.source.id || data.target.id !== edge.target.id || data.source.port !== edge.source.port || data.target.port !== edge.target.port || data.type !== edge.type) {
|
|
410
|
+
for (const seg of edge.segs(g))
|
|
411
|
+
seg.delEdgeId(g, edge.id);
|
|
412
|
+
edge.unlink(g);
|
|
413
|
+
relink = true;
|
|
415
414
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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 });
|
|
415
|
+
edge = edge.mut(g).merge(data);
|
|
416
|
+
if (relink)
|
|
417
|
+
edge.link(g);
|
|
418
|
+
return edge;
|
|
523
419
|
}
|
|
524
420
|
};
|
|
525
421
|
|
|
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
|
-
this.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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;
|
|
422
|
+
// src/graph/seg.ts
|
|
423
|
+
import { Record as Record3, Set as ISet2 } from "immutable";
|
|
424
|
+
var defSegData = {
|
|
425
|
+
id: "",
|
|
426
|
+
source: { id: "" },
|
|
427
|
+
target: { id: "" },
|
|
428
|
+
type: void 0,
|
|
429
|
+
style: void 0,
|
|
430
|
+
edgeIds: ISet2(),
|
|
431
|
+
trackPos: void 0,
|
|
432
|
+
svg: void 0,
|
|
433
|
+
mutable: false
|
|
434
|
+
};
|
|
435
|
+
var Seg = class _Seg extends Record3(defSegData) {
|
|
436
|
+
static prefix = "s:";
|
|
437
|
+
mut(g) {
|
|
438
|
+
if (this.mutable) return this;
|
|
439
|
+
return g.mutateSeg(this);
|
|
440
|
+
}
|
|
441
|
+
final() {
|
|
442
|
+
if (!this.mutable) return this;
|
|
443
|
+
return this.merge({
|
|
444
|
+
edgeIds: this.edgeIds.asImmutable(),
|
|
445
|
+
mutable: false
|
|
446
|
+
}).asImmutable();
|
|
447
|
+
}
|
|
448
|
+
get p1() {
|
|
449
|
+
return this.source.pos;
|
|
450
|
+
}
|
|
451
|
+
get p2() {
|
|
452
|
+
return this.target.pos;
|
|
453
|
+
}
|
|
454
|
+
anySameEnd(other) {
|
|
455
|
+
return this.sameEnd(other, "source") || this.sameEnd(other, "target");
|
|
456
|
+
}
|
|
457
|
+
sameEnd(other, side) {
|
|
458
|
+
const mine = this[side];
|
|
459
|
+
const yours = other[side];
|
|
460
|
+
return mine.id === yours.id && mine.port === yours.port && mine.marker === yours.marker;
|
|
461
|
+
}
|
|
462
|
+
setPos(g, source, target) {
|
|
463
|
+
return this.mut(g).merge({
|
|
464
|
+
source: { ...this.source, pos: source },
|
|
465
|
+
target: { ...this.target, pos: target }
|
|
593
466
|
});
|
|
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
|
-
}
|
|
467
|
+
}
|
|
468
|
+
setTrackPos(g, trackPos) {
|
|
469
|
+
if (this.trackPos == trackPos) return this;
|
|
470
|
+
return this.mut(g).set("trackPos", trackPos);
|
|
471
|
+
}
|
|
472
|
+
setSVG(g, svg) {
|
|
473
|
+
if (this.svg == svg) return this;
|
|
474
|
+
return this.mut(g).set("svg", svg);
|
|
475
|
+
}
|
|
476
|
+
link(g) {
|
|
477
|
+
this.sourceNode(g).addOutSeg(g, this.id);
|
|
478
|
+
this.targetNode(g).addInSeg(g, this.id);
|
|
479
|
+
return this;
|
|
480
|
+
}
|
|
481
|
+
unlink(g) {
|
|
482
|
+
this.sourceNode(g).delOutSeg(g, this.id);
|
|
483
|
+
this.targetNode(g).delInSeg(g, this.id);
|
|
484
|
+
return this;
|
|
485
|
+
}
|
|
486
|
+
delSelf(g) {
|
|
487
|
+
this.unlink(g);
|
|
488
|
+
g.segs.delete(this.id);
|
|
489
|
+
g.dirtySegs.delete(this.id);
|
|
490
|
+
g.delSegs.add(this.id);
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
*edges(g) {
|
|
494
|
+
for (const edgeId of this.edgeIds)
|
|
495
|
+
yield g.getEdge(edgeId);
|
|
496
|
+
}
|
|
497
|
+
node(g, side) {
|
|
498
|
+
return g.getNode(this[side].id);
|
|
499
|
+
}
|
|
500
|
+
sourceNode(g) {
|
|
501
|
+
return this.node(g, "source");
|
|
502
|
+
}
|
|
503
|
+
targetNode(g) {
|
|
504
|
+
return this.node(g, "target");
|
|
505
|
+
}
|
|
506
|
+
addEdgeId(g, edgeId) {
|
|
507
|
+
if (this.edgeIds.has(edgeId)) return this;
|
|
508
|
+
return this.mut(g).set("edgeIds", this.edgeIds.asMutable().add(edgeId));
|
|
509
|
+
}
|
|
510
|
+
delEdgeId(g, edgeId) {
|
|
511
|
+
if (!this.edgeIds.has(edgeId)) return this;
|
|
512
|
+
if (this.edgeIds.size == 1) {
|
|
513
|
+
this.delSelf(g);
|
|
514
|
+
return null;
|
|
646
515
|
}
|
|
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()
|
|
516
|
+
return this.mut(g).set("edgeIds", this.edgeIds.asMutable().remove(edgeId));
|
|
517
|
+
}
|
|
518
|
+
static add(g, data) {
|
|
519
|
+
const seg = new _Seg({
|
|
520
|
+
...data,
|
|
521
|
+
id: Edge.key(data, _Seg.prefix)
|
|
691
522
|
});
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
* Check if a layer is empty
|
|
697
|
-
*
|
|
698
|
-
* @param {string} layerId - Layer ID
|
|
699
|
-
* @returns {boolean} True if the layer is empty
|
|
700
|
-
*/
|
|
701
|
-
_layerIsEmpty(layerId) {
|
|
702
|
-
return this.layers.get(layerId).nodes.size == 0;
|
|
703
|
-
},
|
|
704
|
-
/**
|
|
705
|
-
* Delete a layer and renumber the remaining layers
|
|
706
|
-
*
|
|
707
|
-
* @param {string} layerId - Layer ID
|
|
708
|
-
*/
|
|
709
|
-
_deleteLayer(layerId) {
|
|
710
|
-
const layer = this.getLayer(layerId);
|
|
711
|
-
const index = layer.index;
|
|
712
|
-
log4.debug(`deleting layer ${layerId} at index ${index} / ${this.layerList.size}`);
|
|
713
|
-
this.layerList.remove(index);
|
|
714
|
-
this.layers.delete(layerId);
|
|
715
|
-
for (let i = index; i < this.layerList.size; i++) {
|
|
716
|
-
const id = this.layerList.get(i);
|
|
717
|
-
this.layers.set(id, {
|
|
718
|
-
...this.layers.get(id),
|
|
719
|
-
index: i
|
|
720
|
-
});
|
|
721
|
-
}
|
|
523
|
+
seg.link(g);
|
|
524
|
+
g.segs.set(seg.id, seg);
|
|
525
|
+
g.dirtySegs.add(seg.id);
|
|
526
|
+
return seg;
|
|
722
527
|
}
|
|
723
528
|
};
|
|
724
529
|
|
|
725
|
-
// src/
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
530
|
+
// src/graph/layer.ts
|
|
531
|
+
import { Record as Record4, Set as ISet3 } from "immutable";
|
|
532
|
+
var log3 = logger("layer");
|
|
533
|
+
var defLayerData = {
|
|
534
|
+
id: "",
|
|
535
|
+
index: 0,
|
|
536
|
+
nodeIds: ISet3(),
|
|
537
|
+
sorted: [],
|
|
538
|
+
tracks: [],
|
|
539
|
+
size: 0,
|
|
540
|
+
pos: 0,
|
|
541
|
+
isSorted: false,
|
|
542
|
+
mutable: false
|
|
543
|
+
};
|
|
544
|
+
var Layer = class extends Record4(defLayerData) {
|
|
545
|
+
static prefix = "l:";
|
|
546
|
+
mut(g) {
|
|
547
|
+
if (this.mutable) return this;
|
|
548
|
+
return g.mutateLayer(this);
|
|
734
549
|
}
|
|
735
|
-
|
|
736
|
-
this.
|
|
550
|
+
final() {
|
|
551
|
+
if (!this.mutable) return this;
|
|
552
|
+
return this.merge({
|
|
553
|
+
nodeIds: this.nodeIds.asImmutable(),
|
|
554
|
+
mutable: false
|
|
555
|
+
}).asImmutable();
|
|
737
556
|
}
|
|
738
|
-
|
|
739
|
-
|
|
557
|
+
get size() {
|
|
558
|
+
return this.nodeIds.size;
|
|
740
559
|
}
|
|
741
|
-
|
|
742
|
-
this.
|
|
560
|
+
*nodes(g) {
|
|
561
|
+
for (const nodeId of this.nodeIds.values())
|
|
562
|
+
yield g.getNode(nodeId);
|
|
743
563
|
}
|
|
744
|
-
|
|
745
|
-
|
|
564
|
+
hasSortOrder(order) {
|
|
565
|
+
return order.length == this.sorted.length && this.sorted.every((nodeId, i) => order[i] == nodeId);
|
|
746
566
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
567
|
+
canCrush(g) {
|
|
568
|
+
for (const node of this.nodes(g))
|
|
569
|
+
if (!node.isDummy)
|
|
570
|
+
return false;
|
|
571
|
+
return true;
|
|
752
572
|
}
|
|
753
|
-
|
|
754
|
-
|
|
573
|
+
crush(g) {
|
|
574
|
+
g.layerList.remove(this.index);
|
|
575
|
+
g.layers.delete(this.id);
|
|
576
|
+
g.dirtyLayers.delete(this.id);
|
|
577
|
+
for (let i = this.index; i < g.layerList.size; i++)
|
|
578
|
+
g.getLayer(g.layerList.get(i)).setIndex(g, i);
|
|
579
|
+
for (const node of this.nodes(g))
|
|
580
|
+
if (node.isDummy) node.delSelf(g);
|
|
581
|
+
return null;
|
|
755
582
|
}
|
|
756
|
-
|
|
757
|
-
this.
|
|
583
|
+
setIndex(g, index) {
|
|
584
|
+
if (this.index == index) return this;
|
|
585
|
+
return this.mut(g).set("index", index);
|
|
758
586
|
}
|
|
759
|
-
|
|
760
|
-
|
|
587
|
+
setTracks(g, tracks) {
|
|
588
|
+
if (this.tracks == tracks) return this;
|
|
589
|
+
return this.mut(g).set("tracks", tracks);
|
|
590
|
+
}
|
|
591
|
+
setSize(g, size) {
|
|
592
|
+
if (this.size == size) return this;
|
|
593
|
+
return this.mut(g).set("size", size);
|
|
594
|
+
}
|
|
595
|
+
setPos(g, pos) {
|
|
596
|
+
if (this.pos == pos) return this;
|
|
597
|
+
return this.mut(g).set("pos", pos);
|
|
598
|
+
}
|
|
599
|
+
addNode(g, nodeId) {
|
|
600
|
+
if (this.nodeIds.has(nodeId)) return this;
|
|
601
|
+
return this.mut(g).set("nodeIds", this.nodeIds.asMutable().add(nodeId));
|
|
602
|
+
}
|
|
603
|
+
willCrush(g, nodeId) {
|
|
604
|
+
for (const node of this.nodes(g))
|
|
605
|
+
if (!node.isDummy && node.id != nodeId)
|
|
606
|
+
return false;
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
reindex(g, nodeId) {
|
|
610
|
+
if (!this.isSorted) return void 0;
|
|
611
|
+
const sorted = this.sorted.filter((id) => id != nodeId);
|
|
612
|
+
const idx = this.sorted.findIndex((id) => id == nodeId);
|
|
613
|
+
for (let i = idx; i < sorted.length; i++)
|
|
614
|
+
g.getNode(sorted[i]).setIndex(g, i);
|
|
615
|
+
return sorted;
|
|
616
|
+
}
|
|
617
|
+
delNode(g, nodeId) {
|
|
618
|
+
if (!this.nodeIds.has(nodeId)) return this;
|
|
619
|
+
if (this.willCrush(g, nodeId)) return this.crush(g);
|
|
620
|
+
const nodeIds = this.nodeIds.asMutable().remove(nodeId);
|
|
621
|
+
const sorted = this.reindex(g, nodeId);
|
|
622
|
+
return this.mut(g).merge({ nodeIds, sorted });
|
|
623
|
+
}
|
|
624
|
+
setSorted(g, nodeIds) {
|
|
625
|
+
if (this.hasSortOrder(nodeIds)) return this;
|
|
626
|
+
nodeIds.forEach((nodeId, i) => g.getNode(nodeId).setIndex(g, i));
|
|
627
|
+
return this.mut(g).merge({ sorted: nodeIds, isSorted: true });
|
|
628
|
+
}
|
|
629
|
+
*outEdges(g) {
|
|
630
|
+
for (const node of this.nodes(g))
|
|
631
|
+
yield* node.outEdges(g);
|
|
761
632
|
}
|
|
762
633
|
};
|
|
763
634
|
|
|
764
|
-
// src/graph
|
|
765
|
-
var
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
},
|
|
635
|
+
// src/graph/mutator.ts
|
|
636
|
+
var Mutator = class {
|
|
637
|
+
changes;
|
|
638
|
+
constructor() {
|
|
639
|
+
this.changes = {
|
|
640
|
+
addedNodes: [],
|
|
641
|
+
removedNodes: [],
|
|
642
|
+
updatedNodes: [],
|
|
643
|
+
addedEdges: [],
|
|
644
|
+
removedEdges: [],
|
|
645
|
+
updatedEdges: []
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
describe(description) {
|
|
649
|
+
this.changes.description = description;
|
|
650
|
+
}
|
|
811
651
|
addNode(node) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
},
|
|
652
|
+
this.changes.addedNodes.push(node);
|
|
653
|
+
}
|
|
654
|
+
addNodes(...nodes) {
|
|
655
|
+
nodes.forEach((node) => this.addNode(node));
|
|
656
|
+
}
|
|
831
657
|
removeNode(node) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
658
|
+
this.changes.removedNodes.push(node);
|
|
659
|
+
}
|
|
660
|
+
removeNodes(...nodes) {
|
|
661
|
+
nodes.forEach((node) => this.removeNode(node));
|
|
662
|
+
}
|
|
663
|
+
updateNode(node) {
|
|
664
|
+
this.changes.updatedNodes.push(node);
|
|
665
|
+
}
|
|
666
|
+
updateNodes(...nodes) {
|
|
667
|
+
nodes.forEach((node) => this.updateNode(node));
|
|
668
|
+
}
|
|
669
|
+
addEdge(edge) {
|
|
670
|
+
this.changes.addedEdges.push(edge);
|
|
671
|
+
}
|
|
672
|
+
addEdges(...edges) {
|
|
673
|
+
edges.forEach((edge) => this.addEdge(edge));
|
|
674
|
+
}
|
|
841
675
|
removeEdge(edge) {
|
|
842
|
-
|
|
843
|
-
mutator.removeEdge(edge);
|
|
844
|
-
});
|
|
676
|
+
this.changes.removedEdges.push(edge);
|
|
845
677
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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));
|
|
678
|
+
removeEdges(...edges) {
|
|
679
|
+
edges.forEach((edge) => this.removeEdge(edge));
|
|
680
|
+
}
|
|
681
|
+
updateEdge(edge) {
|
|
682
|
+
this.changes.updatedEdges.push(edge);
|
|
683
|
+
}
|
|
684
|
+
updateEdges(...edges) {
|
|
685
|
+
edges.forEach((edge) => this.updateEdge(edge));
|
|
914
686
|
}
|
|
915
687
|
};
|
|
916
688
|
|
|
917
|
-
// src/graph
|
|
918
|
-
var
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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;
|
|
689
|
+
// src/graph/services/cycles.ts
|
|
690
|
+
var Cycles = class _Cycles {
|
|
691
|
+
static info(g, node) {
|
|
692
|
+
return node.id;
|
|
693
|
+
}
|
|
694
|
+
static checkCycles(g) {
|
|
695
|
+
const totalNodes = g.nodes.size;
|
|
696
|
+
const newStuff = g.changes.addedNodes.length + g.changes.addedEdges.length;
|
|
936
697
|
const changeRatio = newStuff / totalNodes;
|
|
937
698
|
if (changeRatio > 0.2 || totalNodes < 20)
|
|
938
|
-
|
|
699
|
+
_Cycles.checkCyclesFull(g);
|
|
939
700
|
else
|
|
940
|
-
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
* Use a graph traversal algorithm to check for cycles.
|
|
944
|
-
*/
|
|
945
|
-
_checkCyclesFull() {
|
|
701
|
+
_Cycles.checkCyclesIncremental(g);
|
|
702
|
+
}
|
|
703
|
+
static checkCyclesFull(g) {
|
|
946
704
|
const colorMap = /* @__PURE__ */ new Map();
|
|
947
705
|
const parentMap = /* @__PURE__ */ new Map();
|
|
948
|
-
const white = 0, gray = 1, black = 2;
|
|
949
706
|
let start, end;
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
707
|
+
const white = 0, gray = 1, black = 2;
|
|
708
|
+
const visit = (node2) => {
|
|
709
|
+
colorMap.set(node2, gray);
|
|
710
|
+
for (const next of node2.outNodes(g)) {
|
|
711
|
+
switch (colorMap.get(next) ?? white) {
|
|
954
712
|
case gray:
|
|
955
|
-
start =
|
|
956
|
-
end =
|
|
713
|
+
start = next;
|
|
714
|
+
end = node2;
|
|
957
715
|
return true;
|
|
958
716
|
case white:
|
|
959
|
-
parentMap.set(
|
|
960
|
-
if (visit(
|
|
717
|
+
parentMap.set(next, node2);
|
|
718
|
+
if (visit(next)) return true;
|
|
961
719
|
}
|
|
962
720
|
}
|
|
963
|
-
colorMap.set(
|
|
721
|
+
colorMap.set(node2, black);
|
|
964
722
|
return false;
|
|
965
723
|
};
|
|
966
|
-
for (const
|
|
967
|
-
if ((colorMap.get(
|
|
968
|
-
if (visit(
|
|
724
|
+
for (const node2 of g.getNodes())
|
|
725
|
+
if ((colorMap.get(node2) ?? white) == white) {
|
|
726
|
+
if (visit(node2)) break;
|
|
969
727
|
}
|
|
970
|
-
if (!start) return;
|
|
971
|
-
const cycle = [
|
|
972
|
-
let
|
|
973
|
-
while (
|
|
974
|
-
cycle.push(
|
|
975
|
-
|
|
728
|
+
if (!start || !end) return;
|
|
729
|
+
const cycle = [start];
|
|
730
|
+
let node = end;
|
|
731
|
+
while (node != start) {
|
|
732
|
+
cycle.push(node);
|
|
733
|
+
node = parentMap.get(node);
|
|
976
734
|
}
|
|
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);
|
|
735
|
+
_Cycles.throwCycle(g, cycle);
|
|
736
|
+
}
|
|
737
|
+
static checkCyclesIncremental(g) {
|
|
738
|
+
for (const edge of g.changes.addedEdges) {
|
|
739
|
+
const source = g.getNode(edge.source.id);
|
|
740
|
+
const target = g.getNode(edge.target.id);
|
|
741
|
+
const layer1 = source.layerIndex(g);
|
|
742
|
+
const layer2 = target.layerIndex(g);
|
|
993
743
|
if (layer1 < layer2) continue;
|
|
994
|
-
const route =
|
|
744
|
+
const route = _Cycles.findRoute(g, target, source);
|
|
995
745
|
if (!route) continue;
|
|
996
|
-
|
|
997
|
-
cycle.reverse();
|
|
998
|
-
const error = new Error(`Cycle detected: ${cycle.join(" \u2192 ")}`);
|
|
999
|
-
error.cycle = cycle;
|
|
1000
|
-
throw error;
|
|
746
|
+
_Cycles.throwCycle(g, route);
|
|
1001
747
|
}
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
_findRoute(sourceId, targetId) {
|
|
748
|
+
}
|
|
749
|
+
static throwCycle(g, cycle) {
|
|
750
|
+
cycle.push(cycle[0]);
|
|
751
|
+
cycle.reverse();
|
|
752
|
+
const info = cycle.map((node) => _Cycles.info(g, node));
|
|
753
|
+
throw new Error(`Cycle detected: ${info.join(" \u2192 ")}`);
|
|
754
|
+
}
|
|
755
|
+
static findRoute(g, source, target) {
|
|
1011
756
|
const parentMap = /* @__PURE__ */ new Map();
|
|
1012
|
-
const queue = [
|
|
1013
|
-
const visited = /* @__PURE__ */ new Set([
|
|
757
|
+
const queue = [source];
|
|
758
|
+
const visited = /* @__PURE__ */ new Set([source]);
|
|
1014
759
|
while (queue.length > 0) {
|
|
1015
|
-
const
|
|
1016
|
-
if (
|
|
760
|
+
const node = queue.shift();
|
|
761
|
+
if (node == target) {
|
|
1017
762
|
const route = [];
|
|
1018
|
-
let
|
|
1019
|
-
while (
|
|
1020
|
-
route.push(
|
|
1021
|
-
|
|
763
|
+
let currNode = target;
|
|
764
|
+
while (currNode != source) {
|
|
765
|
+
route.push(currNode);
|
|
766
|
+
currNode = parentMap.get(currNode);
|
|
1022
767
|
}
|
|
1023
|
-
route.push(
|
|
768
|
+
route.push(source);
|
|
1024
769
|
route.reverse();
|
|
1025
770
|
return route;
|
|
1026
771
|
}
|
|
1027
|
-
for (const
|
|
1028
|
-
if (!visited.has(
|
|
1029
|
-
visited.add(
|
|
1030
|
-
parentMap.set(
|
|
1031
|
-
queue.push(
|
|
772
|
+
for (const next of node.outNodes(g)) {
|
|
773
|
+
if (!visited.has(next)) {
|
|
774
|
+
visited.add(next);
|
|
775
|
+
parentMap.set(next, node);
|
|
776
|
+
queue.push(next);
|
|
1032
777
|
}
|
|
1033
778
|
}
|
|
1034
779
|
}
|
|
@@ -1036,51 +781,42 @@ var GraphCycle = {
|
|
|
1036
781
|
}
|
|
1037
782
|
};
|
|
1038
783
|
|
|
1039
|
-
// src/graph
|
|
1040
|
-
import { Set as
|
|
1041
|
-
var
|
|
1042
|
-
var
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
*/
|
|
1050
|
-
_updateDummies() {
|
|
1051
|
-
for (const edgeId of this._dirtyEdges) {
|
|
1052
|
-
const edge = this.getEdge(edgeId);
|
|
1053
|
-
const { type } = edge;
|
|
1054
|
-
const sourceLayer = this._nodeLayerIndex(edge.source.id);
|
|
1055
|
-
const targetLayer = this._nodeLayerIndex(edge.target.id);
|
|
784
|
+
// src/graph/services/dummy.ts
|
|
785
|
+
import { Set as ISet4 } from "immutable";
|
|
786
|
+
var log4 = logger("dummy");
|
|
787
|
+
var Dummy = class _Dummy {
|
|
788
|
+
static updateDummies(g) {
|
|
789
|
+
for (const edgeId of g.dirtyEdges) {
|
|
790
|
+
const edge = g.getEdge(edgeId);
|
|
791
|
+
const { type, style } = edge;
|
|
792
|
+
const sourceLayer = edge.sourceNode(g).layerIndex(g);
|
|
793
|
+
const targetLayer = edge.targetNode(g).layerIndex(g);
|
|
1056
794
|
let segIndex = 0;
|
|
1057
795
|
let changed = false;
|
|
1058
796
|
let source = edge.source;
|
|
1059
|
-
const segs = edge.
|
|
797
|
+
const segs = edge.segIds;
|
|
1060
798
|
for (let layerIndex = sourceLayer + 1; layerIndex <= targetLayer; layerIndex++) {
|
|
1061
|
-
const layer =
|
|
799
|
+
const layer = g.layerAt(layerIndex);
|
|
1062
800
|
while (true) {
|
|
1063
801
|
const segId = segs[segIndex];
|
|
1064
|
-
let seg = segId ?
|
|
1065
|
-
const segLayer = seg ?
|
|
802
|
+
let seg = segId ? g.getSeg(segId) : null;
|
|
803
|
+
const segLayer = seg ? seg.targetNode(g).layerIndex(g) : null;
|
|
1066
804
|
if (segIndex == segs.length || segLayer > layerIndex) {
|
|
1067
805
|
let target;
|
|
1068
806
|
if (layerIndex == targetLayer) {
|
|
1069
807
|
target = edge.target;
|
|
1070
808
|
} else {
|
|
1071
|
-
const dummy =
|
|
1072
|
-
edgeId,
|
|
809
|
+
const dummy = Node.addDummy(g, {
|
|
810
|
+
edgeIds: [edgeId],
|
|
1073
811
|
layerId: layer.id
|
|
1074
812
|
});
|
|
1075
|
-
target = {
|
|
813
|
+
target = { id: dummy.id };
|
|
1076
814
|
}
|
|
1077
|
-
seg =
|
|
1078
|
-
log5.debug(`edge ${edgeId}: adding segment ${seg.id} from ${source.id} at layer ${layerIndex - 1} to ${target.id} at layer ${layerIndex}`);
|
|
815
|
+
seg = Seg.add(g, { source, target, type, style, edgeIds: ISet4([edgeId]) });
|
|
1079
816
|
segs.splice(segIndex, 0, seg.id);
|
|
1080
817
|
changed = true;
|
|
1081
818
|
} 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
|
-
this._segDeleteEdge(segId, edgeId);
|
|
819
|
+
seg = seg.delEdgeId(g, edgeId);
|
|
1084
820
|
segs.splice(segIndex, 1);
|
|
1085
821
|
changed = true;
|
|
1086
822
|
continue;
|
|
@@ -1091,68 +827,67 @@ var GraphDummy = {
|
|
|
1091
827
|
}
|
|
1092
828
|
}
|
|
1093
829
|
while (segIndex < segs.length) {
|
|
1094
|
-
|
|
1095
|
-
this._segDeleteEdge(segs[segIndex], edgeId);
|
|
830
|
+
g.getSeg(segs[segIndex]).delEdgeId(g, edgeId);
|
|
1096
831
|
segs.splice(segIndex, 1);
|
|
1097
832
|
changed = true;
|
|
1098
833
|
segIndex++;
|
|
1099
834
|
}
|
|
1100
835
|
if (changed) {
|
|
1101
|
-
|
|
1102
|
-
this.edges.set(edgeId, { ...edge, segs });
|
|
836
|
+
edge.setSegIds(g, segs);
|
|
1103
837
|
}
|
|
1104
838
|
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
for (const side of
|
|
1108
|
-
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
let
|
|
1112
|
-
if (side == "target")
|
|
839
|
+
}
|
|
840
|
+
static mergeDummies(g) {
|
|
841
|
+
for (const side of g.options.mergeOrder)
|
|
842
|
+
_Dummy.mergeScan(g, side);
|
|
843
|
+
}
|
|
844
|
+
static mergeScan(g, side) {
|
|
845
|
+
let layerIds = [...g.layerList].filter((layerId) => g.dirtyLayers.has(layerId));
|
|
846
|
+
if (side == "target") layerIds.reverse();
|
|
1113
847
|
const dir = side == "source" ? "in" : "out";
|
|
1114
848
|
const altSide = side == "source" ? "target" : "source";
|
|
1115
849
|
const altDir = altSide == "source" ? "in" : "out";
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
let layer = this.layers.get(layerId);
|
|
850
|
+
for (const layerId of layerIds) {
|
|
851
|
+
let layer = g.getLayer(layerId);
|
|
1119
852
|
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 =
|
|
853
|
+
for (const nodeId of layer.nodeIds) {
|
|
854
|
+
if (!Node.isDummyId(nodeId)) continue;
|
|
855
|
+
const node = g.getNode(nodeId);
|
|
856
|
+
if (node.isMerged) continue;
|
|
857
|
+
const edge = g.getEdge(node.edgeIds[0]);
|
|
858
|
+
const key = Edge.key(edge, "k:", side);
|
|
1126
859
|
if (!groups.has(key)) groups.set(key, /* @__PURE__ */ new Set());
|
|
1127
860
|
groups.get(key).add(node);
|
|
1128
861
|
}
|
|
1129
862
|
for (const [key, group] of groups) {
|
|
1130
863
|
if (group.size == 1) continue;
|
|
1131
864
|
const edgeIds = [...group].map((node) => node.edgeId);
|
|
1132
|
-
const dummy =
|
|
865
|
+
const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
|
|
1133
866
|
let seg;
|
|
1134
867
|
for (const old of group) {
|
|
1135
|
-
|
|
868
|
+
let edge = g.getEdge(old.edgeIds[0]);
|
|
869
|
+
for (const segId of old.relIds("segs", dir)) {
|
|
1136
870
|
if (!seg) {
|
|
1137
|
-
const example =
|
|
1138
|
-
seg =
|
|
871
|
+
const example = g.getSeg(segId);
|
|
872
|
+
seg = Seg.add(g, {
|
|
1139
873
|
...example,
|
|
1140
|
-
|
|
874
|
+
edgeIds: ISet4([old.edgeId]),
|
|
1141
875
|
[altSide]: { ...example[altSide], id: dummy.id }
|
|
1142
876
|
});
|
|
1143
877
|
}
|
|
1144
|
-
|
|
878
|
+
edge = edge.replaceSegId(g, segId, seg.id);
|
|
1145
879
|
}
|
|
1146
880
|
}
|
|
1147
881
|
for (const old of group) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const
|
|
882
|
+
let edge = g.getEdge(old.edgeIds[0]);
|
|
883
|
+
for (const segId of old.relIds("segs", altDir)) {
|
|
884
|
+
const example = g.getSeg(segId);
|
|
885
|
+
const seg2 = Seg.add(g, {
|
|
1151
886
|
...example,
|
|
1152
|
-
|
|
887
|
+
edgeIds: ISet4([old.edgeId]),
|
|
1153
888
|
[side]: { ...example[side], id: dummy.id }
|
|
1154
889
|
});
|
|
1155
|
-
|
|
890
|
+
edge = edge.replaceSegId(g, segId, seg2.id);
|
|
1156
891
|
}
|
|
1157
892
|
}
|
|
1158
893
|
}
|
|
@@ -1160,209 +895,191 @@ var GraphDummy = {
|
|
|
1160
895
|
}
|
|
1161
896
|
};
|
|
1162
897
|
|
|
1163
|
-
// src/graph
|
|
898
|
+
// src/graph/services/layers.ts
|
|
899
|
+
import { Seq } from "immutable";
|
|
900
|
+
var log5 = logger("layers");
|
|
901
|
+
var Layers = class {
|
|
902
|
+
static updateLayers(g) {
|
|
903
|
+
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);
|
|
904
|
+
const phase2 = new Set(stack);
|
|
905
|
+
const moved = /* @__PURE__ */ new Set();
|
|
906
|
+
while (stack.length > 0) {
|
|
907
|
+
let node = g.getNode(stack.pop());
|
|
908
|
+
const curLayer = node.layerIndex(g);
|
|
909
|
+
let correctLayer = 0;
|
|
910
|
+
const parents = node.inNodes(g);
|
|
911
|
+
for (const parent of parents) {
|
|
912
|
+
const pidx = parent.layerIndex(g);
|
|
913
|
+
if (pidx >= correctLayer) correctLayer = pidx + 1;
|
|
914
|
+
}
|
|
915
|
+
if (curLayer != correctLayer) {
|
|
916
|
+
node = node.moveToLayerIndex(g, correctLayer);
|
|
917
|
+
stack.push(...node.outNodeIds(g));
|
|
918
|
+
moved.add(node.id);
|
|
919
|
+
for (const parent of parents)
|
|
920
|
+
phase2.add(parent.id);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
const byLayer = /* @__PURE__ */ new Map();
|
|
924
|
+
const addParent = (nodeId) => {
|
|
925
|
+
let set;
|
|
926
|
+
const layerId = g.getNode(nodeId).layerId;
|
|
927
|
+
if (!byLayer.has(layerId)) {
|
|
928
|
+
set = /* @__PURE__ */ new Set();
|
|
929
|
+
byLayer.set(layerId, set);
|
|
930
|
+
} else {
|
|
931
|
+
set = byLayer.get(layerId);
|
|
932
|
+
}
|
|
933
|
+
set.add(nodeId);
|
|
934
|
+
};
|
|
935
|
+
for (const id of phase2) addParent(id);
|
|
936
|
+
const layerIds = [...byLayer.keys()].sort(
|
|
937
|
+
(a, b) => g.getLayer(b).index - g.getLayer(a).index
|
|
938
|
+
);
|
|
939
|
+
for (const layerId of layerIds) {
|
|
940
|
+
const curLayer = g.getLayer(layerId).index;
|
|
941
|
+
for (const parentId of byLayer.get(layerId)) {
|
|
942
|
+
let parent = g.getNode(parentId);
|
|
943
|
+
const children = [...parent.outNodes(g)];
|
|
944
|
+
if (children.length == 0) continue;
|
|
945
|
+
const minChild = Seq(children).map((node) => node.layerIndex(g)).min();
|
|
946
|
+
const correctLayer = minChild - 1;
|
|
947
|
+
if (curLayer != correctLayer) {
|
|
948
|
+
moved.add(parentId);
|
|
949
|
+
parent = parent.moveToLayerIndex(g, correctLayer);
|
|
950
|
+
for (const gpId of parent.inNodeIds(g))
|
|
951
|
+
addParent(gpId);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
for (const id of moved)
|
|
956
|
+
for (const edgeId of g.getNode(id).relIds("edges"))
|
|
957
|
+
g.dirtyEdges.add(edgeId);
|
|
958
|
+
}
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
// src/graph/services/layout.ts
|
|
1164
962
|
import { Seq as Seq2 } from "immutable";
|
|
1165
|
-
var log6 = logger("
|
|
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"));
|
|
963
|
+
var log6 = logger("layout");
|
|
964
|
+
var Layout = class _Layout {
|
|
965
|
+
static parentIndex(g, node) {
|
|
966
|
+
const parents = Seq2([...node.adjs(g, "segs", "in")]);
|
|
1175
967
|
const pidx = parents.map((p) => p.index).min();
|
|
1176
|
-
log6.debug(`node ${node.id}: parent index ${pidx}`);
|
|
1177
968
|
if (pidx !== void 0) return pidx;
|
|
1178
969
|
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) {
|
|
970
|
+
}
|
|
971
|
+
static compareNodes(g, aId, bId, pidxs) {
|
|
1188
972
|
const ai = pidxs.get(aId);
|
|
1189
973
|
const bi = pidxs.get(bId);
|
|
1190
974
|
if (ai !== bi) return ai - bi;
|
|
1191
|
-
const a =
|
|
1192
|
-
const b =
|
|
975
|
+
const a = g.getNode(aId);
|
|
976
|
+
const b = g.getNode(bId);
|
|
1193
977
|
if (a.isDummy && !b.isDummy) return -1;
|
|
1194
978
|
if (!a.isDummy && b.isDummy) return 1;
|
|
1195
979
|
if (!a.isDummy) return a.id.localeCompare(b.id);
|
|
1196
980
|
const minA = a.edgeId ?? Seq2(a.edgeIds).min();
|
|
1197
981
|
const minB = b.edgeId ?? Seq2(b.edgeIds).min();
|
|
1198
982
|
return minA.localeCompare(minB);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
* then by comparing their natural ordering. The Y position is assigned based
|
|
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
|
-
}
|
|
983
|
+
}
|
|
984
|
+
static positionNodes(g) {
|
|
985
|
+
for (const nodeId of g.dirtyNodes)
|
|
986
|
+
g.dirtyLayers.add(g.getNode(nodeId).layerId);
|
|
1215
987
|
let adjustNext = false;
|
|
1216
|
-
for (const layerId of
|
|
1217
|
-
if (!adjustNext && !
|
|
988
|
+
for (const layerId of g.layerList) {
|
|
989
|
+
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
1218
990
|
adjustNext = false;
|
|
1219
|
-
|
|
991
|
+
let layer = g.getLayer(layerId);
|
|
1220
992
|
const pidxs = /* @__PURE__ */ new Map();
|
|
1221
|
-
for (const nodeId of layer.
|
|
1222
|
-
pidxs.set(nodeId,
|
|
1223
|
-
const sorted = [...layer.
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
993
|
+
for (const nodeId of layer.nodeIds)
|
|
994
|
+
pidxs.set(nodeId, _Layout.parentIndex(g, g.getNode(nodeId)));
|
|
995
|
+
const sorted = [...layer.nodeIds].sort(
|
|
996
|
+
(aId, bId) => _Layout.compareNodes(g, aId, bId, pidxs)
|
|
997
|
+
);
|
|
998
|
+
if (layer.hasSortOrder(sorted)) continue;
|
|
999
|
+
g.dirtyLayers.add(layerId);
|
|
1000
|
+
layer = layer.setSorted(g, sorted);
|
|
1227
1001
|
adjustNext = true;
|
|
1228
1002
|
let lpos = 0;
|
|
1229
1003
|
for (let i = 0; i < sorted.length; i++) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
lpos += size + this.options.nodeMargin;
|
|
1004
|
+
let node = g.getNode(sorted[i]);
|
|
1005
|
+
node = node.setIndex(g, i).setLayerPos(g, lpos);
|
|
1006
|
+
const size = node.dims?.[g.w] ?? 0;
|
|
1007
|
+
lpos += size + g.options.nodeMargin;
|
|
1235
1008
|
}
|
|
1236
1009
|
}
|
|
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}`]();
|
|
1010
|
+
}
|
|
1011
|
+
static alignAll(g) {
|
|
1012
|
+
if (g.options.layoutSteps) {
|
|
1013
|
+
for (const step of g.options.layoutSteps)
|
|
1014
|
+
_Layout[step](g);
|
|
1250
1015
|
} else {
|
|
1251
|
-
for (let i = 0; i <
|
|
1252
|
-
let anyChanged =
|
|
1016
|
+
for (let i = 0; i < g.options.alignIterations; i++) {
|
|
1017
|
+
let anyChanged = _Layout.alignChildren(g) || _Layout.alignParents(g) || _Layout.compact(g);
|
|
1253
1018
|
if (!anyChanged) break;
|
|
1254
1019
|
}
|
|
1255
1020
|
}
|
|
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];
|
|
1021
|
+
}
|
|
1022
|
+
static alignChildren(g) {
|
|
1023
|
+
return _Layout.alignNodes(g, false, false, false, "in", false);
|
|
1024
|
+
}
|
|
1025
|
+
static alignParents(g) {
|
|
1026
|
+
return _Layout.alignNodes(g, true, true, false, "out", true);
|
|
1027
|
+
}
|
|
1028
|
+
static alignNodes(g, reverseLayers, reverseNodes, reverseMove, dir, conservative) {
|
|
1029
|
+
let layerIds = [...g.layerList];
|
|
1294
1030
|
let anyChanged = false;
|
|
1295
1031
|
if (reverseLayers) layerIds.reverse();
|
|
1296
1032
|
let adjustNext = false;
|
|
1297
1033
|
for (const layerId of layerIds) {
|
|
1298
|
-
if (!adjustNext && !
|
|
1034
|
+
if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
|
|
1299
1035
|
adjustNext = false;
|
|
1036
|
+
let iterations = 0;
|
|
1300
1037
|
while (true) {
|
|
1038
|
+
if (++iterations > 10) {
|
|
1039
|
+
log6.error(`alignNodes: infinite loop detected in layer ${layerId}`);
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1301
1042
|
let changed = false;
|
|
1302
|
-
const nodeIds =
|
|
1043
|
+
const nodeIds = _Layout.sortLayer(g, layerId, reverseNodes);
|
|
1303
1044
|
for (const nodeId of nodeIds) {
|
|
1304
|
-
const {
|
|
1045
|
+
const {
|
|
1046
|
+
isAligned,
|
|
1047
|
+
pos: newPos,
|
|
1048
|
+
nodeId: otherId
|
|
1049
|
+
} = _Layout.nearestNode(g, nodeId, dir, reverseMove, !reverseMove);
|
|
1305
1050
|
if (isAligned || newPos === void 0) continue;
|
|
1306
|
-
if (
|
|
1051
|
+
if (_Layout.shiftNode(g, nodeId, otherId, dir, newPos, reverseMove, conservative)) {
|
|
1307
1052
|
changed = true;
|
|
1308
1053
|
anyChanged = true;
|
|
1309
1054
|
break;
|
|
1310
1055
|
}
|
|
1311
1056
|
}
|
|
1312
1057
|
if (!changed) break;
|
|
1313
|
-
|
|
1058
|
+
g.dirtyLayers.add(layerId);
|
|
1314
1059
|
adjustNext = true;
|
|
1315
1060
|
}
|
|
1316
1061
|
}
|
|
1317
1062
|
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
|
-
}
|
|
1063
|
+
}
|
|
1064
|
+
static sortLayer(g, layerId, reverseNodes) {
|
|
1065
|
+
const layer = g.getLayer(layerId);
|
|
1066
|
+
const sorted = [...layer.nodeIds];
|
|
1067
|
+
sorted.sort((a, b) => g.getNode(a).lpos - g.getNode(b).lpos);
|
|
1068
|
+
layer.setSorted(g, sorted);
|
|
1340
1069
|
if (reverseNodes)
|
|
1341
1070
|
return sorted.toReversed();
|
|
1342
1071
|
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);
|
|
1072
|
+
}
|
|
1073
|
+
static nearestNode(g, nodeId, dir, allowLeft, allowRight) {
|
|
1074
|
+
const node = g.getNode(nodeId);
|
|
1358
1075
|
let minDist = Infinity;
|
|
1359
1076
|
let bestPos, bestNodeId;
|
|
1360
1077
|
const mySide = dir == "in" ? "target" : "source";
|
|
1361
1078
|
const altSide = dir == "in" ? "source" : "target";
|
|
1362
|
-
for (const seg of
|
|
1079
|
+
for (const seg of node.rels(g, "segs", dir)) {
|
|
1363
1080
|
const altId = seg[altSide].id;
|
|
1364
|
-
const myPos =
|
|
1365
|
-
const altPos =
|
|
1081
|
+
const myPos = _Layout.anchorPos(g, seg, mySide)[g.x];
|
|
1082
|
+
const altPos = _Layout.anchorPos(g, seg, altSide)[g.x];
|
|
1366
1083
|
const diff = altPos - myPos;
|
|
1367
1084
|
if (diff == 0) return { nodeId: altId, isAligned: true };
|
|
1368
1085
|
if (diff < 0 && !allowLeft) continue;
|
|
@@ -1375,117 +1092,98 @@ var GraphPos = {
|
|
|
1375
1092
|
}
|
|
1376
1093
|
}
|
|
1377
1094
|
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;
|
|
1095
|
+
}
|
|
1096
|
+
static anchorPos(g, seg, side) {
|
|
1389
1097
|
const nodeId = seg[side].id;
|
|
1390
|
-
const node =
|
|
1391
|
-
let p = { [
|
|
1392
|
-
let w =
|
|
1393
|
-
let h =
|
|
1098
|
+
const node = g.getNode(nodeId);
|
|
1099
|
+
let p = { x: 0, y: 0, [g.x]: node.lpos, ...node.pos || {} };
|
|
1100
|
+
let w = node.dims?.[g.w] ?? 0;
|
|
1101
|
+
let h = node.dims?.[g.h] ?? 0;
|
|
1394
1102
|
if (node.isDummy)
|
|
1395
|
-
return {
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1103
|
+
return {
|
|
1104
|
+
[g.x]: p[g.x] + w / 2,
|
|
1105
|
+
[g.y]: p[g.y] + h / 2
|
|
1106
|
+
};
|
|
1107
|
+
p[g.x] += _Layout.nodePortOffset(g, nodeId, seg, side);
|
|
1108
|
+
if (side == "target" == g.r)
|
|
1109
|
+
p[g.y] += h;
|
|
1399
1110
|
return p;
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
const
|
|
1111
|
+
}
|
|
1112
|
+
static nodePortOffset(g, nodeId, seg, side) {
|
|
1113
|
+
const node = g.getNode(nodeId);
|
|
1114
|
+
const dir = side == "source" ? "out" : "in";
|
|
1115
|
+
const portId = seg[side].port;
|
|
1116
|
+
let min = 0, size = node.dims?.[g.w] ?? 0;
|
|
1117
|
+
if (portId) {
|
|
1118
|
+
const ports = node.ports?.[dir];
|
|
1119
|
+
const port = ports?.find((p) => p.id === portId);
|
|
1120
|
+
if (port?.offset !== void 0) {
|
|
1121
|
+
min = port.offset;
|
|
1122
|
+
size = port.size ?? 0;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
const alt = side == "source" ? "target" : "source";
|
|
1126
|
+
let segs = [];
|
|
1127
|
+
const keyOf = (seg2) => `${seg2.type ?? ""}:${seg2[side].marker ?? ""}`;
|
|
1128
|
+
for (const segId of node.segs[dir])
|
|
1129
|
+
segs.push(g.getSeg(segId));
|
|
1130
|
+
if (portId) segs = segs.filter((s) => s[side].port == portId);
|
|
1131
|
+
const groups = Object.groupBy(segs, (s) => keyOf(s));
|
|
1132
|
+
const posMap = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const [key, segs2] of Object.entries(groups)) {
|
|
1134
|
+
let pos = Infinity;
|
|
1135
|
+
for (const seg2 of segs2) pos = Math.min(pos, seg2.node(g, alt).lpos);
|
|
1136
|
+
posMap.set(key, pos);
|
|
1137
|
+
}
|
|
1138
|
+
const keys = [...posMap.keys()].sort((a, b) => posMap.get(a) - posMap.get(b));
|
|
1139
|
+
const gap = size / (keys.length + 1);
|
|
1140
|
+
const index = keys.indexOf(keyOf(seg));
|
|
1141
|
+
return min + (index + 1) * gap;
|
|
1142
|
+
}
|
|
1143
|
+
static shiftNode(g, nodeId, alignId, dir, lpos, reverseMove, conservative) {
|
|
1144
|
+
const node = g.getNode(nodeId);
|
|
1145
|
+
log6.debug(`shift ${nodeId} (at ${node.lpos}) to ${alignId} (at ${lpos})`);
|
|
1428
1146
|
if (!conservative)
|
|
1429
|
-
|
|
1430
|
-
const space =
|
|
1431
|
-
const nodeWidth =
|
|
1147
|
+
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1148
|
+
const space = g.options.nodeMargin;
|
|
1149
|
+
const nodeWidth = node.dims?.[g.w] ?? 0;
|
|
1432
1150
|
const aMin = lpos - space, aMax = lpos + nodeWidth + space;
|
|
1433
1151
|
repeat:
|
|
1434
|
-
for (const otherId of
|
|
1152
|
+
for (const otherId of node.getLayer(g).nodeIds) {
|
|
1435
1153
|
if (otherId == nodeId) continue;
|
|
1436
|
-
const other =
|
|
1154
|
+
const other = g.getNode(otherId);
|
|
1437
1155
|
const opos = other.lpos;
|
|
1438
|
-
const otherWidth =
|
|
1156
|
+
const otherWidth = other.dims?.[g.w] ?? 0;
|
|
1439
1157
|
const bMin = opos, bMax = opos + otherWidth;
|
|
1440
1158
|
if (aMin < bMax && bMin < aMax) {
|
|
1441
1159
|
if (conservative) return false;
|
|
1442
1160
|
const safePos = reverseMove ? aMin - otherWidth : aMax;
|
|
1443
|
-
|
|
1161
|
+
_Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
|
|
1444
1162
|
continue repeat;
|
|
1445
1163
|
}
|
|
1446
1164
|
}
|
|
1447
1165
|
if (conservative)
|
|
1448
|
-
|
|
1166
|
+
_Layout.markAligned(g, nodeId, alignId, dir, lpos);
|
|
1449
1167
|
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);
|
|
1168
|
+
}
|
|
1169
|
+
static markAligned(g, nodeId, otherId, dir, lpos) {
|
|
1170
|
+
const node = g.getNode(nodeId);
|
|
1461
1171
|
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) {
|
|
1172
|
+
if (node.aligned[dir])
|
|
1173
|
+
g.getNode(node.aligned[dir]).setAligned(g, alt, void 0);
|
|
1174
|
+
if (otherId)
|
|
1175
|
+
g.getNode(otherId).setAligned(g, alt, nodeId);
|
|
1176
|
+
node.setAligned(g, dir, otherId).setLayerPos(g, lpos);
|
|
1177
|
+
}
|
|
1178
|
+
static *aligned(g, nodeId, dir) {
|
|
1481
1179
|
const visit = function* (node2, dir2) {
|
|
1482
1180
|
const otherId = node2.aligned[dir2];
|
|
1483
1181
|
if (!otherId) return;
|
|
1484
|
-
const other =
|
|
1182
|
+
const other = g.getNode(otherId);
|
|
1485
1183
|
yield other;
|
|
1486
|
-
yield* visit
|
|
1487
|
-
}
|
|
1488
|
-
const node =
|
|
1184
|
+
yield* visit(other, dir2);
|
|
1185
|
+
};
|
|
1186
|
+
const node = g.getNode(nodeId);
|
|
1489
1187
|
yield node;
|
|
1490
1188
|
if (dir == "both") {
|
|
1491
1189
|
yield* visit(node, "in");
|
|
@@ -1493,120 +1191,1786 @@ var GraphPos = {
|
|
|
1493
1191
|
} else {
|
|
1494
1192
|
yield* visit(node, dir);
|
|
1495
1193
|
}
|
|
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) {
|
|
1194
|
+
}
|
|
1195
|
+
static leftOf(g, node) {
|
|
1504
1196
|
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);
|
|
1197
|
+
return node.getLayer(g).sorted[node.index - 1];
|
|
1198
|
+
}
|
|
1199
|
+
static rightOf(g, node) {
|
|
1200
|
+
const layer = node.getLayer(g);
|
|
1515
1201
|
if (node.index == layer.sorted.length - 1) return null;
|
|
1516
1202
|
return layer.sorted[node.index + 1];
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
* Compact tries to eliminate empty space between nodes
|
|
1520
|
-
*/
|
|
1521
|
-
_compact() {
|
|
1522
|
-
var _a;
|
|
1203
|
+
}
|
|
1204
|
+
static compact(g) {
|
|
1523
1205
|
let anyChanged = false;
|
|
1524
|
-
for (const layerId of
|
|
1525
|
-
const layer =
|
|
1206
|
+
for (const layerId of g.layerList) {
|
|
1207
|
+
const layer = g.getLayer(layerId);
|
|
1526
1208
|
if (layer.sorted.length < 2) continue;
|
|
1527
1209
|
for (const nodeId of layer.sorted) {
|
|
1528
|
-
const node =
|
|
1210
|
+
const node = g.getNode(nodeId);
|
|
1529
1211
|
if (node.index == 0) continue;
|
|
1530
1212
|
let minGap = Infinity;
|
|
1531
1213
|
const stack = [];
|
|
1532
|
-
for (const right of
|
|
1214
|
+
for (const right of _Layout.aligned(g, nodeId, "both")) {
|
|
1533
1215
|
stack.push(right);
|
|
1534
|
-
const leftId =
|
|
1216
|
+
const leftId = _Layout.leftOf(g, right);
|
|
1535
1217
|
if (!leftId) return;
|
|
1536
|
-
const left =
|
|
1537
|
-
const leftWidth =
|
|
1218
|
+
const left = g.getNode(leftId);
|
|
1219
|
+
const leftWidth = left.dims?.[g.w] ?? 0;
|
|
1538
1220
|
const gap = right.lpos - left.lpos - leftWidth;
|
|
1539
1221
|
if (gap < minGap) minGap = gap;
|
|
1540
1222
|
}
|
|
1541
|
-
const delta = minGap -
|
|
1223
|
+
const delta = minGap - g.options.nodeMargin;
|
|
1542
1224
|
if (delta <= 0) continue;
|
|
1543
1225
|
anyChanged = true;
|
|
1544
1226
|
for (const right of stack)
|
|
1545
|
-
|
|
1227
|
+
right.setLayerPos(g, right.lpos - delta);
|
|
1546
1228
|
}
|
|
1547
1229
|
}
|
|
1548
1230
|
return anyChanged;
|
|
1549
1231
|
}
|
|
1232
|
+
static getCoords(g) {
|
|
1233
|
+
let pos = 0;
|
|
1234
|
+
const dir = g.r ? -1 : 1;
|
|
1235
|
+
const trackSep = Math.max(
|
|
1236
|
+
g.options.edgeSpacing,
|
|
1237
|
+
g.options.turnRadius
|
|
1238
|
+
);
|
|
1239
|
+
const marginSep = Math.max(
|
|
1240
|
+
g.options.edgeSpacing,
|
|
1241
|
+
g.options.layerMargin,
|
|
1242
|
+
g.options.turnRadius + g.options.markerSize
|
|
1243
|
+
);
|
|
1244
|
+
for (const layerId of g.layerList) {
|
|
1245
|
+
let layer = g.getLayer(layerId);
|
|
1246
|
+
let height;
|
|
1247
|
+
if (g.dirtyLayers.has(layerId)) {
|
|
1248
|
+
height = Seq2(layer.nodes(g)).map((node) => node.dims?.[g.h] ?? 0).max() ?? 0;
|
|
1249
|
+
layer = layer.setSize(g, height);
|
|
1250
|
+
} else height = layer.size;
|
|
1251
|
+
for (const node of layer.nodes(g)) {
|
|
1252
|
+
if (!g.dirtyNodes.has(node.id) && pos == layer.pos) continue;
|
|
1253
|
+
const npos = { [g.x]: node.lpos, [g.y]: pos };
|
|
1254
|
+
if (!g.n) npos[g.y] += dir * height;
|
|
1255
|
+
if (g.r == g.n) npos[g.y] -= node.dims?.[g.h] ?? 0;
|
|
1256
|
+
node.setPos(g, npos);
|
|
1257
|
+
}
|
|
1258
|
+
layer = layer.setPos(g, pos);
|
|
1259
|
+
pos += dir * (height + marginSep);
|
|
1260
|
+
for (const track of layer.tracks) {
|
|
1261
|
+
for (const segId of track)
|
|
1262
|
+
g.getSeg(segId).setTrackPos(g, pos);
|
|
1263
|
+
pos += dir * trackSep;
|
|
1264
|
+
}
|
|
1265
|
+
pos += dir * (marginSep - trackSep);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1550
1268
|
};
|
|
1551
1269
|
|
|
1552
|
-
// src/
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1270
|
+
// src/canvas/marker.tsx
|
|
1271
|
+
import { default as default2 } from "./marker.css?raw";
|
|
1272
|
+
import { jsx } from "jsx-dom/jsx-runtime";
|
|
1273
|
+
function arrow(size, classPrefix, reverse = false) {
|
|
1274
|
+
const h = size / 1.5;
|
|
1275
|
+
const w = size;
|
|
1276
|
+
const ry = h / 2;
|
|
1277
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1278
|
+
return /* @__PURE__ */ jsx(
|
|
1279
|
+
"marker",
|
|
1280
|
+
{
|
|
1281
|
+
id: `g3p-marker-arrow${suffix}`,
|
|
1282
|
+
className: `${classPrefix}-marker ${classPrefix}-marker-arrow`,
|
|
1283
|
+
markerWidth: size,
|
|
1284
|
+
markerHeight: size,
|
|
1285
|
+
refX: "2",
|
|
1286
|
+
refY: ry,
|
|
1287
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1288
|
+
markerUnits: "userSpaceOnUse",
|
|
1289
|
+
children: /* @__PURE__ */ jsx("path", { d: `M0,0 L0,${h} L${w},${ry} z` })
|
|
1290
|
+
}
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
function circle(size, classPrefix, reverse = false) {
|
|
1294
|
+
const r = size / 3;
|
|
1295
|
+
const cy = size / 2;
|
|
1296
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1297
|
+
return /* @__PURE__ */ jsx(
|
|
1298
|
+
"marker",
|
|
1299
|
+
{
|
|
1300
|
+
id: `g3p-marker-circle${suffix}`,
|
|
1301
|
+
className: `${classPrefix}-marker ${classPrefix}-marker-circle`,
|
|
1302
|
+
markerWidth: size,
|
|
1303
|
+
markerHeight: size,
|
|
1304
|
+
refX: "2",
|
|
1305
|
+
refY: cy,
|
|
1306
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1307
|
+
markerUnits: "userSpaceOnUse",
|
|
1308
|
+
children: /* @__PURE__ */ jsx("circle", { cx: r + 2, cy, r })
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
function diamond(size, classPrefix, reverse = false) {
|
|
1313
|
+
const w = size * 0.7;
|
|
1314
|
+
const h = size / 2;
|
|
1315
|
+
const cy = size / 2;
|
|
1316
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1317
|
+
return /* @__PURE__ */ jsx(
|
|
1318
|
+
"marker",
|
|
1319
|
+
{
|
|
1320
|
+
id: `g3p-marker-diamond${suffix}`,
|
|
1321
|
+
className: `${classPrefix}-marker ${classPrefix}-marker-diamond`,
|
|
1322
|
+
markerWidth: size,
|
|
1323
|
+
markerHeight: size,
|
|
1324
|
+
refX: "2",
|
|
1325
|
+
refY: cy,
|
|
1326
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1327
|
+
markerUnits: "userSpaceOnUse",
|
|
1328
|
+
children: /* @__PURE__ */ jsx("path", { d: `M2,${cy} L${2 + w / 2},${cy - h / 2} L${2 + w},${cy} L${2 + w / 2},${cy + h / 2} z` })
|
|
1329
|
+
}
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
function bar(size, classPrefix, reverse = false) {
|
|
1333
|
+
const h = size * 0.6;
|
|
1334
|
+
const cy = size / 2;
|
|
1335
|
+
const suffix = reverse ? "-reverse" : "";
|
|
1336
|
+
return /* @__PURE__ */ jsx(
|
|
1337
|
+
"marker",
|
|
1338
|
+
{
|
|
1339
|
+
id: `g3p-marker-bar${suffix}`,
|
|
1340
|
+
className: `${classPrefix}-marker ${classPrefix}-marker-bar`,
|
|
1341
|
+
markerWidth: size,
|
|
1342
|
+
markerHeight: size,
|
|
1343
|
+
refX: "2",
|
|
1344
|
+
refY: cy,
|
|
1345
|
+
orient: reverse ? "auto-start-reverse" : "auto",
|
|
1346
|
+
markerUnits: "userSpaceOnUse",
|
|
1347
|
+
children: /* @__PURE__ */ jsx("line", { x1: "2", y1: cy - h / 2, x2: "2", y2: cy + h / 2, "stroke-width": "2" })
|
|
1348
|
+
}
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
function none(size, classPrefix, reverse = false) {
|
|
1352
|
+
return void 0;
|
|
1353
|
+
}
|
|
1354
|
+
function normalize(data) {
|
|
1355
|
+
let source = data.source?.marker ?? data.style?.marker?.source;
|
|
1356
|
+
let target = data.target?.marker ?? data.style?.marker?.target ?? "arrow";
|
|
1357
|
+
if (source == "none") source = void 0;
|
|
1358
|
+
if (target == "none") target = void 0;
|
|
1359
|
+
return { source, target };
|
|
1360
|
+
}
|
|
1361
|
+
var markerDefs = {
|
|
1362
|
+
arrow,
|
|
1363
|
+
circle,
|
|
1364
|
+
diamond,
|
|
1365
|
+
bar,
|
|
1366
|
+
none
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
// src/graph/services/lines.ts
|
|
1370
|
+
var log7 = logger("lines");
|
|
1371
|
+
var Lines = class _Lines {
|
|
1372
|
+
static layoutSeg(g, seg) {
|
|
1373
|
+
const sourcePos = Layout.anchorPos(g, seg, "source");
|
|
1374
|
+
const targetPos = Layout.anchorPos(g, seg, "target");
|
|
1375
|
+
return seg.setPos(g, sourcePos[g.x], targetPos[g.x]);
|
|
1376
|
+
}
|
|
1377
|
+
static layerSegs(g, layer) {
|
|
1378
|
+
const segs = [];
|
|
1379
|
+
for (const node of layer.nodes(g))
|
|
1380
|
+
for (const seg of node.outSegs(g))
|
|
1381
|
+
segs.push(_Lines.layoutSeg(g, seg));
|
|
1382
|
+
return segs;
|
|
1383
|
+
}
|
|
1384
|
+
static trackEdges(g) {
|
|
1385
|
+
for (const segId of g.dirtySegs.values())
|
|
1386
|
+
g.dirtyLayers.add(g.getSeg(segId).sourceNode(g).layerId);
|
|
1387
|
+
const minLength = g.options.turnRadius * 2;
|
|
1388
|
+
for (const layerId of g.dirtyLayers.values()) {
|
|
1389
|
+
const layer = g.getLayer(layerId);
|
|
1390
|
+
const leftTracks = [];
|
|
1391
|
+
const rightTracks = [];
|
|
1392
|
+
const allTracks = [];
|
|
1393
|
+
const segs = _Lines.layerSegs(g, layer).sort((a, b) => a.p1 - b.p1);
|
|
1394
|
+
for (const seg of segs) {
|
|
1395
|
+
if (Math.abs(seg.p1 - seg.p2) < minLength) {
|
|
1396
|
+
seg.setTrackPos(g, void 0);
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
let trackSet;
|
|
1400
|
+
if (!g.options.separateTrackSets)
|
|
1401
|
+
trackSet = allTracks;
|
|
1402
|
+
else if (seg.p1 < seg.p2)
|
|
1403
|
+
trackSet = rightTracks;
|
|
1404
|
+
else
|
|
1405
|
+
trackSet = leftTracks;
|
|
1406
|
+
let validTrack;
|
|
1407
|
+
for (let i = trackSet.length - 1; i >= 0; i--) {
|
|
1408
|
+
const track = trackSet[i];
|
|
1409
|
+
let overlap = false;
|
|
1410
|
+
for (const other of track) {
|
|
1411
|
+
if (seg.anySameEnd(other)) {
|
|
1412
|
+
track.push(seg);
|
|
1413
|
+
validTrack = track;
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
if (other.p1 < seg.p2 && seg.p1 < other.p2) {
|
|
1417
|
+
overlap = true;
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (!overlap) {
|
|
1422
|
+
validTrack = track;
|
|
1423
|
+
break;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
if (validTrack)
|
|
1427
|
+
validTrack.push(seg);
|
|
1428
|
+
else
|
|
1429
|
+
trackSet.push([seg]);
|
|
1430
|
+
}
|
|
1431
|
+
const tracks = [];
|
|
1432
|
+
const all = leftTracks.concat(rightTracks).concat(allTracks);
|
|
1433
|
+
for (const track of all)
|
|
1434
|
+
tracks.push(track.map((seg) => seg.id));
|
|
1435
|
+
layer.setTracks(g, tracks);
|
|
1436
|
+
}
|
|
1437
|
+
return this;
|
|
1438
|
+
}
|
|
1439
|
+
static pathEdges(g) {
|
|
1440
|
+
for (const seg of g.segs.values()) {
|
|
1441
|
+
if (!g.dirtySegs.has(seg.id)) continue;
|
|
1442
|
+
const radius = g.options.turnRadius;
|
|
1443
|
+
const p1 = Layout.anchorPos(g, seg, "source");
|
|
1444
|
+
const p2 = Layout.anchorPos(g, seg, "target");
|
|
1445
|
+
const source = seg.sourceNode(g);
|
|
1446
|
+
const target = seg.targetNode(g);
|
|
1447
|
+
const marker = normalize(seg);
|
|
1448
|
+
if (source.isDummy) marker.source = void 0;
|
|
1449
|
+
if (target.isDummy) marker.target = void 0;
|
|
1450
|
+
const path = seg.trackPos !== void 0 ? _Lines.createRailroadPath(g, p1, p2, seg.trackPos, radius, marker) : _Lines.createDirectPath(g, p1, p2, radius, marker);
|
|
1451
|
+
const svg = _Lines.pathToSVG(path);
|
|
1452
|
+
seg.setSVG(g, svg);
|
|
1453
|
+
}
|
|
1454
|
+
return this;
|
|
1455
|
+
}
|
|
1456
|
+
static pathLine(p1, p2, type, radius) {
|
|
1457
|
+
if (p2.x === void 0) p2.x = p1.x;
|
|
1458
|
+
if (p2.y === void 0) p2.y = p1.y;
|
|
1459
|
+
if (p2.s === void 0) p2.s = p1.s;
|
|
1460
|
+
const line = {
|
|
1461
|
+
type,
|
|
1462
|
+
x1: p1.x,
|
|
1463
|
+
y1: p1.y,
|
|
1464
|
+
x2: p2.x,
|
|
1465
|
+
y2: p2.y
|
|
1570
1466
|
};
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1467
|
+
p1.x = p2.x;
|
|
1468
|
+
p1.y = p2.y;
|
|
1469
|
+
p1.s = p2.s;
|
|
1470
|
+
if (type == "arc") {
|
|
1471
|
+
line.radius = radius;
|
|
1472
|
+
line.sweep = p1.s;
|
|
1473
|
+
}
|
|
1474
|
+
return line;
|
|
1475
|
+
}
|
|
1476
|
+
static pathBuilder(g, start, end, trackPos, radius, marker) {
|
|
1477
|
+
const { x, y } = g;
|
|
1478
|
+
const lr = end[x] > start[x];
|
|
1479
|
+
const d = lr ? 1 : -1;
|
|
1480
|
+
const o = g.r ? -1 : 1;
|
|
1481
|
+
const rd = radius * d;
|
|
1482
|
+
const ro = radius * o;
|
|
1483
|
+
const t = trackPos;
|
|
1484
|
+
let s = 0;
|
|
1485
|
+
if (g.r) s = 1 - s;
|
|
1486
|
+
if (!lr) s = 1 - s;
|
|
1487
|
+
if (!g.v) s = 1 - s;
|
|
1488
|
+
if (marker.source) start[y] += o * (g.options.markerSize - 1);
|
|
1489
|
+
if (marker.target) end[y] -= o * (g.options.markerSize - 1);
|
|
1490
|
+
const p = { ...start, s };
|
|
1491
|
+
const path = [];
|
|
1492
|
+
const advance = (p2, type) => {
|
|
1493
|
+
path.push(this.pathLine(p, p2, type, radius));
|
|
1576
1494
|
};
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1495
|
+
return { x, y, lr, d, o, rd, ro, t, s, p, path, advance };
|
|
1496
|
+
}
|
|
1497
|
+
// Create a railroad-style path with two 90-degree turns
|
|
1498
|
+
static createRailroadPath(g, start, end, trackPos, radius, marker) {
|
|
1499
|
+
const { x, y, rd, ro, t, s, p, path, advance } = this.pathBuilder(g, start, end, trackPos, radius, marker);
|
|
1500
|
+
advance({ [y]: t - ro }, "line");
|
|
1501
|
+
advance({ [x]: p[x] + rd, [y]: t }, "arc");
|
|
1502
|
+
advance({ [x]: end[x] - rd }, "line");
|
|
1503
|
+
advance({ [x]: end[x], [y]: t + ro, s: 1 - s }, "arc");
|
|
1504
|
+
advance({ [y]: end[y] }, "line");
|
|
1505
|
+
return path;
|
|
1506
|
+
}
|
|
1507
|
+
// Create a mostly-vertical path with optional S-curve
|
|
1508
|
+
static createDirectPath(g, start, end, radius, marker) {
|
|
1509
|
+
const { x, y, d, o, s, p, path, advance } = this.pathBuilder(g, start, end, 0, radius, marker);
|
|
1510
|
+
const dx = Math.abs(end.x - start.x);
|
|
1511
|
+
const dy = Math.abs(end.y - start.y);
|
|
1512
|
+
const d_ = { x: dx, y: dy };
|
|
1513
|
+
if (dx < 0.1) {
|
|
1514
|
+
advance({ ...end }, "line");
|
|
1515
|
+
return path;
|
|
1516
|
+
}
|
|
1517
|
+
const curve = _Lines.calculateSCurve(d_[x], d_[y], radius);
|
|
1518
|
+
if (!curve || curve.Ly == 0) {
|
|
1519
|
+
advance({ ...end }, "line");
|
|
1520
|
+
return path;
|
|
1521
|
+
}
|
|
1522
|
+
const m = {
|
|
1523
|
+
[x]: d * (d_[x] - curve.Lx) / 2,
|
|
1524
|
+
[y]: o * (d_[y] - curve.Ly) / 2
|
|
1589
1525
|
};
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
this._natural = natAligns[this.options.orientation] == this.options.nodeAlign;
|
|
1595
|
-
if (this._dirty) this._update();
|
|
1526
|
+
advance({ [x]: p[x] + m[x], [y]: p[y] + m[y] }, "arc");
|
|
1527
|
+
advance({ [x]: end[x] - m[x], [y]: end[y] - m[y] }, "line");
|
|
1528
|
+
advance({ [x]: end[x], [y]: end[y], s: 1 - s }, "arc");
|
|
1529
|
+
return path;
|
|
1596
1530
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1531
|
+
static solveSCurveAngle(dx, dy, r) {
|
|
1532
|
+
const f = (alpha2) => {
|
|
1533
|
+
return dx * Math.cos(alpha2) - dy * Math.sin(alpha2) - 2 * r * Math.cos(alpha2) + 2 * r;
|
|
1534
|
+
};
|
|
1535
|
+
const fPrime = (alpha2) => {
|
|
1536
|
+
return -dx * Math.sin(alpha2) - dy * Math.cos(alpha2) + 2 * r * Math.sin(alpha2);
|
|
1537
|
+
};
|
|
1538
|
+
let alpha = Math.min(0.1, Math.abs(dx) / dy);
|
|
1539
|
+
const maxIterations = 20;
|
|
1540
|
+
const tolerance = 1e-6;
|
|
1541
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
1542
|
+
const fVal = f(alpha);
|
|
1543
|
+
const fPrimeVal = fPrime(alpha);
|
|
1544
|
+
if (Math.abs(fVal) < tolerance) {
|
|
1545
|
+
const Ly = dy - 2 * r * Math.sin(alpha);
|
|
1546
|
+
if (Ly >= 0 && alpha > 0 && alpha < Math.PI / 2) {
|
|
1547
|
+
return alpha;
|
|
1548
|
+
}
|
|
1549
|
+
return null;
|
|
1550
|
+
}
|
|
1551
|
+
if (Math.abs(fPrimeVal) < 1e-10) {
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
const nextAlpha = alpha - fVal / fPrimeVal;
|
|
1555
|
+
if (nextAlpha < 0 || nextAlpha > Math.PI / 2) {
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
alpha = nextAlpha;
|
|
1559
|
+
}
|
|
1560
|
+
return null;
|
|
1561
|
+
}
|
|
1562
|
+
static calculateSCurve(dx, dy, r) {
|
|
1563
|
+
const alpha = _Lines.solveSCurveAngle(dx, dy, r);
|
|
1564
|
+
if (alpha === null) {
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
const Ly = dy - 2 * r * Math.sin(alpha);
|
|
1568
|
+
const Lx = Ly * Math.tan(alpha);
|
|
1569
|
+
const L = Ly / Math.cos(alpha);
|
|
1570
|
+
return {
|
|
1571
|
+
alpha,
|
|
1572
|
+
// Angle of each arc (radians)
|
|
1573
|
+
Lx,
|
|
1574
|
+
// Horizontal component of straight section
|
|
1575
|
+
Ly,
|
|
1576
|
+
// Vertical component of straight section
|
|
1577
|
+
L
|
|
1578
|
+
// Length of straight section
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
static pathToSVG(path) {
|
|
1582
|
+
if (!path || path.length === 0) return "";
|
|
1583
|
+
const lines = [];
|
|
1584
|
+
const first = path[0];
|
|
1585
|
+
lines.push(`M ${first.x1},${first.y1}`);
|
|
1586
|
+
for (const line of path) {
|
|
1587
|
+
if (line.type === "line") {
|
|
1588
|
+
lines.push(`L ${line.x2},${line.y2}`);
|
|
1589
|
+
} else if (line.type === "arc") {
|
|
1590
|
+
const r = line.radius;
|
|
1591
|
+
const largeArc = 0;
|
|
1592
|
+
const sweep = line.sweep || 0;
|
|
1593
|
+
lines.push(`A ${r},${r} 0 ${largeArc} ${sweep} ${line.x2},${line.y2}`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
return lines.join(" ");
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
// src/graph/graph.ts
|
|
1601
|
+
var emptyChanges = {
|
|
1602
|
+
addedNodes: [],
|
|
1603
|
+
removedNodes: [],
|
|
1604
|
+
updatedNodes: [],
|
|
1605
|
+
addedEdges: [],
|
|
1606
|
+
removedEdges: [],
|
|
1607
|
+
updatedEdges: []
|
|
1608
|
+
};
|
|
1609
|
+
var Graph = class _Graph {
|
|
1610
|
+
prior;
|
|
1611
|
+
nodes;
|
|
1612
|
+
edges;
|
|
1613
|
+
segs;
|
|
1614
|
+
layers;
|
|
1615
|
+
layerList;
|
|
1616
|
+
nextLayerId;
|
|
1617
|
+
nextDummyId;
|
|
1618
|
+
options;
|
|
1619
|
+
changes;
|
|
1620
|
+
dirtyNodes;
|
|
1621
|
+
dirtyEdges;
|
|
1622
|
+
dirtyLayers;
|
|
1623
|
+
dirtySegs;
|
|
1624
|
+
dirty;
|
|
1625
|
+
delNodes;
|
|
1626
|
+
delEdges;
|
|
1627
|
+
delSegs;
|
|
1628
|
+
r;
|
|
1629
|
+
v;
|
|
1630
|
+
n;
|
|
1631
|
+
h;
|
|
1632
|
+
w;
|
|
1633
|
+
x;
|
|
1634
|
+
y;
|
|
1635
|
+
d;
|
|
1636
|
+
constructor({ prior, changes, options }) {
|
|
1637
|
+
this.options = prior?.options ?? options;
|
|
1638
|
+
this.changes = changes ?? emptyChanges;
|
|
1639
|
+
this.initFromPrior(prior);
|
|
1640
|
+
this.r = this.options.orientation === "BT" || this.options.orientation === "RL";
|
|
1641
|
+
this.v = this.options.orientation === "TB" || this.options.orientation === "BT";
|
|
1642
|
+
this.h = this.v ? "h" : "w";
|
|
1643
|
+
this.w = this.v ? "w" : "h";
|
|
1644
|
+
this.x = this.v ? "x" : "y";
|
|
1645
|
+
this.y = this.v ? "y" : "x";
|
|
1646
|
+
this.d = {
|
|
1647
|
+
x: this.v ? 0 : this.r ? -1 : 1,
|
|
1648
|
+
y: this.v ? this.r ? -1 : 1 : 0
|
|
1649
|
+
};
|
|
1650
|
+
const natAligns = { TB: "top", BT: "bottom", LR: "left", RL: "right" };
|
|
1651
|
+
if (this.options.nodeAlign == "natural")
|
|
1652
|
+
this.n = true;
|
|
1653
|
+
else
|
|
1654
|
+
this.n = natAligns[this.options.orientation] == this.options.nodeAlign;
|
|
1655
|
+
if (this.dirty) this.processUpdate();
|
|
1656
|
+
}
|
|
1657
|
+
processUpdate() {
|
|
1658
|
+
try {
|
|
1659
|
+
this.beginMutate();
|
|
1660
|
+
this.applyChanges();
|
|
1661
|
+
Cycles.checkCycles(this);
|
|
1662
|
+
Layers.updateLayers(this);
|
|
1663
|
+
Dummy.updateDummies(this);
|
|
1664
|
+
Dummy.mergeDummies(this);
|
|
1665
|
+
Layout.positionNodes(this);
|
|
1666
|
+
Layout.alignAll(this);
|
|
1667
|
+
Lines.trackEdges(this);
|
|
1668
|
+
Layout.getCoords(this);
|
|
1669
|
+
Lines.pathEdges(this);
|
|
1670
|
+
} catch (e) {
|
|
1671
|
+
this.initFromPrior(this.prior);
|
|
1672
|
+
throw e;
|
|
1673
|
+
} finally {
|
|
1674
|
+
this.endMutate();
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
applyChanges() {
|
|
1678
|
+
for (const edge of this.changes.removedEdges)
|
|
1679
|
+
Edge.del(this, edge);
|
|
1680
|
+
for (const node of this.changes.removedNodes)
|
|
1681
|
+
Node.del(this, node);
|
|
1682
|
+
for (const node of this.changes.addedNodes)
|
|
1683
|
+
Node.addNormal(this, node);
|
|
1684
|
+
for (const edge of this.changes.addedEdges)
|
|
1685
|
+
Edge.add(this, edge);
|
|
1686
|
+
for (const node of this.changes.updatedNodes)
|
|
1687
|
+
Node.update(this, node);
|
|
1688
|
+
for (const edge of this.changes.updatedEdges)
|
|
1689
|
+
Edge.update(this, edge);
|
|
1690
|
+
}
|
|
1691
|
+
layerAt(index) {
|
|
1692
|
+
while (index >= this.layerList.size)
|
|
1693
|
+
this.addLayer();
|
|
1694
|
+
const layerId = this.layerList.get(index);
|
|
1695
|
+
return this.getLayer(layerId);
|
|
1696
|
+
}
|
|
1697
|
+
addLayer() {
|
|
1698
|
+
const id = `${Layer.prefix}${this.nextLayerId++}`;
|
|
1699
|
+
this.layers.set(id, new Layer({
|
|
1700
|
+
id,
|
|
1701
|
+
index: this.layerList.size,
|
|
1702
|
+
nodeIds: ISet6()
|
|
1703
|
+
}));
|
|
1704
|
+
this.layerList.push(id);
|
|
1705
|
+
this.dirtyLayers.add(id);
|
|
1706
|
+
}
|
|
1707
|
+
isEdgeId(id) {
|
|
1708
|
+
return id.startsWith(Edge.prefix);
|
|
1709
|
+
}
|
|
1710
|
+
isSegId(id) {
|
|
1711
|
+
return id.startsWith(Seg.prefix);
|
|
1712
|
+
}
|
|
1713
|
+
getNode(nodeId) {
|
|
1714
|
+
const node = this.nodes.get(nodeId);
|
|
1715
|
+
if (!node) throw new Error(`cannot find node ${nodeId}`);
|
|
1716
|
+
return node;
|
|
1717
|
+
}
|
|
1718
|
+
getEdge(edgeId) {
|
|
1719
|
+
const edge = this.edges.get(edgeId);
|
|
1720
|
+
if (!edge) throw new Error(`cannot find edge ${edgeId}`);
|
|
1721
|
+
return edge;
|
|
1722
|
+
}
|
|
1723
|
+
getSeg(segId) {
|
|
1724
|
+
const seg = this.segs.get(segId);
|
|
1725
|
+
if (!seg) throw new Error(`cannot find seg ${segId}`);
|
|
1726
|
+
return seg;
|
|
1727
|
+
}
|
|
1728
|
+
getLayer(layerId) {
|
|
1729
|
+
const layer = this.layers.get(layerId);
|
|
1730
|
+
if (!layer) throw new Error(`cannot find layer ${layerId}`);
|
|
1731
|
+
return layer;
|
|
1732
|
+
}
|
|
1733
|
+
layerIndex(nodeId) {
|
|
1734
|
+
return this.getNode(nodeId).layerIndex(this);
|
|
1735
|
+
}
|
|
1736
|
+
getRel(relId) {
|
|
1737
|
+
return this.isSegId(relId) ? this.getSeg(relId) : this.getEdge(relId);
|
|
1738
|
+
}
|
|
1739
|
+
*getNodes(includeDummy = false) {
|
|
1740
|
+
const gen = this.nodes.values();
|
|
1741
|
+
for (const node of this.nodes.values())
|
|
1742
|
+
if (includeDummy || !node.isDummy)
|
|
1743
|
+
yield node;
|
|
1744
|
+
}
|
|
1745
|
+
*getEdges() {
|
|
1746
|
+
yield* this.edges.values();
|
|
1747
|
+
}
|
|
1748
|
+
*getSegs() {
|
|
1749
|
+
yield* this.segs.values();
|
|
1750
|
+
}
|
|
1751
|
+
withMutations(callback) {
|
|
1752
|
+
const mut = new Mutator();
|
|
1753
|
+
callback(mut);
|
|
1754
|
+
return new _Graph({ prior: this, changes: mut.changes });
|
|
1755
|
+
}
|
|
1756
|
+
addNode(node) {
|
|
1757
|
+
return this.withMutations((mutator) => {
|
|
1758
|
+
mutator.addNode(node);
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
addNodes(...nodes) {
|
|
1762
|
+
return this.withMutations((mutator) => {
|
|
1763
|
+
nodes.forEach((node) => mutator.addNode(node));
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
removeNodes(...nodes) {
|
|
1767
|
+
return this.withMutations((mutator) => {
|
|
1768
|
+
nodes.forEach((node) => mutator.removeNode(node));
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
removeNode(node) {
|
|
1772
|
+
return this.withMutations((mutator) => {
|
|
1773
|
+
mutator.removeNode(node);
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
addEdges(...edges) {
|
|
1777
|
+
return this.withMutations((mutator) => {
|
|
1778
|
+
edges.forEach((edge) => mutator.addEdge(edge));
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
addEdge(edge) {
|
|
1782
|
+
return this.withMutations((mutator) => {
|
|
1783
|
+
mutator.addEdge(edge);
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
removeEdges(...edges) {
|
|
1787
|
+
return this.withMutations((mutator) => {
|
|
1788
|
+
edges.forEach((edge) => mutator.removeEdge(edge));
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
removeEdge(edge) {
|
|
1792
|
+
return this.withMutations((mutator) => {
|
|
1793
|
+
mutator.removeEdge(edge);
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
mutateNode(node) {
|
|
1797
|
+
if (node.mutable) return node;
|
|
1798
|
+
node = node.asMutable().set("mutable", true);
|
|
1799
|
+
this.nodes.set(node.id, node);
|
|
1800
|
+
this.dirtyNodes.add(node.id);
|
|
1801
|
+
return node;
|
|
1802
|
+
}
|
|
1803
|
+
mutateEdge(edge) {
|
|
1804
|
+
if (edge.mutable) return edge;
|
|
1805
|
+
edge = edge.asMutable().set("mutable", true);
|
|
1806
|
+
this.edges.set(edge.id, edge);
|
|
1807
|
+
this.dirtyEdges.add(edge.id);
|
|
1808
|
+
return edge;
|
|
1809
|
+
}
|
|
1810
|
+
mutateLayer(layer) {
|
|
1811
|
+
if (layer.mutable) return layer;
|
|
1812
|
+
layer = layer.asMutable().set("mutable", true);
|
|
1813
|
+
this.layers.set(layer.id, layer);
|
|
1814
|
+
this.dirtyLayers.add(layer.id);
|
|
1815
|
+
return layer;
|
|
1816
|
+
}
|
|
1817
|
+
mutateSeg(seg) {
|
|
1818
|
+
if (seg.mutable) return seg;
|
|
1819
|
+
seg = seg.asMutable().set("mutable", true);
|
|
1820
|
+
this.segs.set(seg.id, seg);
|
|
1821
|
+
this.dirtySegs.add(seg.id);
|
|
1822
|
+
return seg;
|
|
1823
|
+
}
|
|
1824
|
+
initFromPrior(prior) {
|
|
1825
|
+
this.nodes = prior?.nodes ?? IMap();
|
|
1826
|
+
this.edges = prior?.edges ?? IMap();
|
|
1827
|
+
this.layers = prior?.layers ?? IMap();
|
|
1828
|
+
this.layerList = prior?.layerList ?? IList();
|
|
1829
|
+
this.segs = prior?.segs ?? IMap();
|
|
1830
|
+
this.nextLayerId = prior?.nextLayerId ?? 0;
|
|
1831
|
+
this.nextDummyId = prior?.nextDummyId ?? 0;
|
|
1832
|
+
this.prior = prior;
|
|
1833
|
+
this.dirtyNodes = /* @__PURE__ */ new Set();
|
|
1834
|
+
this.dirtyEdges = /* @__PURE__ */ new Set();
|
|
1835
|
+
this.dirtyLayers = /* @__PURE__ */ new Set();
|
|
1836
|
+
this.dirtySegs = /* @__PURE__ */ new Set();
|
|
1837
|
+
this.delNodes = /* @__PURE__ */ new Set();
|
|
1838
|
+
this.delEdges = /* @__PURE__ */ new Set();
|
|
1839
|
+
this.delSegs = /* @__PURE__ */ new Set();
|
|
1840
|
+
this.dirty = this.changes.addedNodes.length > 0 || this.changes.removedNodes.length > 0 || this.changes.updatedNodes.length > 0 || this.changes.addedEdges.length > 0 || this.changes.removedEdges.length > 0;
|
|
1841
|
+
}
|
|
1842
|
+
beginMutate() {
|
|
1843
|
+
this.nodes = this.nodes.asMutable();
|
|
1844
|
+
this.edges = this.edges.asMutable();
|
|
1845
|
+
this.layers = this.layers.asMutable();
|
|
1846
|
+
this.layerList = this.layerList.asMutable();
|
|
1847
|
+
this.segs = this.segs.asMutable();
|
|
1848
|
+
}
|
|
1849
|
+
endMutate() {
|
|
1850
|
+
for (const nodeId of this.dirtyNodes)
|
|
1851
|
+
if (this.nodes.has(nodeId))
|
|
1852
|
+
this.nodes.set(nodeId, this.nodes.get(nodeId).final());
|
|
1853
|
+
for (const edgeId of this.dirtyEdges)
|
|
1854
|
+
if (this.edges.has(edgeId))
|
|
1855
|
+
this.edges.set(edgeId, this.edges.get(edgeId).final());
|
|
1856
|
+
for (const segId of this.dirtySegs)
|
|
1857
|
+
if (this.segs.has(segId))
|
|
1858
|
+
this.segs.set(segId, this.segs.get(segId).final());
|
|
1859
|
+
for (const layerId of this.dirtyLayers)
|
|
1860
|
+
if (this.layers.has(layerId))
|
|
1861
|
+
this.layers.set(layerId, this.layers.get(layerId).final());
|
|
1862
|
+
this.nodes = this.nodes.asImmutable();
|
|
1863
|
+
this.edges = this.edges.asImmutable();
|
|
1864
|
+
this.layers = this.layers.asImmutable();
|
|
1865
|
+
this.layerList = this.layerList.asImmutable();
|
|
1866
|
+
this.segs = this.segs.asImmutable();
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
|
|
1870
|
+
// src/common.ts
|
|
1871
|
+
var screenPos = (x, y) => ({ x, y });
|
|
1872
|
+
var canvasPos = (x, y) => ({ x, y });
|
|
1873
|
+
var graphPos = (x, y) => ({ x, y });
|
|
1874
|
+
|
|
1875
|
+
// src/canvas/node.tsx
|
|
1876
|
+
import styles from "./node.css?raw";
|
|
1877
|
+
|
|
1878
|
+
// src/canvas/styler.ts
|
|
1879
|
+
var injected = {};
|
|
1880
|
+
function styler(name, styles4, prefix) {
|
|
1881
|
+
if (prefix === "g3p" && !injected[name]) {
|
|
1882
|
+
const style = document.createElement("style");
|
|
1883
|
+
style.textContent = styles4;
|
|
1884
|
+
document.head.appendChild(style);
|
|
1885
|
+
injected[name] = true;
|
|
1886
|
+
}
|
|
1887
|
+
return (str, condition) => {
|
|
1888
|
+
if (!(condition ?? true)) return "";
|
|
1889
|
+
const parts = str.split(/\s+/);
|
|
1890
|
+
const fixed = parts.map((p) => `${prefix}-${name}-${p}`);
|
|
1891
|
+
return fixed.join(" ");
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// src/canvas/node.tsx
|
|
1896
|
+
import { jsx as jsx2, jsxs } from "jsx-dom/jsx-runtime";
|
|
1897
|
+
var log8 = logger("canvas");
|
|
1898
|
+
var Node2 = class {
|
|
1899
|
+
selected;
|
|
1900
|
+
hovered;
|
|
1901
|
+
container;
|
|
1902
|
+
content;
|
|
1903
|
+
canvas;
|
|
1904
|
+
data;
|
|
1905
|
+
classPrefix;
|
|
1906
|
+
isDummy;
|
|
1907
|
+
pos;
|
|
1908
|
+
constructor(canvas, data, isDummy = false) {
|
|
1909
|
+
this.canvas = canvas;
|
|
1910
|
+
this.data = data;
|
|
1911
|
+
this.selected = false;
|
|
1912
|
+
this.hovered = false;
|
|
1913
|
+
this.classPrefix = canvas.classPrefix;
|
|
1914
|
+
this.isDummy = isDummy;
|
|
1915
|
+
if (this.isDummy) {
|
|
1916
|
+
const size = canvas.dummyNodeSize;
|
|
1917
|
+
} else {
|
|
1918
|
+
const render = data.render ?? canvas.renderNode;
|
|
1919
|
+
this.content = this.renderContent(render(data.data));
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
remove() {
|
|
1923
|
+
this.container.remove();
|
|
1924
|
+
}
|
|
1925
|
+
append() {
|
|
1926
|
+
console.log("append", this);
|
|
1927
|
+
this.canvas.group.appendChild(this.container);
|
|
1928
|
+
}
|
|
1929
|
+
needsContentSize() {
|
|
1930
|
+
return !this.isDummy && this.content instanceof HTMLElement;
|
|
1931
|
+
}
|
|
1932
|
+
needsContainerSize() {
|
|
1933
|
+
return !this.isDummy;
|
|
1934
|
+
}
|
|
1935
|
+
handleClick(e) {
|
|
1936
|
+
e.stopPropagation();
|
|
1937
|
+
}
|
|
1938
|
+
handleMouseEnter(e) {
|
|
1939
|
+
}
|
|
1940
|
+
handleMouseLeave(e) {
|
|
1941
|
+
}
|
|
1942
|
+
handleContextMenu(e) {
|
|
1943
|
+
}
|
|
1944
|
+
handleMouseDown(e) {
|
|
1945
|
+
}
|
|
1946
|
+
handleMouseUp(e) {
|
|
1947
|
+
}
|
|
1948
|
+
setPos(pos) {
|
|
1949
|
+
this.pos = pos;
|
|
1950
|
+
const { x, y } = pos;
|
|
1951
|
+
this.container.setAttribute("transform", `translate(${x}, ${y})`);
|
|
1952
|
+
}
|
|
1953
|
+
hasPorts() {
|
|
1954
|
+
return !!this.data?.ports?.in?.length || !!this.data?.ports?.out?.length;
|
|
1955
|
+
}
|
|
1956
|
+
renderContent(el) {
|
|
1957
|
+
const hasPorts = this.hasPorts();
|
|
1958
|
+
el = this.renderBorder(el);
|
|
1959
|
+
if (hasPorts)
|
|
1960
|
+
el = this.renderOutsidePorts(el);
|
|
1961
|
+
return el;
|
|
1962
|
+
}
|
|
1963
|
+
renderContainer() {
|
|
1964
|
+
const c = styler("node", styles, this.classPrefix);
|
|
1965
|
+
const hasPorts = this.hasPorts();
|
|
1966
|
+
const inner = this.isDummy ? this.renderDummy() : this.renderForeign();
|
|
1967
|
+
const nodeType = this.data?.type;
|
|
1968
|
+
const typeClass = nodeType ? `g3p-node-type-${nodeType}` : "";
|
|
1969
|
+
this.container = /* @__PURE__ */ jsx2(
|
|
1970
|
+
"g",
|
|
1971
|
+
{
|
|
1972
|
+
className: `${c("container")} ${c("dummy", this.isDummy)} ${typeClass}`.trim(),
|
|
1973
|
+
onClick: (e) => this.handleClick(e),
|
|
1974
|
+
onMouseEnter: (e) => this.handleMouseEnter(e),
|
|
1975
|
+
onMouseLeave: (e) => this.handleMouseLeave(e),
|
|
1976
|
+
onContextMenu: (e) => this.handleContextMenu(e),
|
|
1977
|
+
onMouseDown: (e) => this.handleMouseDown(e),
|
|
1978
|
+
onMouseUp: (e) => this.handleMouseUp(e),
|
|
1979
|
+
style: { cursor: "pointer" },
|
|
1980
|
+
children: inner
|
|
1981
|
+
}
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
renderForeign() {
|
|
1985
|
+
const { w, h } = this.data.dims;
|
|
1986
|
+
return /* @__PURE__ */ jsx2("foreignObject", { width: w, height: h, children: this.content });
|
|
1987
|
+
}
|
|
1988
|
+
renderDummy() {
|
|
1989
|
+
const c = styler("node", styles, this.classPrefix);
|
|
1990
|
+
let w = this.canvas.dummyNodeSize;
|
|
1991
|
+
let h = this.canvas.dummyNodeSize;
|
|
1992
|
+
w /= 2;
|
|
1993
|
+
h /= 2;
|
|
1994
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
1995
|
+
/* @__PURE__ */ jsx2(
|
|
1996
|
+
"ellipse",
|
|
1997
|
+
{
|
|
1998
|
+
cx: w,
|
|
1999
|
+
cy: h,
|
|
2000
|
+
rx: w,
|
|
2001
|
+
ry: h,
|
|
2002
|
+
className: c("background")
|
|
2003
|
+
}
|
|
2004
|
+
),
|
|
2005
|
+
/* @__PURE__ */ jsx2(
|
|
2006
|
+
"ellipse",
|
|
2007
|
+
{
|
|
2008
|
+
cx: w,
|
|
2009
|
+
cy: h,
|
|
2010
|
+
rx: w,
|
|
2011
|
+
ry: h,
|
|
2012
|
+
fill: "none",
|
|
2013
|
+
className: c("border")
|
|
2014
|
+
}
|
|
2015
|
+
)
|
|
2016
|
+
] });
|
|
2017
|
+
}
|
|
2018
|
+
measure(isVertical) {
|
|
2019
|
+
const rect = this.content.getBoundingClientRect();
|
|
2020
|
+
const data = this.data;
|
|
2021
|
+
data.dims = { w: rect.width, h: rect.height };
|
|
2022
|
+
for (const dir of ["in", "out"]) {
|
|
2023
|
+
const ports = data.ports?.[dir];
|
|
2024
|
+
if (!ports) continue;
|
|
2025
|
+
for (const port of ports) {
|
|
2026
|
+
const el = this.content.querySelector(`#g3p-port-${data.id}-${port.id}`);
|
|
2027
|
+
if (!el) continue;
|
|
2028
|
+
const portRect = el.getBoundingClientRect();
|
|
2029
|
+
if (isVertical) {
|
|
2030
|
+
port.offset = portRect.left - rect.left;
|
|
2031
|
+
port.size = portRect.width;
|
|
2032
|
+
} else {
|
|
2033
|
+
port.offset = portRect.top - rect.top;
|
|
2034
|
+
port.size = portRect.height;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
getPortPosition(dir) {
|
|
2040
|
+
const o = this.canvas.orientation;
|
|
2041
|
+
if (dir === "in") {
|
|
2042
|
+
if (o === "TB") return "top";
|
|
2043
|
+
if (o === "BT") return "bottom";
|
|
2044
|
+
if (o === "LR") return "left";
|
|
2045
|
+
return "right";
|
|
2046
|
+
} else {
|
|
2047
|
+
if (o === "TB") return "bottom";
|
|
2048
|
+
if (o === "BT") return "top";
|
|
2049
|
+
if (o === "LR") return "right";
|
|
2050
|
+
return "left";
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
isVerticalOrientation() {
|
|
2054
|
+
const o = this.canvas.orientation;
|
|
2055
|
+
return o === "TB" || o === "BT";
|
|
2056
|
+
}
|
|
2057
|
+
isReversedOrientation() {
|
|
2058
|
+
const o = this.canvas.orientation;
|
|
2059
|
+
return o === "BT" || o === "RL";
|
|
2060
|
+
}
|
|
2061
|
+
renderPortRow(dir, inout) {
|
|
2062
|
+
const ports = this.data?.ports?.[dir];
|
|
2063
|
+
if (!ports?.length) return null;
|
|
2064
|
+
const c = styler("node", styles, this.classPrefix);
|
|
2065
|
+
const pos = this.getPortPosition(dir);
|
|
2066
|
+
const isVertical = this.isVerticalOrientation();
|
|
2067
|
+
const layoutClass = isVertical ? "row" : "col";
|
|
2068
|
+
const rotateLabels = false;
|
|
2069
|
+
const rotateClass = rotateLabels ? `port-rotated-${pos}` : "";
|
|
2070
|
+
return /* @__PURE__ */ jsx2("div", { className: `${c("ports")} ${c(`ports-${layoutClass}`)}`, children: ports.map((port) => /* @__PURE__ */ jsx2(
|
|
2071
|
+
"div",
|
|
2072
|
+
{
|
|
2073
|
+
id: `g3p-port-${this.data.id}-${port.id}`,
|
|
2074
|
+
className: `${c("port")} ${c(`port-${inout}-${pos}`)} ${c(rotateClass)}`,
|
|
2075
|
+
children: port.label ?? port.id
|
|
2076
|
+
}
|
|
2077
|
+
)) });
|
|
2078
|
+
}
|
|
2079
|
+
renderInsidePorts(el) {
|
|
2080
|
+
const c = styler("node", styles, this.classPrefix);
|
|
2081
|
+
const isVertical = this.isVerticalOrientation();
|
|
2082
|
+
const isReversed = this.isReversedOrientation();
|
|
2083
|
+
let inPorts = this.renderPortRow("in", "in");
|
|
2084
|
+
let outPorts = this.renderPortRow("out", "in");
|
|
2085
|
+
if (!inPorts && !outPorts) return el;
|
|
2086
|
+
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2087
|
+
const wrapperClass = isVertical ? "v" : "h";
|
|
2088
|
+
return /* @__PURE__ */ jsxs("div", { className: `${c("with-ports")} ${c(`with-ports-${wrapperClass}`)}`, children: [
|
|
2089
|
+
inPorts,
|
|
2090
|
+
el,
|
|
2091
|
+
outPorts
|
|
2092
|
+
] });
|
|
2093
|
+
}
|
|
2094
|
+
renderOutsidePorts(el) {
|
|
2095
|
+
const c = styler("node", styles, this.classPrefix);
|
|
2096
|
+
const isVertical = this.isVerticalOrientation();
|
|
2097
|
+
const isReversed = this.isReversedOrientation();
|
|
2098
|
+
let inPorts = this.renderPortRow("in", "out");
|
|
2099
|
+
let outPorts = this.renderPortRow("out", "out");
|
|
2100
|
+
if (!inPorts && !outPorts) return el;
|
|
2101
|
+
if (isReversed) [inPorts, outPorts] = [outPorts, inPorts];
|
|
2102
|
+
const wrapperClass = isVertical ? "v" : "h";
|
|
2103
|
+
return /* @__PURE__ */ jsxs("div", { className: `${c("with-ports")} ${c(`with-ports-${wrapperClass}`)}`, children: [
|
|
2104
|
+
inPorts,
|
|
2105
|
+
el,
|
|
2106
|
+
outPorts
|
|
2107
|
+
] });
|
|
2108
|
+
}
|
|
2109
|
+
renderBorder(el) {
|
|
2110
|
+
const c = styler("node", styles, this.classPrefix);
|
|
2111
|
+
return /* @__PURE__ */ jsx2("div", { className: c("border"), children: el });
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
|
|
2115
|
+
// src/canvas/seg.tsx
|
|
2116
|
+
import styles2 from "./seg.css?raw";
|
|
2117
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "jsx-dom/jsx-runtime";
|
|
2118
|
+
var log9 = logger("canvas");
|
|
2119
|
+
var Seg2 = class {
|
|
2120
|
+
id;
|
|
2121
|
+
selected;
|
|
2122
|
+
hovered;
|
|
2123
|
+
canvas;
|
|
2124
|
+
classPrefix;
|
|
2125
|
+
type;
|
|
2126
|
+
svg;
|
|
2127
|
+
el;
|
|
2128
|
+
source;
|
|
2129
|
+
target;
|
|
2130
|
+
constructor(canvas, data, g) {
|
|
2131
|
+
this.id = data.id;
|
|
2132
|
+
this.canvas = canvas;
|
|
2133
|
+
this.selected = false;
|
|
2134
|
+
this.hovered = false;
|
|
2135
|
+
this.svg = data.svg;
|
|
2136
|
+
this.classPrefix = canvas.classPrefix;
|
|
2137
|
+
this.source = { ...data.source, isDummy: data.sourceNode(g).isDummy };
|
|
2138
|
+
this.target = { ...data.target, isDummy: data.targetNode(g).isDummy };
|
|
2139
|
+
this.type = data.type;
|
|
2140
|
+
this.el = this.render();
|
|
2141
|
+
}
|
|
2142
|
+
handleClick(e) {
|
|
2143
|
+
e.stopPropagation();
|
|
2144
|
+
}
|
|
2145
|
+
handleMouseEnter(e) {
|
|
2146
|
+
}
|
|
2147
|
+
handleMouseLeave(e) {
|
|
2148
|
+
}
|
|
2149
|
+
handleContextMenu(e) {
|
|
2150
|
+
}
|
|
2151
|
+
append() {
|
|
2152
|
+
this.canvas.group.appendChild(this.el);
|
|
2153
|
+
}
|
|
2154
|
+
remove() {
|
|
2155
|
+
this.el.remove();
|
|
2156
|
+
}
|
|
2157
|
+
update(data) {
|
|
2158
|
+
this.svg = data.svg;
|
|
2159
|
+
this.type = data.type;
|
|
2160
|
+
this.source = data.source;
|
|
2161
|
+
this.target = data.target;
|
|
2162
|
+
this.remove();
|
|
2163
|
+
this.el = this.render();
|
|
2164
|
+
this.append();
|
|
2165
|
+
}
|
|
2166
|
+
render() {
|
|
2167
|
+
const c = styler("seg", styles2, this.classPrefix);
|
|
2168
|
+
let { source, target } = normalize(this);
|
|
2169
|
+
if (this.source.isDummy) source = void 0;
|
|
2170
|
+
if (this.target.isDummy) target = void 0;
|
|
2171
|
+
const typeClass = this.type ? `g3p-edge-type-${this.type}` : "";
|
|
2172
|
+
return /* @__PURE__ */ jsxs2(
|
|
2173
|
+
"g",
|
|
2174
|
+
{
|
|
2175
|
+
ref: (el) => this.el = el,
|
|
2176
|
+
id: `g3p-seg-${this.id}`,
|
|
2177
|
+
className: `${c("container")} ${typeClass}`.trim(),
|
|
2178
|
+
onClick: this.handleClick.bind(this),
|
|
2179
|
+
onMouseEnter: this.handleMouseEnter.bind(this),
|
|
2180
|
+
onMouseLeave: this.handleMouseLeave.bind(this),
|
|
2181
|
+
onContextMenu: this.handleContextMenu.bind(this),
|
|
2182
|
+
children: [
|
|
2183
|
+
/* @__PURE__ */ jsx3(
|
|
2184
|
+
"path",
|
|
2185
|
+
{
|
|
2186
|
+
d: this.svg,
|
|
2187
|
+
fill: "none",
|
|
2188
|
+
className: c("line"),
|
|
2189
|
+
markerStart: source ? `url(#g3p-marker-${source}-reverse)` : void 0,
|
|
2190
|
+
markerEnd: target ? `url(#g3p-marker-${target})` : void 0
|
|
2191
|
+
}
|
|
2192
|
+
),
|
|
2193
|
+
/* @__PURE__ */ jsx3(
|
|
2194
|
+
"path",
|
|
2195
|
+
{
|
|
2196
|
+
d: this.svg,
|
|
2197
|
+
stroke: "transparent",
|
|
2198
|
+
fill: "none",
|
|
2199
|
+
className: c("hitbox"),
|
|
2200
|
+
style: { cursor: "pointer" }
|
|
2201
|
+
}
|
|
2202
|
+
)
|
|
2203
|
+
]
|
|
2204
|
+
}
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
};
|
|
2208
|
+
|
|
2209
|
+
// src/canvas/canvas.tsx
|
|
2210
|
+
import styles3 from "./canvas.css?raw";
|
|
2211
|
+
import zoomStyles from "./zoom.css?raw";
|
|
2212
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "jsx-dom/jsx-runtime";
|
|
2213
|
+
var log10 = logger("canvas");
|
|
2214
|
+
var themeVarMap = {
|
|
2215
|
+
// Canvas
|
|
2216
|
+
bg: "--g3p-bg",
|
|
2217
|
+
shadow: "--g3p-shadow",
|
|
2218
|
+
// Node
|
|
2219
|
+
border: "--g3p-border",
|
|
2220
|
+
borderHover: "--g3p-border-hover",
|
|
2221
|
+
borderSelected: "--g3p-border-selected",
|
|
2222
|
+
text: "--g3p-text",
|
|
2223
|
+
textMuted: "--g3p-text-muted",
|
|
2224
|
+
// Port
|
|
2225
|
+
bgHover: "--g3p-port-bg-hover",
|
|
2226
|
+
// Edge
|
|
2227
|
+
color: "--g3p-edge-color"
|
|
2228
|
+
};
|
|
2229
|
+
function themeToCSS(theme, selector, prefix = "") {
|
|
2230
|
+
const entries = Object.entries(theme).filter(([_, v]) => v !== void 0);
|
|
2231
|
+
if (!entries.length) return "";
|
|
2232
|
+
let css = `${selector} {
|
|
2233
|
+
`;
|
|
2234
|
+
for (const [key, value] of entries) {
|
|
2235
|
+
let cssVar = themeVarMap[key];
|
|
2236
|
+
if (key === "bg" && prefix === "node") {
|
|
2237
|
+
cssVar = "--g3p-bg-node";
|
|
2238
|
+
} else if (key === "bg" && prefix === "port") {
|
|
2239
|
+
cssVar = "--g3p-port-bg";
|
|
2240
|
+
}
|
|
2241
|
+
if (cssVar) {
|
|
2242
|
+
css += ` ${cssVar}: ${value};
|
|
2243
|
+
`;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
css += "}\n";
|
|
2247
|
+
return css;
|
|
2248
|
+
}
|
|
2249
|
+
var Canvas = class {
|
|
2250
|
+
container;
|
|
2251
|
+
root;
|
|
2252
|
+
group;
|
|
2253
|
+
transform;
|
|
2254
|
+
bounds;
|
|
2255
|
+
measurement;
|
|
2256
|
+
allNodes;
|
|
2257
|
+
curNodes;
|
|
2258
|
+
curSegs;
|
|
2259
|
+
updating;
|
|
2260
|
+
// Pan-zoom state
|
|
2261
|
+
isPanning = false;
|
|
2262
|
+
panStart = null;
|
|
2263
|
+
transformStart = null;
|
|
2264
|
+
panScale = null;
|
|
2265
|
+
zoomControls;
|
|
2266
|
+
constructor(options) {
|
|
2267
|
+
Object.assign(this, options);
|
|
2268
|
+
this.allNodes = /* @__PURE__ */ new Map();
|
|
2269
|
+
this.curNodes = /* @__PURE__ */ new Map();
|
|
2270
|
+
this.curSegs = /* @__PURE__ */ new Map();
|
|
2271
|
+
this.updating = false;
|
|
2272
|
+
this.bounds = { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } };
|
|
2273
|
+
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2274
|
+
this.createMeasurementContainer();
|
|
2275
|
+
this.createCanvasContainer();
|
|
2276
|
+
if (this.panZoom) this.setupPanZoom();
|
|
2277
|
+
}
|
|
2278
|
+
createMeasurementContainer() {
|
|
2279
|
+
this.measurement = document.createElement("div");
|
|
2280
|
+
this.measurement.style.cssText = `
|
|
2281
|
+
position: absolute;
|
|
2282
|
+
left: -9999px;
|
|
2283
|
+
top: -9999px;
|
|
2284
|
+
visibility: hidden;
|
|
2285
|
+
pointer-events: none;
|
|
2286
|
+
`;
|
|
2287
|
+
document.body.appendChild(this.measurement);
|
|
2288
|
+
}
|
|
2289
|
+
getNode(key) {
|
|
2290
|
+
const node = this.allNodes.get(key);
|
|
2291
|
+
if (!node) throw new Error(`node not found: ${key}`);
|
|
2292
|
+
return node;
|
|
2293
|
+
}
|
|
2294
|
+
update() {
|
|
2295
|
+
let bx0 = Infinity, by0 = Infinity;
|
|
2296
|
+
let bx1 = -Infinity, by1 = -Infinity;
|
|
2297
|
+
for (const node of this.curNodes.values()) {
|
|
2298
|
+
const { x, y } = node.pos;
|
|
2299
|
+
const { w, h } = node.data.dims;
|
|
2300
|
+
const nx0 = x, nx1 = x + w;
|
|
2301
|
+
const ny0 = y, ny1 = y + h;
|
|
2302
|
+
bx0 = Math.min(bx0, nx0);
|
|
2303
|
+
by0 = Math.min(by0, ny0);
|
|
2304
|
+
bx1 = Math.max(bx1, nx1);
|
|
2305
|
+
by1 = Math.max(by1, ny1);
|
|
2306
|
+
}
|
|
2307
|
+
this.bounds = { min: { x: bx0, y: by0 }, max: { x: bx1, y: by1 } };
|
|
2308
|
+
this.root.setAttribute("viewBox", this.viewBox());
|
|
2309
|
+
}
|
|
2310
|
+
addNode(gnode) {
|
|
2311
|
+
if (this.curNodes.has(gnode.id))
|
|
2312
|
+
throw new Error("node already exists");
|
|
2313
|
+
const { key } = gnode;
|
|
2314
|
+
let node;
|
|
2315
|
+
if (gnode.isDummy) {
|
|
2316
|
+
node = new Node2(this, gnode, true);
|
|
2317
|
+
node.renderContainer();
|
|
2318
|
+
node.setPos(gnode.pos);
|
|
2319
|
+
this.allNodes.set(key, node);
|
|
2320
|
+
} else {
|
|
2321
|
+
if (!this.allNodes.has(key))
|
|
2322
|
+
throw new Error("node has not been measured");
|
|
2323
|
+
node = this.getNode(key);
|
|
2324
|
+
}
|
|
2325
|
+
this.curNodes.set(gnode.id, node);
|
|
2326
|
+
node.append();
|
|
2327
|
+
}
|
|
2328
|
+
updateNode(gnode) {
|
|
2329
|
+
if (gnode.isDummy) throw new Error("dummy node cannot be updated");
|
|
2330
|
+
const node = this.getNode(gnode.key);
|
|
2331
|
+
const cur = this.curNodes.get(gnode.id);
|
|
2332
|
+
if (cur) cur.remove();
|
|
2333
|
+
this.curNodes.set(gnode.id, node);
|
|
2334
|
+
node.append();
|
|
2335
|
+
}
|
|
2336
|
+
deleteNode(gnode) {
|
|
2337
|
+
const node = this.getNode(gnode.key);
|
|
2338
|
+
this.curNodes.delete(gnode.id);
|
|
2339
|
+
node.remove();
|
|
2340
|
+
}
|
|
2341
|
+
addSeg(gseg, g) {
|
|
2342
|
+
if (this.curSegs.has(gseg.id))
|
|
2343
|
+
throw new Error("seg already exists");
|
|
2344
|
+
const seg = new Seg2(this, gseg, g);
|
|
2345
|
+
this.curSegs.set(gseg.id, seg);
|
|
2346
|
+
seg.append();
|
|
2347
|
+
}
|
|
2348
|
+
updateSeg(gseg) {
|
|
2349
|
+
const seg = this.curSegs.get(gseg.id);
|
|
2350
|
+
if (!seg) throw new Error("seg not found");
|
|
2351
|
+
seg.update(gseg);
|
|
2352
|
+
}
|
|
2353
|
+
deleteSeg(gseg) {
|
|
2354
|
+
const seg = this.curSegs.get(gseg.id);
|
|
2355
|
+
if (!seg) throw new Error("seg not found");
|
|
2356
|
+
this.curSegs.delete(gseg.id);
|
|
2357
|
+
seg.remove();
|
|
2358
|
+
}
|
|
2359
|
+
async measureNodes(nodes) {
|
|
2360
|
+
const newNodes = /* @__PURE__ */ new Map();
|
|
2361
|
+
for (const data of nodes) {
|
|
2362
|
+
const node = new Node2(this, data);
|
|
2363
|
+
newNodes.set(data.data, node);
|
|
2364
|
+
this.measurement.appendChild(node.content);
|
|
2365
|
+
}
|
|
2366
|
+
await new Promise(requestAnimationFrame);
|
|
2367
|
+
const isVertical = this.orientation === "TB" || this.orientation === "BT";
|
|
2368
|
+
for (const node of newNodes.values()) {
|
|
2369
|
+
node.measure(isVertical);
|
|
2370
|
+
const { id, version } = node.data;
|
|
2371
|
+
const key = `${id}:${version}`;
|
|
2372
|
+
this.allNodes.set(key, node);
|
|
2373
|
+
node.renderContainer();
|
|
2374
|
+
}
|
|
2375
|
+
this.measurement.innerHTML = "";
|
|
2376
|
+
return newNodes;
|
|
2377
|
+
}
|
|
2378
|
+
onClick(e) {
|
|
2379
|
+
console.log("click", e);
|
|
2380
|
+
}
|
|
2381
|
+
onContextMenu(e) {
|
|
2382
|
+
console.log("context menu", e);
|
|
2383
|
+
}
|
|
2384
|
+
groupTransform() {
|
|
2385
|
+
return `translate(${this.transform.x}, ${this.transform.y}) scale(${this.transform.scale})`;
|
|
2386
|
+
}
|
|
2387
|
+
viewBox() {
|
|
2388
|
+
const p = this.padding;
|
|
2389
|
+
const x = this.bounds.min.x - p;
|
|
2390
|
+
const y = this.bounds.min.y - p;
|
|
2391
|
+
const w = this.bounds.max.x - this.bounds.min.x + p * 2;
|
|
2392
|
+
const h = this.bounds.max.y - this.bounds.min.y + p * 2;
|
|
2393
|
+
return `${x} ${y} ${w} ${h}`;
|
|
2394
|
+
}
|
|
2395
|
+
generateDynamicStyles() {
|
|
2396
|
+
let css = "";
|
|
2397
|
+
const prefix = this.classPrefix;
|
|
2398
|
+
css += themeToCSS(this.theme, `.${prefix}-canvas-container`);
|
|
2399
|
+
for (const [type, vars] of Object.entries(this.nodeTypes)) {
|
|
2400
|
+
css += themeToCSS(vars, `.${prefix}-node-type-${type}`, "node");
|
|
2401
|
+
}
|
|
2402
|
+
for (const [type, vars] of Object.entries(this.edgeTypes)) {
|
|
2403
|
+
css += themeToCSS(vars, `.${prefix}-edge-type-${type}`);
|
|
2404
|
+
}
|
|
2405
|
+
return css;
|
|
2406
|
+
}
|
|
2407
|
+
createCanvasContainer() {
|
|
2408
|
+
const markerStyleEl = document.createElement("style");
|
|
2409
|
+
markerStyleEl.textContent = default2;
|
|
2410
|
+
document.head.appendChild(markerStyleEl);
|
|
2411
|
+
const zoomStyleEl = document.createElement("style");
|
|
2412
|
+
zoomStyleEl.textContent = zoomStyles;
|
|
2413
|
+
document.head.appendChild(zoomStyleEl);
|
|
2414
|
+
const dynamicStyles = this.generateDynamicStyles();
|
|
2415
|
+
if (dynamicStyles) {
|
|
2416
|
+
const themeStyleEl = document.createElement("style");
|
|
2417
|
+
themeStyleEl.textContent = dynamicStyles;
|
|
2418
|
+
document.head.appendChild(themeStyleEl);
|
|
2419
|
+
}
|
|
2420
|
+
const c = styler("canvas", styles3, this.classPrefix);
|
|
2421
|
+
const colorModeClass = this.colorMode !== "system" ? `g3p-${this.colorMode}` : "";
|
|
2422
|
+
this.container = /* @__PURE__ */ jsx4(
|
|
2423
|
+
"div",
|
|
2424
|
+
{
|
|
2425
|
+
className: `${c("container")} ${colorModeClass}`.trim(),
|
|
2426
|
+
ref: (el) => this.container = el,
|
|
2427
|
+
onContextMenu: this.onContextMenu.bind(this),
|
|
2428
|
+
children: /* @__PURE__ */ jsxs3(
|
|
2429
|
+
"svg",
|
|
2430
|
+
{
|
|
2431
|
+
ref: (el) => this.root = el,
|
|
2432
|
+
className: c("root"),
|
|
2433
|
+
width: this.width,
|
|
2434
|
+
height: this.height,
|
|
2435
|
+
viewBox: this.viewBox(),
|
|
2436
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
2437
|
+
onClick: this.onClick.bind(this),
|
|
2438
|
+
children: [
|
|
2439
|
+
/* @__PURE__ */ jsxs3("defs", { children: [
|
|
2440
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, this.classPrefix, false)),
|
|
2441
|
+
Object.values(markerDefs).map((marker) => marker(this.markerSize, this.classPrefix, true))
|
|
2442
|
+
] }),
|
|
2443
|
+
/* @__PURE__ */ jsx4(
|
|
2444
|
+
"g",
|
|
2445
|
+
{
|
|
2446
|
+
ref: (el) => this.group = el,
|
|
2447
|
+
transform: this.groupTransform()
|
|
2448
|
+
}
|
|
2449
|
+
)
|
|
2450
|
+
]
|
|
2451
|
+
}
|
|
2452
|
+
)
|
|
2453
|
+
}
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2456
|
+
// ==================== Pan-Zoom ====================
|
|
2457
|
+
setupPanZoom() {
|
|
2458
|
+
this.container.addEventListener("wheel", this.onWheel.bind(this), { passive: false });
|
|
2459
|
+
this.container.addEventListener("mousedown", this.onMouseDown.bind(this));
|
|
2460
|
+
document.addEventListener("mousemove", this.onMouseMove.bind(this));
|
|
2461
|
+
document.addEventListener("mouseup", this.onMouseUp.bind(this));
|
|
2462
|
+
this.createZoomControls();
|
|
2463
|
+
}
|
|
2464
|
+
/** Convert screen coordinates to canvas-relative coordinates */
|
|
2465
|
+
screenToCanvas(screen) {
|
|
2466
|
+
const rect = this.container.getBoundingClientRect();
|
|
2467
|
+
return canvasPos(screen.x - rect.left, screen.y - rect.top);
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Get the effective scale from canvas pixels to graph units,
|
|
2471
|
+
* accounting for preserveAspectRatio="xMidYMid meet" which uses
|
|
2472
|
+
* the smaller scale (to fit) and centers the content.
|
|
2473
|
+
*/
|
|
2474
|
+
getEffectiveScale() {
|
|
2475
|
+
const vb = this.currentViewBox();
|
|
2476
|
+
const rect = this.container.getBoundingClientRect();
|
|
2477
|
+
const scaleX = vb.w / rect.width;
|
|
2478
|
+
const scaleY = vb.h / rect.height;
|
|
2479
|
+
const scale = Math.max(scaleX, scaleY);
|
|
2480
|
+
const actualW = rect.width * scale;
|
|
2481
|
+
const actualH = rect.height * scale;
|
|
2482
|
+
const offsetX = (actualW - vb.w) / 2;
|
|
2483
|
+
const offsetY = (actualH - vb.h) / 2;
|
|
2484
|
+
return { scale, offsetX, offsetY };
|
|
2485
|
+
}
|
|
2486
|
+
/** Convert canvas coordinates to graph coordinates */
|
|
2487
|
+
canvasToGraph(canvas) {
|
|
2488
|
+
const vb = this.currentViewBox();
|
|
2489
|
+
const { scale, offsetX, offsetY } = this.getEffectiveScale();
|
|
2490
|
+
return graphPos(
|
|
2491
|
+
vb.x - offsetX + canvas.x * scale,
|
|
2492
|
+
vb.y - offsetY + canvas.y * scale
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
/** Get current viewBox as an object */
|
|
2496
|
+
currentViewBox() {
|
|
2497
|
+
const p = this.padding;
|
|
2498
|
+
const t = this.transform;
|
|
2499
|
+
const baseX = this.bounds.min.x - p;
|
|
2500
|
+
const baseY = this.bounds.min.y - p;
|
|
2501
|
+
const baseW = this.bounds.max.x - this.bounds.min.x + p * 2;
|
|
2502
|
+
const baseH = this.bounds.max.y - this.bounds.min.y + p * 2;
|
|
2503
|
+
const cx = baseX + baseW / 2;
|
|
2504
|
+
const cy = baseY + baseH / 2;
|
|
2505
|
+
const w = baseW / t.scale;
|
|
2506
|
+
const h = baseH / t.scale;
|
|
2507
|
+
const x = cx - w / 2 - t.x;
|
|
2508
|
+
const y = cy - h / 2 - t.y;
|
|
2509
|
+
return { x, y, w, h };
|
|
2510
|
+
}
|
|
2511
|
+
onWheel(e) {
|
|
2512
|
+
e.preventDefault();
|
|
2513
|
+
const zoomFactor = 1.1;
|
|
2514
|
+
const delta = e.deltaY > 0 ? 1 / zoomFactor : zoomFactor;
|
|
2515
|
+
const screenCursor = screenPos(e.clientX, e.clientY);
|
|
2516
|
+
const canvasCursor = this.screenToCanvas(screenCursor);
|
|
2517
|
+
const graphCursor = this.canvasToGraph(canvasCursor);
|
|
2518
|
+
const oldScale = this.transform.scale;
|
|
2519
|
+
const newScale = Math.max(0.1, Math.min(10, oldScale * delta));
|
|
2520
|
+
this.transform.scale = newScale;
|
|
2521
|
+
const newGraphCursor = this.canvasToGraph(canvasCursor);
|
|
2522
|
+
this.transform.x += newGraphCursor.x - graphCursor.x;
|
|
2523
|
+
this.transform.y += newGraphCursor.y - graphCursor.y;
|
|
2524
|
+
this.applyTransform();
|
|
2525
|
+
}
|
|
2526
|
+
onMouseDown(e) {
|
|
2527
|
+
if (e.button !== 0) return;
|
|
2528
|
+
if (e.target.closest(".g3p-zoom-controls")) return;
|
|
2529
|
+
this.isPanning = true;
|
|
2530
|
+
this.panStart = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
2531
|
+
this.transformStart = { ...this.transform };
|
|
2532
|
+
const { scale } = this.getEffectiveScale();
|
|
2533
|
+
this.panScale = { x: scale, y: scale };
|
|
2534
|
+
this.container.style.cursor = "grabbing";
|
|
2535
|
+
e.preventDefault();
|
|
2536
|
+
}
|
|
2537
|
+
onMouseMove(e) {
|
|
2538
|
+
if (!this.isPanning || !this.panStart || !this.transformStart || !this.panScale) return;
|
|
2539
|
+
const current = this.screenToCanvas(screenPos(e.clientX, e.clientY));
|
|
2540
|
+
const dx = current.x - this.panStart.x;
|
|
2541
|
+
const dy = current.y - this.panStart.y;
|
|
2542
|
+
this.transform.x = this.transformStart.x + dx * this.panScale.x;
|
|
2543
|
+
this.transform.y = this.transformStart.y + dy * this.panScale.y;
|
|
2544
|
+
this.applyTransform();
|
|
2545
|
+
}
|
|
2546
|
+
onMouseUp(e) {
|
|
2547
|
+
if (!this.isPanning) return;
|
|
2548
|
+
this.isPanning = false;
|
|
2549
|
+
this.panStart = null;
|
|
2550
|
+
this.transformStart = null;
|
|
2551
|
+
this.panScale = null;
|
|
2552
|
+
this.container.style.cursor = "";
|
|
2553
|
+
}
|
|
2554
|
+
applyTransform() {
|
|
2555
|
+
const vb = this.currentViewBox();
|
|
2556
|
+
this.root.setAttribute("viewBox", `${vb.x} ${vb.y} ${vb.w} ${vb.h}`);
|
|
2557
|
+
this.updateZoomLevel();
|
|
2558
|
+
}
|
|
2559
|
+
createZoomControls() {
|
|
2560
|
+
const c = styler("zoom", zoomStyles, this.classPrefix);
|
|
2561
|
+
this.zoomControls = /* @__PURE__ */ jsxs3("div", { className: c("controls"), children: [
|
|
2562
|
+
/* @__PURE__ */ jsx4("button", { className: c("btn"), onClick: () => this.zoomIn(), children: "+" }),
|
|
2563
|
+
/* @__PURE__ */ jsx4("div", { className: c("level"), id: "g3p-zoom-level", children: "100%" }),
|
|
2564
|
+
/* @__PURE__ */ jsx4("button", { className: c("btn"), onClick: () => this.zoomOut(), children: "\u2212" }),
|
|
2565
|
+
/* @__PURE__ */ jsx4("button", { className: `${c("btn")} ${c("reset")}`, onClick: () => this.zoomReset(), children: "\u27F2" })
|
|
2566
|
+
] });
|
|
2567
|
+
this.container.appendChild(this.zoomControls);
|
|
2568
|
+
}
|
|
2569
|
+
updateZoomLevel() {
|
|
2570
|
+
const level = this.container.querySelector("#g3p-zoom-level");
|
|
2571
|
+
if (level) {
|
|
2572
|
+
level.textContent = `${Math.round(this.transform.scale * 100)}%`;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
zoomIn() {
|
|
2576
|
+
this.transform.scale = Math.min(10, this.transform.scale * 1.2);
|
|
2577
|
+
this.applyTransform();
|
|
2578
|
+
}
|
|
2579
|
+
zoomOut() {
|
|
2580
|
+
this.transform.scale = Math.max(0.1, this.transform.scale / 1.2);
|
|
2581
|
+
this.applyTransform();
|
|
2582
|
+
}
|
|
2583
|
+
zoomReset() {
|
|
2584
|
+
this.transform = { x: 0, y: 0, scale: 1 };
|
|
2585
|
+
this.applyTransform();
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2588
|
+
|
|
2589
|
+
// src/canvas/render-node.tsx
|
|
2590
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "jsx-dom/jsx-runtime";
|
|
2591
|
+
function renderNode(node) {
|
|
2592
|
+
if (typeof node == "string") node = { id: node };
|
|
2593
|
+
const title = node?.title ?? node?.label ?? node?.name ?? node?.text ?? node?.id ?? "?";
|
|
2594
|
+
const detail = node?.detail ?? node?.description ?? node?.subtitle;
|
|
2595
|
+
return /* @__PURE__ */ jsxs4("div", { className: "g3p-node-default", children: [
|
|
2596
|
+
/* @__PURE__ */ jsx5("div", { className: "g3p-node-title", children: title }),
|
|
2597
|
+
detail && /* @__PURE__ */ jsx5("div", { className: "g3p-node-detail", children: detail })
|
|
2598
|
+
] });
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// src/api/defaults.ts
|
|
2602
|
+
function applyDefaults(options) {
|
|
2603
|
+
const { graph: graph2, canvas, props } = defaults();
|
|
2604
|
+
return {
|
|
2605
|
+
graph: { ...graph2, ...options?.graph },
|
|
2606
|
+
canvas: { ...canvas, ...options?.canvas },
|
|
2607
|
+
props: { ...props, ...options?.props }
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
function defaults() {
|
|
2611
|
+
return {
|
|
2612
|
+
graph: {
|
|
2613
|
+
mergeOrder: ["target", "source"],
|
|
2614
|
+
nodeMargin: 15,
|
|
2615
|
+
dummyNodeSize: 15,
|
|
2616
|
+
nodeAlign: "natural",
|
|
2617
|
+
edgeSpacing: 10,
|
|
2618
|
+
turnRadius: 10,
|
|
2619
|
+
orientation: "TB",
|
|
2620
|
+
layerMargin: 5,
|
|
2621
|
+
alignIterations: 5,
|
|
2622
|
+
alignThreshold: 10,
|
|
2623
|
+
separateTrackSets: true,
|
|
2624
|
+
markerSize: 10,
|
|
2625
|
+
layoutSteps: null
|
|
2626
|
+
},
|
|
2627
|
+
canvas: {
|
|
2628
|
+
renderNode,
|
|
2629
|
+
classPrefix: "g3p",
|
|
2630
|
+
width: "100%",
|
|
2631
|
+
height: "100%",
|
|
2632
|
+
padding: 20,
|
|
2633
|
+
editable: false,
|
|
2634
|
+
panZoom: true,
|
|
2635
|
+
markerSize: 10,
|
|
2636
|
+
colorMode: "system",
|
|
2637
|
+
theme: {},
|
|
2638
|
+
nodeTypes: {},
|
|
2639
|
+
edgeTypes: {}
|
|
2640
|
+
},
|
|
2641
|
+
props: {}
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// src/api/updater.ts
|
|
2646
|
+
var Updater = class _Updater {
|
|
2647
|
+
update;
|
|
2648
|
+
constructor() {
|
|
2649
|
+
this.update = {
|
|
2650
|
+
addNodes: [],
|
|
2651
|
+
removeNodes: [],
|
|
2652
|
+
updateNodes: [],
|
|
2653
|
+
addEdges: [],
|
|
2654
|
+
removeEdges: [],
|
|
2655
|
+
updateEdges: []
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
describe(desc) {
|
|
2659
|
+
this.update.description = desc;
|
|
2660
|
+
return this;
|
|
2661
|
+
}
|
|
2662
|
+
addNode(node) {
|
|
2663
|
+
this.update.addNodes.push(node);
|
|
2664
|
+
return this;
|
|
2665
|
+
}
|
|
2666
|
+
deleteNode(node) {
|
|
2667
|
+
this.update.removeNodes.push(node);
|
|
2668
|
+
return this;
|
|
2669
|
+
}
|
|
2670
|
+
updateNode(node) {
|
|
2671
|
+
this.update.updateNodes.push(node);
|
|
2672
|
+
return this;
|
|
2673
|
+
}
|
|
2674
|
+
addEdge(edge) {
|
|
2675
|
+
this.update.addEdges.push(edge);
|
|
2676
|
+
return this;
|
|
2677
|
+
}
|
|
2678
|
+
deleteEdge(edge) {
|
|
2679
|
+
this.update.removeEdges.push(edge);
|
|
2680
|
+
return this;
|
|
2681
|
+
}
|
|
2682
|
+
updateEdge(edge) {
|
|
2683
|
+
this.update.updateEdges.push(edge);
|
|
2684
|
+
return this;
|
|
2685
|
+
}
|
|
2686
|
+
static add(nodes, edges) {
|
|
2687
|
+
const updater = new _Updater();
|
|
2688
|
+
updater.update.addNodes = nodes;
|
|
2689
|
+
updater.update.addEdges = edges;
|
|
2690
|
+
return updater;
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
|
|
2694
|
+
// src/api/api.ts
|
|
2695
|
+
var log11 = logger("api");
|
|
2696
|
+
var API = class {
|
|
2697
|
+
state;
|
|
2698
|
+
seq;
|
|
2699
|
+
index;
|
|
2700
|
+
canvas;
|
|
2701
|
+
options;
|
|
2702
|
+
history;
|
|
2703
|
+
nodeIds;
|
|
2704
|
+
edgeIds;
|
|
2705
|
+
nodeVersions;
|
|
2706
|
+
nextNodeId;
|
|
2707
|
+
nextEdgeId;
|
|
2708
|
+
root;
|
|
2709
|
+
constructor(args) {
|
|
2710
|
+
this.root = args.root;
|
|
2711
|
+
this.options = applyDefaults(args.options);
|
|
2712
|
+
let graph2 = new Graph({ options: this.options.graph });
|
|
2713
|
+
this.state = { graph: graph2, update: null };
|
|
2714
|
+
this.seq = [this.state];
|
|
2715
|
+
this.index = 0;
|
|
2716
|
+
this.nodeIds = /* @__PURE__ */ new Map();
|
|
2717
|
+
this.edgeIds = /* @__PURE__ */ new Map();
|
|
2718
|
+
this.nodeVersions = /* @__PURE__ */ new Map();
|
|
2719
|
+
this.nextNodeId = 1;
|
|
2720
|
+
this.nextEdgeId = 1;
|
|
2721
|
+
this.canvas = new Canvas({
|
|
2722
|
+
...this.options.canvas,
|
|
2723
|
+
dummyNodeSize: this.options.graph.dummyNodeSize,
|
|
2724
|
+
orientation: this.options.graph.orientation
|
|
2725
|
+
});
|
|
2726
|
+
if (args.history) {
|
|
2727
|
+
this.history = args.history;
|
|
2728
|
+
} else if (args.nodes) {
|
|
2729
|
+
this.history = [Updater.add(args.nodes, args.edges || []).update];
|
|
2730
|
+
} else {
|
|
2731
|
+
this.history = [];
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
async init() {
|
|
2735
|
+
const root = document.getElementById(this.root);
|
|
2736
|
+
if (!root) throw new Error("root element not found");
|
|
2737
|
+
root.appendChild(this.canvas.container);
|
|
2738
|
+
for (const update of this.history)
|
|
2739
|
+
await this.applyUpdate(update);
|
|
2740
|
+
}
|
|
2741
|
+
nav(nav) {
|
|
2742
|
+
let newIndex;
|
|
2743
|
+
switch (nav) {
|
|
2744
|
+
case "first":
|
|
2745
|
+
newIndex = 0;
|
|
2746
|
+
break;
|
|
2747
|
+
case "last":
|
|
2748
|
+
newIndex = this.seq.length - 1;
|
|
2749
|
+
break;
|
|
2750
|
+
case "prev":
|
|
2751
|
+
newIndex = this.index - 1;
|
|
2752
|
+
break;
|
|
2753
|
+
case "next":
|
|
2754
|
+
newIndex = this.index + 1;
|
|
2755
|
+
break;
|
|
2756
|
+
}
|
|
2757
|
+
if (newIndex < 0 || newIndex >= this.seq.length || newIndex == this.index)
|
|
2758
|
+
return;
|
|
2759
|
+
this.applyDiff(this.index, newIndex);
|
|
2760
|
+
this.index = newIndex;
|
|
2761
|
+
this.state = this.seq[this.index];
|
|
2762
|
+
}
|
|
2763
|
+
applyDiff(oldIndex, newIndex) {
|
|
2764
|
+
const oldGraph = this.seq[oldIndex].graph;
|
|
2765
|
+
const newGraph = this.seq[newIndex].graph;
|
|
2766
|
+
for (const oldNode of oldGraph.nodes.values()) {
|
|
2767
|
+
const newNode = newGraph.nodes.get(oldNode.id);
|
|
2768
|
+
if (!newNode) this.canvas.deleteNode(oldNode);
|
|
2769
|
+
}
|
|
2770
|
+
for (const newNode of newGraph.nodes.values()) {
|
|
2771
|
+
if (!oldGraph.nodes.has(newNode.id)) this.canvas.addNode(newNode);
|
|
2772
|
+
}
|
|
2773
|
+
for (const oldSeg of oldGraph.segs.values()) {
|
|
2774
|
+
const newSeg = newGraph.segs.get(oldSeg.id);
|
|
2775
|
+
if (!newSeg)
|
|
2776
|
+
this.canvas.deleteSeg(oldSeg);
|
|
2777
|
+
else if (oldSeg.svg != newSeg.svg)
|
|
2778
|
+
this.canvas.updateSeg(newSeg);
|
|
2779
|
+
}
|
|
2780
|
+
for (const newSeg of newGraph.segs.values()) {
|
|
2781
|
+
if (!oldGraph.segs.has(newSeg.id))
|
|
2782
|
+
this.canvas.addSeg(newSeg, newGraph);
|
|
2783
|
+
}
|
|
2784
|
+
this.canvas.update();
|
|
2785
|
+
}
|
|
2786
|
+
async addNode(node) {
|
|
2787
|
+
await this.update((update) => update.addNode(node));
|
|
2788
|
+
}
|
|
2789
|
+
async deleteNode(node) {
|
|
2790
|
+
await this.update((update) => update.deleteNode(node));
|
|
2791
|
+
}
|
|
2792
|
+
async updateNode(node) {
|
|
2793
|
+
await this.update((update) => update.updateNode(node));
|
|
2794
|
+
}
|
|
2795
|
+
async addEdge(edge) {
|
|
2796
|
+
await this.update((update) => update.addEdge(edge));
|
|
2797
|
+
}
|
|
2798
|
+
async deleteEdge(edge) {
|
|
2799
|
+
await this.update((update) => update.deleteEdge(edge));
|
|
2800
|
+
}
|
|
2801
|
+
async applyUpdate(update) {
|
|
2802
|
+
log11.info("applyUpdate", update);
|
|
2803
|
+
const nodes = await this.measureNodes(update);
|
|
2804
|
+
const graph2 = this.state.graph.withMutations((mut) => {
|
|
2805
|
+
for (const edge of update.removeEdges ?? [])
|
|
2806
|
+
this._removeEdge(edge, mut);
|
|
2807
|
+
for (const node of update.removeNodes ?? [])
|
|
2808
|
+
this._removeNode(node, mut);
|
|
2809
|
+
for (const node of update.addNodes ?? [])
|
|
2810
|
+
this._addNode(nodes.get(node), mut);
|
|
2811
|
+
for (const node of update.updateNodes ?? [])
|
|
2812
|
+
this._updateNode(nodes.get(node), mut);
|
|
2813
|
+
for (const edge of update.addEdges ?? [])
|
|
2814
|
+
this._addEdge(edge, mut);
|
|
2815
|
+
for (const edge of update.updateEdges ?? [])
|
|
2816
|
+
this._updateEdge(edge, mut);
|
|
2817
|
+
});
|
|
2818
|
+
this.state = { graph: graph2, update };
|
|
2819
|
+
this.setNodePositions();
|
|
2820
|
+
this.seq.splice(this.index + 1);
|
|
2821
|
+
this.seq.push(this.state);
|
|
2822
|
+
this.nav("last");
|
|
2823
|
+
}
|
|
2824
|
+
setNodePositions() {
|
|
2825
|
+
const { graph: graph2 } = this.state;
|
|
2826
|
+
for (const nodeId of graph2.dirtyNodes) {
|
|
2827
|
+
const node = graph2.getNode(nodeId);
|
|
2828
|
+
if (!node.isDummy)
|
|
2829
|
+
this.canvas.getNode(node.key).setPos(node.pos);
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
async update(callback) {
|
|
2833
|
+
const updater = new Updater();
|
|
2834
|
+
callback(updater);
|
|
2835
|
+
await this.applyUpdate(updater.update);
|
|
2836
|
+
}
|
|
2837
|
+
async measureNodes(update) {
|
|
2838
|
+
const data = [];
|
|
2839
|
+
for (const set of [update.updateNodes, update.addNodes])
|
|
2840
|
+
for (const node of set ?? [])
|
|
2841
|
+
data.push(this.parseNode(node, true));
|
|
2842
|
+
return await this.canvas.measureNodes(data);
|
|
2843
|
+
}
|
|
2844
|
+
parseNode(data, bumpVersion = false) {
|
|
2845
|
+
const get = this.options.props.node;
|
|
2846
|
+
let props;
|
|
2847
|
+
if (get) props = get(data);
|
|
2848
|
+
else if (!data) throw new Error(`invalid node ${data}`);
|
|
2849
|
+
else if (typeof data == "string") props = { id: data };
|
|
2850
|
+
else if (typeof data == "object") props = data;
|
|
2851
|
+
else throw new Error(`invalid node ${data}`);
|
|
2852
|
+
let { id, title, text, type, render } = props;
|
|
2853
|
+
id ??= this.getNodeId(data);
|
|
2854
|
+
const ports = this.parsePorts(props.ports);
|
|
2855
|
+
let version = this.nodeVersions.get(data);
|
|
2856
|
+
if (!version) version = 1;
|
|
2857
|
+
else if (bumpVersion) version++;
|
|
2858
|
+
this.nodeVersions.set(data, version);
|
|
2859
|
+
return { id, data, ports, title, text, type, render, version };
|
|
2860
|
+
}
|
|
2861
|
+
parseEdge(data) {
|
|
2862
|
+
const get = this.options.props.edge;
|
|
2863
|
+
let props;
|
|
2864
|
+
if (get) props = get(data);
|
|
2865
|
+
else if (!data) throw new Error(`invalid edge ${data}`);
|
|
2866
|
+
else if (typeof data == "string") props = this.parseStringEdge(data);
|
|
2867
|
+
else if (typeof data == "object") props = data;
|
|
2868
|
+
else throw new Error(`invalid edge ${data}`);
|
|
2869
|
+
let { id, source, target, type } = props;
|
|
2870
|
+
id ??= this.getEdgeId(data);
|
|
2871
|
+
source = this.parseEdgeEnd(source);
|
|
2872
|
+
target = this.parseEdgeEnd(target);
|
|
2873
|
+
const edge = { id, source, target, type, data };
|
|
2874
|
+
return edge;
|
|
2875
|
+
}
|
|
2876
|
+
parseEdgeEnd(end) {
|
|
2877
|
+
if (!end) throw new Error(`edge has an undefined source or target`);
|
|
2878
|
+
if (typeof end == "string") return { id: end };
|
|
2879
|
+
if (typeof end == "object") {
|
|
2880
|
+
const keys = Object.keys(end);
|
|
2881
|
+
const pidx = keys.indexOf("port");
|
|
2882
|
+
if (pidx != -1) {
|
|
2883
|
+
if (typeof end.port != "string") return end;
|
|
2884
|
+
keys.splice(pidx, 1);
|
|
2885
|
+
}
|
|
2886
|
+
if (keys.length != 1) return end;
|
|
2887
|
+
if (keys[0] == "id") return end;
|
|
2888
|
+
if (keys[0] != "node") return end;
|
|
2889
|
+
const id = this.nodeIds.get(end.node);
|
|
2890
|
+
if (!id) throw new Error(`edge end ${end} references unknown node ${end.node}`);
|
|
2891
|
+
return { id, port: end.port };
|
|
2892
|
+
}
|
|
2893
|
+
throw new Error(`invalid edge end ${end}`);
|
|
2894
|
+
}
|
|
2895
|
+
parseStringEdge(str) {
|
|
2896
|
+
const [source, target] = str.split(/\s*(?::|-+>?)\s*/);
|
|
2897
|
+
return { source, target };
|
|
2898
|
+
}
|
|
2899
|
+
parsePorts(ports) {
|
|
2900
|
+
const fixed = { in: null, out: null };
|
|
2901
|
+
for (const key of ["in", "out"]) {
|
|
2902
|
+
if (ports?.[key] && ports[key].length > 0)
|
|
2903
|
+
fixed[key] = ports[key].map((port) => typeof port == "string" ? { id: port } : port);
|
|
2904
|
+
}
|
|
2905
|
+
return fixed;
|
|
2906
|
+
}
|
|
2907
|
+
getNodeId(node) {
|
|
2908
|
+
let id = this.nodeIds.get(node);
|
|
2909
|
+
if (!id) {
|
|
2910
|
+
id = `n${this.nextNodeId++}`;
|
|
2911
|
+
this.nodeIds.set(node, id);
|
|
2912
|
+
}
|
|
2913
|
+
return id;
|
|
2914
|
+
}
|
|
2915
|
+
getEdgeId(edge) {
|
|
2916
|
+
let id = this.edgeIds.get(edge);
|
|
2917
|
+
if (!id) {
|
|
2918
|
+
id = `e${this.nextEdgeId++}`;
|
|
2919
|
+
this.edgeIds.set(edge, id);
|
|
2920
|
+
}
|
|
2921
|
+
return id;
|
|
2922
|
+
}
|
|
2923
|
+
_addNode(node, mut) {
|
|
2924
|
+
const { data, id: newId } = node.data;
|
|
2925
|
+
const oldId = this.nodeIds.get(data);
|
|
2926
|
+
console.log("addNode", node, oldId, newId);
|
|
2927
|
+
if (oldId && oldId != newId)
|
|
2928
|
+
throw new Error(`node id of ${data} changed from ${oldId} to ${newId}`);
|
|
2929
|
+
this.nodeIds.set(data, newId);
|
|
2930
|
+
mut.addNode(node.data);
|
|
2931
|
+
}
|
|
2932
|
+
_removeNode(node, mut) {
|
|
2933
|
+
const id = this.nodeIds.get(node);
|
|
2934
|
+
if (!id) throw new Error(`removing node ${node} which does not exist`);
|
|
2935
|
+
mut.removeNode({ id });
|
|
2936
|
+
}
|
|
2937
|
+
_updateNode(node, mut) {
|
|
2938
|
+
const { data, id: newId } = node.data;
|
|
2939
|
+
const oldId = this.nodeIds.get(data);
|
|
2940
|
+
if (!oldId) throw new Error(`updating unknown node ${node}`);
|
|
2941
|
+
if (oldId != newId) throw new Error(`node id changed from ${oldId} to ${newId}`);
|
|
2942
|
+
mut.updateNode(node.data);
|
|
2943
|
+
}
|
|
2944
|
+
_addEdge(edge, mut) {
|
|
2945
|
+
const data = this.parseEdge(edge);
|
|
2946
|
+
const id = this.edgeIds.get(edge);
|
|
2947
|
+
if (id && id != data.id)
|
|
2948
|
+
throw new Error(`edge id changed from ${id} to ${data.id}`);
|
|
2949
|
+
this.edgeIds.set(edge, data.id);
|
|
2950
|
+
mut.addEdge(data);
|
|
2951
|
+
}
|
|
2952
|
+
_removeEdge(edge, mut) {
|
|
2953
|
+
const id = this.edgeIds.get(edge);
|
|
2954
|
+
if (!id) throw new Error(`removing edge ${edge} which does not exist`);
|
|
2955
|
+
mut.removeEdge(this.parseEdge(edge));
|
|
2956
|
+
}
|
|
2957
|
+
_updateEdge(edge, mut) {
|
|
2958
|
+
const id = this.edgeIds.get(edge);
|
|
2959
|
+
if (!id) throw new Error(`updating unknown edge ${edge}`);
|
|
2960
|
+
const data = this.parseEdge(edge);
|
|
2961
|
+
if (data.id !== id) throw new Error(`edge id changed from ${id} to ${data.id}`);
|
|
2962
|
+
mut.updateEdge(data);
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
// src/index.ts
|
|
2967
|
+
async function graph(args = { root: "app" }) {
|
|
2968
|
+
const api = new API(args);
|
|
2969
|
+
await api.init();
|
|
2970
|
+
return api;
|
|
2971
|
+
}
|
|
2972
|
+
var index_default = graph;
|
|
1610
2973
|
export {
|
|
1611
|
-
|
|
2974
|
+
index_default as default,
|
|
2975
|
+
graph
|
|
1612
2976
|
};
|