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