@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 CHANGED
@@ -1,8 +1,7 @@
1
- var __create = Object.create;
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.js
20
+ // src/index.ts
30
21
  var index_exports = {};
31
22
  __export(index_exports, {
32
- Graph: () => Graph
23
+ default: () => index_default,
24
+ graph: () => graph
33
25
  });
34
26
  module.exports = __toCommonJS(index_exports);
35
27
 
36
- // src/graph.js
37
- var import_immutable5 = require("immutable");
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/options.js
40
- var defaultOptions = {
41
- mergeOrder: ["target", "source"],
42
- nodeMargin: 15,
43
- dummyNodeSize: 15,
44
- defaultPortOffset: 20,
45
- nodeAlign: "natural",
46
- // 'natural' || 'top' || 'bottom' || 'left' || 'right'
47
- edgeSpacing: 10,
48
- turnRadius: 10,
49
- graphPadding: 20,
50
- orientation: "TB",
51
- layerMargin: 5,
52
- alignIterations: 5,
53
- alignThreshold: 10,
54
- separateTrackSets: true
55
- };
34
+ // src/graph/types/graph.ts
35
+ var import_immutable8 = require("immutable");
56
36
 
57
- // src/graph-nodes.js
37
+ // src/graph/types/node.ts
58
38
  var import_immutable = require("immutable");
