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