59
-
60
- // src/log.js
61
- var import_winston = __toESM(require("winston"), 1);
62
-
63
- // src/vitest-transport.js
64
- var import_winston_transport = __toESM(require("winston-transport"), 1);
65
- var VitestTransport = class extends import_winston_transport.default {
66
- constructor(opts) {
67
- super(opts);
68
- }
69
- log(info, callback) {
70
- setImmediate(() => {
71
- this.emit("logged", info);
72
- });
73
- const message = info[/* @__PURE__ */ Symbol.for("message")] || info.message;
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
- // src/log.js
92
- var { combine, timestamp, printf, colorize, align } = import_winston.default.format;
93
- var format;
94
- var transports;
95
- switch (process.env.NODE_ENV) {
96
- case "development":
97
- case "test":
98
- format = combine(
99
- colorize(),
100
- align(),
101
- printf((info) => `${info.level}: ${info.module ?? "core"}: ${info.message}`)
102
- );
103
- transports = process.env.VITEST ? [new VitestTransport()] : [new import_winston.default.transports.Console()];
104
- break;
105
- default:
106
- format = combine(
107
- timestamp({
108
- format: "YYYY-MM-DD hh:mm:ss.SSS A"
109
- }),
110
- json()
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
- if (!node.layerId)
143
- node.layerId = this._layerAtIndex(0).id;
144
- this._layerAddNode(node.layerId, node.id);
145
- this.nodes.set(node.id, node);
146
- this._markDirty(node.id);
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
- * Add a dummy node to the graph
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: this._newDummyId(),
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
- width: this.options.dummyNodeSize,
163
- height: this.options.dummyNodeSize
99
+ w: g.options.dummyNodeSize,
100
+ h: g.options.dummyNodeSize
164
101
  }
165
102
  });
166
- },
167
- /**
168
- * Remove a node from the graph
169
- *
170
- * @param {string} nodeId - Node ID
171
- */
172
- _deleteNode(nodeId) {
173
- log2.debug(`deleting node ${nodeId}`);
174
- const node = this.nodes.get(nodeId);
175
- if (!node) return;
176
- this._layerDeleteNode(node.layerId, nodeId);
177
- for (const relId of this._relIds(nodeId))
178
- this._deleteRelById(relId);
179
- this.nodes.delete(nodeId);
180
- },
181
- /**
182
- * Check if a node is unlinked (has no relationships)
183
- *
184
- * @param {string} nodeId - Node ID
185
- * @returns {boolean} True if the node is unlinked, false otherwise
186
- */
187
- _nodeIsUnlinked(nodeId) {
188
- const node = this.nodes.get(nodeId);
189
- if (!node) return false;
190
- return node.edges.in.isEmpty() && node.edges.out.isEmpty() && node.segs.in.isEmpty() && node.segs.out.isEmpty();
191
- },
192
- /**
193
- * Generate a new dummy node ID
194
- *
195
- * @returns {string} A new dummy node ID
196
- */
197
- _newDummyId() {
198
- return `d:${this.nextDummyId++}`;
199
- },
200
- /**
201
- * Check if an ID is a dummy node ID
202
- *
203
- * @param {string} nodeId - ID to check (required)
204
- * @returns {boolean} True if the ID is a dummy node ID, false otherwise
205
- */
206
- _isDummyId(nodeId) {
207
- return nodeId.startsWith("d:");
208
- },
209
- /**
210
- * Iterate over all node IDs
211
- *
212
- * @returns {Iterator} Iterator over node IDs
213
- */
214
- *_nodeIds() {
215
- yield* this.nodes.keySeq();
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-edges.js
220
- var log3 = logger("edges");
221
- var GraphEdges = {
222
- /**
223
- * Get a relationship (edge or segment) by ID
224
- *
225
- * @param {string} relId - Relationship ID
226
- * @returns {Object} The relationship
227
- */
228
- _getRel(relId) {
229
- return relId.startsWith("e:") ? this.getEdge(relId) : this.getSeg(relId);
230
- },
231
- /**
232
- * Generate the ID of an edge or segment. The ID is in the format
233
- *
234
- * {type}:{source.id}[.{source.port}]-[{type}]-{target.id}[.{target.port}]
235
- *
236
- * Examples:
237
- *
238
- * e:n1--n2
239
- * e:n1.out1--n2
240
- * e:n1--n2.in1
241
- * e:n1.out1--n2.in1
242
- * e:n1.out1-bold-n2.in1
243
- *
244
- * @param {Object} obj - Edge or segment
245
- * @param {string} type - Type of relationship
246
- * @returns {string} The relationship ID
247
- */
248
- _edgeSegId(obj, type, side = "both") {
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 = obj.source.id;
252
- if (obj.source.port) source += `.${obj.source.port}`;
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 = "-" + obj.target.id;
257
- if (obj.target.port) target += `.${obj.target.port}`;
364
+ target = edge.target.id;
365
+ if (edge.target.port)
366
+ target = `${target}.${edge.target.port}`;
367
+ target = "-" + target;
258
368
  }
259
- return `${type}:${source}${obj.type || ""}${target}`;
260
- },
261
- /**
262
- * Generate the ID of an edge
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: this._edgeId(props),
397
- segs: []
398
- };
399
- this.edges.set(edge.id, edge);
400
- this._linkEdge(edge);
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-layers.js
562
- var import_immutable2 = require("immutable");
563
- var log4 = logger("layers");
564
- var GraphLayers = {
565
- /**
566
- * Get the index of a node's layer
567
- *
568
- * @param {string} nodeId - Node ID
569
- * @returns {number} The index of the node's layer
570
- */
571
- _nodeLayerIndex(nodeId) {
572
- return this.getLayer(this.getNode(nodeId).layerId).index;
573
- },
574
- /**
575
- * Add a node to a layer.
576
- *
577
- * @param {string} layerId - Layer ID
578
- * @param {string} nodeId - Node ID
579
- */
580
- _layerAddNode(layerId, nodeId) {
581
- const layer = this.getLayer(layerId);
582
- this.layers.set(layerId, {
583
- ...layer,
584
- nodes: layer.nodes.add(nodeId)
585
- });
586
- },
587
- /**
588
- * Remove a node from a layer.
589
- *
590
- * @param {string} layerId - Layer ID
591
- * @param {string} nodeId - Node ID
592
- */
593
- _layerDeleteNode(layerId, nodeId) {
594
- const layer = this.getLayer(layerId);
595
- let sorted = layer.sorted;
596
- if (sorted) {
597
- const idx = sorted.findIndex((id) => id == nodeId);
598
- if (idx >= 0) {
599
- sorted = sorted.filter((id) => id != nodeId);
600
- for (let i = idx; i < sorted.length; i++) {
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
- stack.sort((a, b) => this._nodeLayerIndex(b) - this._nodeLayerIndex(a));
630
- const phase2 = new Set(stack);
631
- const moved = /* @__PURE__ */ new Set();
632
- while (stack.length > 0) {
633
- const id = stack.pop();
634
- const parentIds = [...this._adjIds(id, "edges", "in")];
635
- let correctLayer;
636
- if (parentIds.length == 0) {
637
- correctLayer = 0;
638
- } else {
639
- const maxParent = (0, import_immutable2.Seq)(parentIds).map((id2) => this._nodeLayerIndex(id2)).max();
640
- correctLayer = maxParent + 1;
641
- }
642
- const curLayer = this._nodeLayerIndex(id);
643
- if (curLayer != correctLayer) {
644
- moved.add(id);
645
- this._moveNodeLayer(id, correctLayer);
646
- stack.push(...this._adjIds(id, "edges", "out"));
647
- for (const parentId of parentIds)
648
- phase2.add(parentId);
649
- }
650
- }
651
- const byLayer = /* @__PURE__ */ new Map();
652
- const addParent = (nodeId) => {
653
- let set;
654
- const layerId = this.getNode(nodeId).layerId;
655
- if (!byLayer.has(layerId)) {
656
- set = /* @__PURE__ */ new Set();
657
- byLayer.set(layerId, set);
658
- } else {
659
- set = byLayer.get(layerId);
660
- }
661
- set.add(nodeId);
662
- };
663
- for (const id of phase2) addParent(id);
664
- const layerIds = [...byLayer.keys()].sort(
665
- (a, b) => this.layers.get(b).index - this.layers.get(a).index
666
- );
667
- for (const layerId of layerIds) {
668
- const curLayer = this.layers.get(layerId).index;
669
- for (const parentId of byLayer.get(layerId)) {
670
- const children = [...this._adjIds(parentId, "edges", "out")];
671
- if (children.length == 0) continue;
672
- const minChild = (0, import_immutable2.Seq)(children).map((id) => this._nodeLayerIndex(id)).min();
673
- const correctLayer = minChild - 1;
674
- if (curLayer != correctLayer) {
675
- moved.add(parentId);
676
- this._moveNodeLayer(parentId, correctLayer);
677
- for (const grandParentId of this._adjIds(parentId, "edges", "in"))
678
- addParent(grandParentId);
679
- }
680
- }
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
- for (const id of moved)
683
- for (const edgeId of this._relIds(id, "edges", "both"))
684
- this._dirtyEdges.add(edgeId);
685
- for (const edge of this.changes.addedEdges)
686
- this._dirtyEdges.add(this._edgeId(edge));
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
- this.layerList.push(id);
728
- this.dirtyLayers.add(id);
729
- },
730
- /**
731
- * Check if a layer is empty
732
- *
733
- * @param {string} layerId - Layer ID
734
- * @returns {boolean} True if the layer is empty
735
- */
736
- _layerIsEmpty(layerId) {
737
- return this.layers.get(layerId).nodes.size == 0;
738
- },
739
- /**
740
- * Delete a layer and renumber the remaining layers
741
- *
742
- * @param {string} layerId - Layer ID
743
- */
744
- _deleteLayer(layerId) {
745
- const layer = this.getLayer(layerId);
746
- const index = layer.index;
747
- log4.debug(`deleting layer ${layerId} at index ${index} / ${this.layerList.size}`);
748
- this.layerList.remove(index);
749
- this.layers.delete(layerId);
750
- for (let i = index; i < this.layerList.size; i++) {
751
- const id = this.layerList.get(i);
752
- this.layers.set(id, {
753
- ...this.layers.get(id),
754
- index: i
755
- });
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.js
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 == "string")
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-api.js
800
- var GraphAPI = {
801
- isEmpty() {
802
- return this.nodes.isEmpty();
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
- // src/graph-cycle.js
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
- this._checkCyclesFull();
678
+ _Cycles.checkCyclesFull(g);
974
679
  else
975
- this._checkCyclesIncremental();
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 visit = (nodeId2) => {
986
- colorMap.set(nodeId2, gray);
987
- for (const nextId of this._adjIds(nodeId2, "edges", "out")) {
988
- switch (colorMap.get(nextId) ?? white) {
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 = nextId;
991
- end = nodeId2;
692
+ start = next;
693
+ end = node2;
992
694
  return true;
993
695
  case white:
994
- parentMap.set(nextId, nodeId2);
995
- if (visit(nextId)) return true;
696
+ parentMap.set(next, node2);
697
+ if (visit(next)) return true;
996
698
  }
997
699
  }
998
- colorMap.set(nodeId2, black);
700
+ colorMap.set(node2, black);
999
701
  return false;
1000
702
  };
1001
- for (const nodeId2 of this._nodeIds())
1002
- if ((colorMap.get(nodeId2) ?? white) == white) {
1003
- if (visit(nodeId2)) break;
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 = [this._cycleInfo(start)];
1007
- let nodeId = end;
1008
- while (nodeId != start) {
1009
- cycle.push(this._cycleInfo(nodeId));
1010
- nodeId = parentMap.get(nodeId);
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
- cycle.push(this._cycleInfo(start));
1013
- cycle.reverse();
1014
- const error = new Error(`Cycle detected: ${cycle.join(" \u2192 ")}`);
1015
- error.cycle = cycle;
1016
- throw error;
1017
- },
1018
- /**
1019
- * Check for cycles in the graph incrementally. For each potential
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 = this._findRoute(edge.target.id, edge.source.id);
723
+ const route = _Cycles.findRoute(g, target, source);
1030
724
  if (!route) continue;
1031
- const cycle = route.map((id) => this._cycleInfo(id));
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
- * Find a route from the source to the target.
1040
- *
1041
- * @param {string} sourceId - Source node ID
1042
- * @param {string} targetId - Target node ID
1043
- * @returns {Array} The route, or null if no route exists
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 = [sourceId];
1048
- const visited = /* @__PURE__ */ new Set([sourceId]);
736
+ const queue = [source];
737
+ const visited = /* @__PURE__ */ new Set([source]);
1049
738
  while (queue.length > 0) {
1050
- const nodeId = queue.shift();
1051
- if (nodeId == targetId) {
739
+ const node = queue.shift();
740
+ if (node == target) {
1052
741
  const route = [];
1053
- let currId = targetId;
1054
- while (currId != sourceId) {
1055
- route.push(currId);
1056
- currId = parentMap.get(currId);
742
+ let currNode = target;
743
+ while (currNode != source) {
744
+ route.push(currNode);
745
+ currNode = parentMap.get(currNode);
1057
746
  }
1058
- route.push(sourceId);
747
+ route.push(source);
1059
748
  route.reverse();
1060
749
  return route;
1061
750
  }
1062
- for (const nextId of this._adjIds(nodeId, "edges", "out")) {
1063
- if (!visited.has(nextId)) {
1064
- visited.add(nextId);
1065
- parentMap.set(nextId, nodeId);
1066
- queue.push(nextId);
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-dummy.js
1075
- var import_immutable3 = require("immutable");
1076
- var log5 = logger("dummy");
1077
- var GraphDummy = {
1078
- /**
1079
- * Update dummy nodes and segments. Dummy nodes are inserted along
1080
- * edges that span multiple layers. Segments are one-hop connections
1081
- * between adjacent layers. Edges store an array of their segment IDs.
1082
- * Since segments can be re-used once dummies are merged, the segments
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 = this._nodeLayerIndex(edge.source.id);
1090
- const targetLayer = this._nodeLayerIndex(edge.target.id);
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.segs;
778
+ const segs = edge.segIds;
1095
779
  for (let layerIndex = sourceLayer + 1; layerIndex <= targetLayer; layerIndex++) {
1096
- const layer = this._layerAtIndex(layerIndex);
780
+ const layer = g.layerAt(layerIndex);
1097
781
  while (true) {
1098
782
  const segId = segs[segIndex];
1099
- let seg = segId ? this.getSeg(segId) : null;
1100
- const segLayer = seg ? this._nodeLayerIndex(seg.target.id) : null;
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 = this._addDummy({
1107
- edgeId,
790
+ const dummy = Node.addDummy(g, {
791
+ edgeIds: [edgeId],
1108
792
  layerId: layer.id
1109
793
  });
1110
- target = { ...target, id: dummy.id };
794
+ target = { id: dummy.id };
1111
795
  }
1112
- seg = this._addSeg({ source, target, type, edges: (0, import_immutable3.Set)([edgeId]) });
1113
- log5.debug(`edge ${edgeId}: adding segment ${seg.id} from ${source.id} at layer ${layerIndex - 1} to ${target.id} at layer ${layerIndex}`);
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
- log5.debug(`edge ${edgeId}: removing segment ${seg.id} from layer ${layerIndex - 1} to layer ${layerIndex}`);
1118
- this._segDeleteEdge(segId, edgeId);
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
- log5.debug(`edge ${edgeId}: removing trailing segment ${segs[segIndex]}`);
1130
- this._segDeleteEdge(segs[segIndex], edgeId);
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
- log5.debug(`edge ${edgeId}: updated segments to ${segs.join(", ")}`);
1137
- this.edges.set(edgeId, { ...edge, segs });
820
+ log3.debug(`edge ${edgeId}: updated segments to ${segs.join(", ")}`);
821
+ edge.setSegIds(g, segs);
1138
822
  }
1139
823
  }
1140
- },
1141
- _mergeDummies() {
1142
- for (const side of this.options.mergeOrder)
1143
- this._mergeScan(side);
1144
- },
1145
- _mergeScan(side) {
1146
- let layers = [...this.layerList];
1147
- if (side == "target") layers.reverse();
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
- log5.debug(`merging dummies by ${side}`);
1152
- for (const layerId of layers) {
1153
- let layer = this.layers.get(layerId);
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.nodes) {
1156
- if (!this._isDummyId(nodeId)) continue;
1157
- const node = this.getNode(nodeId);
1158
- if (node.merged) continue;
1159
- const edge = this.getEdge(node.edgeId);
1160
- const key = this._edgeSegId(edge, "k", side);
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 = this._addDummy({ edgeIds, layerId, merged: true });
851
+ const dummy = Node.addDummy(g, { edgeIds, layerId, isMerged: true });
1168
852
  let seg;
1169
853
  for (const old of group) {
1170
- for (const segId of this._relIds(old.id, "segs", dir)) {
854
+ let edge = g.getEdge(old.edgeIds[0]);
855
+ for (const segId of old.relIds("segs", dir)) {
1171
856
  if (!seg) {
1172
- const example = this.getSeg(segId);
1173
- seg = this._addSeg({
857
+ const example = g.getSeg(segId);
858
+ seg = Seg.add(g, {
1174
859
  ...example,
1175
- edges: (0, import_immutable3.Set)([old.edgeId]),
860
+ edgeIds: (0, import_immutable5.Set)([old.edgeId]),
1176
861
  [altSide]: { ...example[altSide], id: dummy.id }
1177
862
  });
1178
863
  }
1179
- this._edgeReplaceSeg(old.edgeId, segId, seg.id);
864
+ edge = edge.replaceSegId(g, segId, seg.id);
1180
865
  }
1181
866
  }
1182
867
  for (const old of group) {
1183
- for (const segId of this._relIds(old.id, "segs", altDir)) {
1184
- const example = this.getSeg(segId);
1185
- const seg2 = this._addSeg({
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
- edges: (0, import_immutable3.Set)([old.edgeId]),
873
+ edgeIds: (0, import_immutable5.Set)([old.edgeId]),
1188
874
  [side]: { ...example[side], id: dummy.id }
1189
875
  });
1190
- this._edgeReplaceSeg(old.edgeId, segId, seg2.id);
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-pos.js
1199
- var import_immutable4 = require("immutable");
1200
- var log6 = logger("pos");
1201
- var GraphPos = {
1202
- /**
1203
- * Find the minimum index of incoming edges to a node
1204
- *
1205
- * @param {Object} node - Node
1206
- * @returns {number} The minimum index of incoming edges
1207
- */
1208
- _parentIndex(node) {
1209
- const parents = (0, import_immutable4.Seq)(this._adjs(node.id, "segs", "in"));
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
- log6.debug(`node ${node.id}: parent index ${pidx}`);
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 = this.getNode(aId);
1227
- const b = this.getNode(bId);
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, import_immutable4.Seq)(a.edgeIds).min();
1232
- const minB = b.edgeId ?? (0, import_immutable4.Seq)(b.edgeIds).min();
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
- * Does a first pass of assigning X and Y positions to nodes.
1237
- * Nodes in each layer are ordered first by the order of their parents, and
1238
- * then by comparing their natural ordering. The Y position is assigned based
1239
- * on the layer index, and the X position is assigned based on the node index
1240
- * within the layer.
1241
- */
1242
- _positionNodes() {
1243
- var _a;
1244
- for (const nodeId of this._dirtyNodes) {
1245
- const node = this.nodes.get(nodeId);
1246
- if (!node) continue;
1247
- const layerId = node.layerId;
1248
- this.dirtyLayers.add(layerId);
1249
- }
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 this.layerList) {
1252
- if (!adjustNext && !this.dirtyLayers.has(layerId)) continue;
977
+ for (const layerId of g.layerList) {
978
+ if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
1253
979
  adjustNext = false;
1254
- const layer = this.getLayer(layerId);
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.nodes)
1257
- pidxs.set(nodeId, this._parentIndex(this.getNode(nodeId)));
1258
- const sorted = [...layer.nodes].sort((a, b) => this._compareNodes(a, b, pidxs));
1259
- if (layer.sorted && sorted.every((nodeId, i) => layer.sorted[i] == nodeId)) continue;
1260
- this.dirtyLayers.add(layerId);
1261
- this.layers.set(layerId, { ...layer, sorted });
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
- const node = this.getNode(sorted[i]);
1266
- log6.debug(`node ${node.id}: final index ${i}`);
1267
- this.nodes.set(node.id, { ...node, index: i, lpos });
1268
- const size = ((_a = node.dims) == null ? void 0 : _a[this._width]) ?? 0;
1269
- lpos += size + this.options.nodeMargin;
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
- * Align the nodes based on either a specified procedure, or a default procedure,
1275
- * which consists of a number of iterations of:
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 < this.options.alignIterations; i++) {
1287
- let anyChanged = this._alignChildren() || this._alignParents() || this._compact();
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
- return this;
1292
- },
1293
- // Align children to their parents.
1294
- //
1295
- // - Sweep layers first to last
1296
- // - Sweep nodes left to right
1297
- // - Move nodes only to the right
1298
- // - On overlap, shift the colliding nodes to the right
1299
- _alignChildren() {
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 && !this.dirtyLayers.has(layerId)) continue;
1027
+ if (!adjustNext && !g.dirtyLayers.has(layerId)) continue;
1334
1028
  adjustNext = false;
1335
1029
  while (true) {
1336
1030
  let changed = false;
1337
- const nodeIds = this._sortLayer(layerId, reverseNodes);
1031
+ const nodeIds = _Layout.sortLayer(g, layerId, reverseNodes);
1338
1032
  for (const nodeId of nodeIds) {
1339
- const { isAligned, pos: newPos, nodeId: otherId } = this._nearestNode(nodeId, dir, reverseMove, !reverseMove);
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 (this._shiftNode(nodeId, otherId, dir, newPos, reverseMove, conservative)) {
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
- this.dirtyLayers.add(layerId);
1046
+ g.dirtyLayers.add(layerId);
1349
1047
  adjustNext = true;
1350
1048
  }
1351
1049
  }
1352
1050
  return anyChanged;
1353
- },
1354
- /**
1355
- * Sort the nodes of a layer by their position and store
1356
- * on layer.sorted. Return the sorted array, reversed if requested.
1357
- *
1358
- * @param {string} layerId - The ID of the layer to sort
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
- * Find the nearest node in the given relation that is in the correct direction.
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 this._rels(nodeId, "segs", dir)) {
1067
+ for (const seg of node.rels(g, "segs", dir)) {
1398
1068
  const altId = seg[altSide].id;
1399
- const myPos = this._anchorPos(seg, mySide)[this._x];
1400
- const altPos = this._anchorPos(seg, altSide)[this._x];
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 = this.getNode(nodeId);
1426
- let p = { [_x]: node.lpos, ...node.pos || {} };
1427
- let w = ((_a = node.dims) == null ? void 0 : _a[this._width]) ?? 0;
1428
- let h = ((_b = node.dims) == null ? void 0 : _b[this._height]) ?? 0;
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 { [_x]: p[_x] + w / 2, [_y]: p[_y] + h / 2 };
1431
- p[_x] += this._nodePortOffset(nodeId, seg[side].port);
1432
- if (side == "source" == this._reverse)
1433
- p[_y] += h;
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
- * Return an offset for a node's port; port is optional.
1438
- *
1439
- * @param {string} nodeId - Node ID to check
1440
- * @param {string} port - The port to compute offset for
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
- this._markAligned(nodeId, alignId, dir, lpos);
1465
- const space = this.options.nodeMargin;
1466
- const nodeWidth = ((_a = node.dims) == null ? void 0 : _a[this._width]) ?? 0;
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 this.getLayer(node.layerId).nodes) {
1112
+ for (const otherId of node.getLayer(g).nodeIds) {
1470
1113
  if (otherId == nodeId) continue;
1471
- const other = this.getNode(otherId);
1114
+ const other = g.getNode(otherId);
1472
1115
  const opos = other.lpos;
1473
- const otherWidth = ((_b = other.dims) == null ? void 0 : _b[this._width]) ?? 0;
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
- this._shiftNode(otherId, void 0, dir, safePos, reverseMove, conservative);
1121
+ _Layout.shiftNode(g, otherId, void 0, dir, safePos, reverseMove, conservative);
1479
1122
  continue repeat;
1480
1123
  }
1481
1124
  }
1482
1125
  if (conservative)
1483
- this._markAligned(nodeId, alignId, dir, lpos);
1126
+ _Layout.markAligned(g, nodeId, alignId, dir, lpos);
1484
1127
  return true;
1485
- },
1486
- /**
1487
- * Mark nodes as aligned, unlinking any existing alignment.
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
- const ex = this.getNode(node.aligned[dir]);
1499
- this.nodes.set(node.aligned[dir], { ...ex, aligned: { ...ex.aligned, [alt]: void 0 } });
1500
- }
1501
- if (otherId) {
1502
- const other = this.getNode(otherId);
1503
- this.nodes.set(otherId, { ...other, aligned: { ...other.aligned, [alt]: nodeId } });
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 = this.getNode(otherId);
1142
+ const other = g.getNode(otherId);
1520
1143
  yield other;
1521
- yield* visit.call(this, other, dir2);
1522
- }.bind(this);
1523
- const node = this.getNode(nodeId);
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 this.getLayer(node.layerId).sorted[node.index - 1];
1541
- },
1542
- /**
1543
- * Get the node id immediately to the right of the given node in the same layer
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 this.layerList) {
1560
- const layer = this.getLayer(layerId);
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 = this.getNode(nodeId);
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 this._aligned(nodeId, "both")) {
1174
+ for (const right of _Layout.aligned(g, nodeId, "both")) {
1568
1175
  stack.push(right);
1569
- const leftId = this._leftOf(right);
1176
+ const leftId = _Layout.leftOf(g, right);
1570
1177
  if (!leftId) return;
1571
- const left = this.getNode(leftId);
1572
- const leftWidth = ((_a = left.dims) == null ? void 0 : _a[this._width]) ?? 0;
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 - this.options.nodeMargin;
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
- this.nodes.set(right.id, { ...right, lpos: right.lpos - delta });
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.js
1588
- var Graph = class {
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.nodes = (prior == null ? void 0 : prior.nodes) ?? (0, import_immutable5.Map)();
1591
- this.edges = (prior == null ? void 0 : prior.edges) ?? (0, import_immutable5.Map)();
1592
- this.layers = (prior == null ? void 0 : prior.layers) ?? (0, import_immutable5.Map)();
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.prior = prior;
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
- ...(prior == null ? void 0 : prior.options) ?? {},
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._dirty = this.changes.addedNodes.length > 0 || this.changes.removedNodes.length > 0 || this.changes.addedEdges.length > 0 || this.changes.removedEdges.length > 0;
1615
- this._reverse = this.options.orientation === "BT" || this.options.orientation === "RL";
1616
- this._vertical = this.options.orientation === "TB" || this.options.orientation === "BT";
1617
- this._height = this._vertical ? "height" : "width";
1618
- this._width = this._vertical ? "width" : "height";
1619
- this._x = this._vertical ? "x" : "y";
1620
- this._y = this._vertical ? "y" : "x";
1621
- this._d = {
1622
- x: this._vertical ? 0 : this._reverse ? -1 : 1,
1623
- y: this._vertical ? this._reverse ? -1 : 1 : 0
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._natural = true;
1530
+ this.n = true;
1628
1531
  else
1629
- this._natural = natAligns[this.options.orientation] == this.options.nodeAlign;
1630
- if (this._dirty) this._update();
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
- var mixins = [
1634
- GraphNodes,
1635
- GraphEdges,
1636
- GraphLayers,
1637
- GraphAPI,
1638
- GraphMutate,
1639
- GraphCycle,
1640
- GraphDummy,
1641
- GraphPos
1642
- ];
1643
- for (const mixin of mixins)
1644
- Object.assign(Graph.prototype, mixin);
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
- Graph
2523
+ graph
1648
2524
  });