@likec4/core 1.46.4 → 1.48.0
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/LICENSE +1 -1
- package/dist/_chunks/Builder.view-element.mjs +3284 -0
- package/dist/_chunks/LikeC4Model.d.mts +1287 -0
- package/dist/_chunks/LikeC4Styles.mjs +691 -0
- package/dist/_chunks/RichText.d.mts +84 -0
- package/dist/_chunks/RichText.mjs +148 -0
- package/dist/_chunks/_aux.d.mts +289 -0
- package/dist/_chunks/_aux.mjs +18 -0
- package/dist/_chunks/_types.d.mts +781 -0
- package/dist/_chunks/_types2.d.mts +38 -0
- package/dist/_chunks/assignTagColors.mjs +24 -0
- package/dist/_chunks/calcDriftsFromSnapshot.mjs +379 -0
- package/dist/_chunks/connection.mjs +0 -0
- package/dist/_chunks/const.d.mts +43 -0
- package/dist/_chunks/const.mjs +33 -0
- package/dist/_chunks/expression-model.d.mts +192 -0
- package/dist/_chunks/expression-model.mjs +103 -0
- package/dist/_chunks/expression.d.mts +347 -0
- package/dist/_chunks/expression.mjs +107 -0
- package/dist/_chunks/find.d.mts +82 -0
- package/dist/_chunks/find.mjs +646 -0
- package/dist/_chunks/find2.d.mts +78 -0
- package/dist/_chunks/find2.mjs +287 -0
- package/dist/_chunks/fqnRef.d.mts +58 -0
- package/dist/_chunks/fqnRef.mjs +41 -0
- package/dist/_chunks/geometry.mjs +335 -0
- package/dist/_chunks/index.d.mts +169 -0
- package/dist/_chunks/index2.d.mts +937 -0
- package/dist/_chunks/index3.d.mts +146 -0
- package/dist/_chunks/index4.d.mts +62 -0
- package/dist/_chunks/index5.d.mts +42 -0
- package/dist/_chunks/index6.d.mts +43 -0
- package/dist/{shared/core.OtRNdMxy.d.mts → _chunks/index7.d.mts} +113 -197
- package/dist/_chunks/index8.d.mts +144 -0
- package/dist/_chunks/invariant.mjs +18 -0
- package/dist/_chunks/iterable.mjs +146 -0
- package/dist/_chunks/libs/@mantine/colors-generator.mjs +3416 -0
- package/dist/_chunks/libs/@ungap/structured-clone.mjs +213 -0
- package/dist/_chunks/libs/bail.mjs +14 -0
- package/dist/_chunks/libs/ccount.mjs +25 -0
- package/dist/_chunks/libs/character-entities-html4.mjs +263 -0
- package/dist/_chunks/libs/character-entities-legacy.mjs +117 -0
- package/dist/_chunks/libs/character-entities.mjs +2136 -0
- package/dist/_chunks/libs/comma-separated-tokens.mjs +18 -0
- package/dist/_chunks/libs/decode-named-character-reference.mjs +21 -0
- package/dist/_chunks/libs/defu.mjs +42 -0
- package/dist/_chunks/libs/devlop.mjs +5 -0
- package/dist/_chunks/libs/escape-string-regexp.mjs +8 -0
- package/dist/_chunks/libs/extend.mjs +77 -0
- package/dist/_chunks/libs/graphology-dag.mjs +703 -0
- package/dist/_chunks/libs/graphology-types.d.mts +601 -0
- package/dist/_chunks/libs/graphology.d.mts +6 -0
- package/dist/_chunks/libs/graphology.mjs +4565 -0
- package/dist/_chunks/libs/hast-util-sanitize.mjs +863 -0
- package/dist/_chunks/libs/hast-util-to-html.mjs +2696 -0
- package/dist/_chunks/libs/is-plain-obj.mjs +9 -0
- package/dist/_chunks/libs/longest-streak.mjs +30 -0
- package/dist/_chunks/libs/markdown-table.mjs +290 -0
- package/dist/_chunks/libs/mdast-util-find-and-replace.mjs +653 -0
- package/dist/_chunks/libs/mdast-util-from-markdown.mjs +8522 -0
- package/dist/_chunks/libs/mdast-util-gfm.mjs +2393 -0
- package/dist/_chunks/libs/mdast-util-to-hast.mjs +1803 -0
- package/dist/_chunks/libs/micromark-extension-gfm.mjs +2243 -0
- package/dist/_chunks/libs/mnemonist.d.mts +254 -0
- package/dist/_chunks/libs/mnemonist.mjs +1978 -0
- package/dist/_chunks/libs/natural-compare-lite.mjs +42 -0
- package/dist/_chunks/libs/rehype-sanitize.mjs +29 -0
- package/dist/_chunks/libs/rehype-stringify.mjs +34 -0
- package/dist/_chunks/libs/remark-gfm.mjs +36 -0
- package/dist/_chunks/libs/remark-github-blockquote-alert.mjs +100 -0
- package/dist/_chunks/libs/remark-parse.mjs +39 -0
- package/dist/_chunks/libs/remark-rehype.mjs +171 -0
- package/dist/_chunks/libs/remeda.mjs +702 -0
- package/dist/_chunks/libs/trough.mjs +158 -0
- package/dist/_chunks/libs/unified.mjs +2212 -0
- package/dist/_chunks/mnemonist.d.mts +1 -0
- package/dist/_chunks/mnemonist.mjs +0 -0
- package/dist/_chunks/model.mjs +1898 -0
- package/dist/_chunks/model2.mjs +0 -0
- package/dist/_chunks/relationships-view.mjs +132 -0
- package/dist/_chunks/rolldown-runtime.mjs +42 -0
- package/dist/{shared/core.C_3y3eqV.d.mts → _chunks/scalar.d.mts} +13 -43
- package/dist/_chunks/scalar.mjs +84 -0
- package/dist/_chunks/utils.mjs +483 -0
- package/dist/builder/index.d.mts +11 -773
- package/dist/builder/index.mjs +631 -978
- package/dist/compute-view/index.d.mts +90 -27
- package/dist/compute-view/index.mjs +253 -83
- package/dist/compute-view/relationships-view/index.d.mts +16 -51
- package/dist/compute-view/relationships-view/index.mjs +6 -184
- package/dist/geometry/index.d.mts +2 -109
- package/dist/geometry/index.mjs +2 -327
- package/dist/index.d.mts +18 -20
- package/dist/index.mjs +27 -18
- package/dist/model/connection/deployment/index.d.mts +12 -15
- package/dist/model/connection/deployment/index.mjs +9 -2
- package/dist/model/connection/index.d.mts +14 -75
- package/dist/model/connection/index.mjs +10 -3
- package/dist/model/connection/model/index.d.mts +12 -15
- package/dist/model/connection/model/index.mjs +8 -2
- package/dist/model/index.d.mts +15 -57
- package/dist/model/index.mjs +11 -4
- package/dist/styles/index.d.mts +9 -117
- package/dist/styles/index.mjs +26 -59
- package/dist/types/RichText.d.mts +3 -0
- package/dist/types/RichText.mjs +7 -0
- package/dist/types/_aux.d.mts +3 -4
- package/dist/types/_aux.mjs +2 -7
- package/dist/types/expression-model.d.mts +6 -190
- package/dist/types/expression-model.mjs +6 -108
- package/dist/types/expression.d.mts +5 -6
- package/dist/types/expression.mjs +6 -112
- package/dist/types/fqnRef.d.mts +4 -60
- package/dist/types/fqnRef.mjs +6 -44
- package/dist/types/index.d.mts +9 -47
- package/dist/types/index.mjs +14 -16
- package/dist/types/scalar.d.mts +2 -2
- package/dist/types/scalar.mjs +7 -4
- package/dist/utils/graphology/index.d.mts +2 -1864
- package/dist/utils/graphology/index.mjs +3 -1
- package/dist/utils/index.d.mts +11 -124
- package/dist/utils/index.mjs +16 -43
- package/dist/utils/iterable/index.d.mts +9 -132
- package/dist/utils/iterable/index.mjs +2 -61
- package/dist/utils/mnemonist.d.mts +2 -0
- package/dist/utils/mnemonist.mjs +12 -0
- package/package.json +18 -22
- package/compute-view/relationships/package.json +0 -4
- package/dist/shared/core.3ZhnKAlZ.mjs +0 -106
- package/dist/shared/core.78IWAHAE.mjs +0 -332
- package/dist/shared/core.B85MJLTf.mjs +0 -5
- package/dist/shared/core.BFd1lU9R.mjs +0 -29
- package/dist/shared/core.BKlholso.mjs +0 -3
- package/dist/shared/core.BPGOjone.d.mts +0 -46
- package/dist/shared/core.BYdtmqY5.mjs +0 -1255
- package/dist/shared/core.BZCgljZd.d.mts +0 -83
- package/dist/shared/core.BbE4y-yl.mjs +0 -177
- package/dist/shared/core.BhNcrwzK.mjs +0 -7
- package/dist/shared/core.BkVA6gjl.mjs +0 -5
- package/dist/shared/core.BoYfZOBm.mjs +0 -3
- package/dist/shared/core.BsSXS-_R.mjs +0 -7
- package/dist/shared/core.BspBF3SJ.mjs +0 -3944
- package/dist/shared/core.BvPm_-u3.d.mts +0 -334
- package/dist/shared/core.C-Jdx7xo.d.mts +0 -1263
- package/dist/shared/core.C3JowhaZ.mjs +0 -32
- package/dist/shared/core.C4x8aaWa.mjs +0 -27
- package/dist/shared/core.CBLX4mT8.mjs +0 -28411
- package/dist/shared/core.CLSZtnFh.mjs +0 -69
- package/dist/shared/core.CRhT4Iio.mjs +0 -785
- package/dist/shared/core.CbT8JVYL.mjs +0 -5
- package/dist/shared/core.CdNCAwq7.mjs +0 -176
- package/dist/shared/core.ClTP6-TN.d.mts +0 -87
- package/dist/shared/core.CmGY9blO.mjs +0 -6717
- package/dist/shared/core.CmPXpJ8l.mjs +0 -21
- package/dist/shared/core.Coi_LOUZ.mjs +0 -7
- package/dist/shared/core.CqAJJpK_.mjs +0 -3
- package/dist/shared/core.Ctj2S3bD.d.mts +0 -466
- package/dist/shared/core.CywrQs86.d.mts +0 -31
- package/dist/shared/core.D00YbOhQ.d.mts +0 -300
- package/dist/shared/core.D0WZutNd.mjs +0 -94
- package/dist/shared/core.D4npX2q8.mjs +0 -18
- package/dist/shared/core.DB0Ue5Wx.d.mts +0 -70
- package/dist/shared/core.DEK-BO3_.mjs +0 -1206
- package/dist/shared/core.DH7TXqXf.mjs +0 -136
- package/dist/shared/core.DRJma0Ol.mjs +0 -22
- package/dist/shared/core.DSalPD6v.mjs +0 -1352
- package/dist/shared/core.DVfPJPJj.mjs +0 -2200
- package/dist/shared/core.DXhDWKFZ.d.mts +0 -3
- package/dist/shared/core.DbfO90vF.mjs +0 -434
- package/dist/shared/core.DcS-0zys.mjs +0 -10
- package/dist/shared/core.DeDCYTDR.d.mts +0 -98
- package/dist/shared/core.DpKY_3NF.d.mts +0 -504
- package/dist/shared/core.Dt0v3djy.d.mts +0 -41
- package/dist/shared/core.FZ305VYd.mjs +0 -21
- package/dist/shared/core.RfnaEmgA.mjs +0 -7
- package/dist/shared/core.oSV6SRfk.mjs +0 -13
- package/dist/shared/core.om36dXXW.mjs +0 -41
- package/dist/shared/core.q10OP9oJ.mjs +0 -5
- package/dist/shared/core.uFk_o9X6.mjs +0 -13
- package/dist/shared/core.xeuDd__E.mjs +0 -300
- package/dist/shared/core.xgoPk2RG.mjs +0 -4989
|
@@ -0,0 +1,4565 @@
|
|
|
1
|
+
//#region \0polyfill-node.events.js
|
|
2
|
+
var domain;
|
|
3
|
+
function EventHandlers() {}
|
|
4
|
+
EventHandlers.prototype = Object.create(null);
|
|
5
|
+
function EventEmitter() {
|
|
6
|
+
EventEmitter.init.call(this);
|
|
7
|
+
}
|
|
8
|
+
EventEmitter.EventEmitter = EventEmitter;
|
|
9
|
+
EventEmitter.usingDomains = false;
|
|
10
|
+
EventEmitter.prototype.domain = void 0;
|
|
11
|
+
EventEmitter.prototype._events = void 0;
|
|
12
|
+
EventEmitter.prototype._maxListeners = void 0;
|
|
13
|
+
EventEmitter.defaultMaxListeners = 10;
|
|
14
|
+
EventEmitter.init = function() {
|
|
15
|
+
this.domain = null;
|
|
16
|
+
if (EventEmitter.usingDomains) {
|
|
17
|
+
if (domain.active && !(this instanceof domain.Domain)) this.domain = domain.active;
|
|
18
|
+
}
|
|
19
|
+
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
|
|
20
|
+
this._events = new EventHandlers();
|
|
21
|
+
this._eventsCount = 0;
|
|
22
|
+
}
|
|
23
|
+
this._maxListeners = this._maxListeners || void 0;
|
|
24
|
+
};
|
|
25
|
+
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
|
|
26
|
+
if (typeof n !== "number" || n < 0 || isNaN(n)) throw new TypeError("\"n\" argument must be a positive number");
|
|
27
|
+
this._maxListeners = n;
|
|
28
|
+
return this;
|
|
29
|
+
};
|
|
30
|
+
function $getMaxListeners(that) {
|
|
31
|
+
if (that._maxListeners === void 0) return EventEmitter.defaultMaxListeners;
|
|
32
|
+
return that._maxListeners;
|
|
33
|
+
}
|
|
34
|
+
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
|
|
35
|
+
return $getMaxListeners(this);
|
|
36
|
+
};
|
|
37
|
+
function emitNone(handler, isFn, self) {
|
|
38
|
+
if (isFn) handler.call(self);
|
|
39
|
+
else {
|
|
40
|
+
var len = handler.length;
|
|
41
|
+
var listeners = arrayClone(handler, len);
|
|
42
|
+
for (var i = 0; i < len; ++i) listeners[i].call(self);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function emitOne(handler, isFn, self, arg1) {
|
|
46
|
+
if (isFn) handler.call(self, arg1);
|
|
47
|
+
else {
|
|
48
|
+
var len = handler.length;
|
|
49
|
+
var listeners = arrayClone(handler, len);
|
|
50
|
+
for (var i = 0; i < len; ++i) listeners[i].call(self, arg1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function emitTwo(handler, isFn, self, arg1, arg2) {
|
|
54
|
+
if (isFn) handler.call(self, arg1, arg2);
|
|
55
|
+
else {
|
|
56
|
+
var len = handler.length;
|
|
57
|
+
var listeners = arrayClone(handler, len);
|
|
58
|
+
for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function emitThree(handler, isFn, self, arg1, arg2, arg3) {
|
|
62
|
+
if (isFn) handler.call(self, arg1, arg2, arg3);
|
|
63
|
+
else {
|
|
64
|
+
var len = handler.length;
|
|
65
|
+
var listeners = arrayClone(handler, len);
|
|
66
|
+
for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2, arg3);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function emitMany(handler, isFn, self, args) {
|
|
70
|
+
if (isFn) handler.apply(self, args);
|
|
71
|
+
else {
|
|
72
|
+
var len = handler.length;
|
|
73
|
+
var listeners = arrayClone(handler, len);
|
|
74
|
+
for (var i = 0; i < len; ++i) listeners[i].apply(self, args);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
EventEmitter.prototype.emit = function emit(type) {
|
|
78
|
+
var er, handler, len, args, i, events, domain;
|
|
79
|
+
var needDomainExit = false;
|
|
80
|
+
var doError = type === "error";
|
|
81
|
+
events = this._events;
|
|
82
|
+
if (events) doError = doError && events.error == null;
|
|
83
|
+
else if (!doError) return false;
|
|
84
|
+
domain = this.domain;
|
|
85
|
+
if (doError) {
|
|
86
|
+
er = arguments[1];
|
|
87
|
+
if (domain) {
|
|
88
|
+
if (!er) er = /* @__PURE__ */ new Error("Uncaught, unspecified \"error\" event");
|
|
89
|
+
er.domainEmitter = this;
|
|
90
|
+
er.domain = domain;
|
|
91
|
+
er.domainThrown = false;
|
|
92
|
+
domain.emit("error", er);
|
|
93
|
+
} else if (er instanceof Error) throw er;
|
|
94
|
+
else {
|
|
95
|
+
var err = /* @__PURE__ */ new Error("Uncaught, unspecified \"error\" event. (" + er + ")");
|
|
96
|
+
err.context = er;
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
handler = events[type];
|
|
102
|
+
if (!handler) return false;
|
|
103
|
+
var isFn = typeof handler === "function";
|
|
104
|
+
len = arguments.length;
|
|
105
|
+
switch (len) {
|
|
106
|
+
case 1:
|
|
107
|
+
emitNone(handler, isFn, this);
|
|
108
|
+
break;
|
|
109
|
+
case 2:
|
|
110
|
+
emitOne(handler, isFn, this, arguments[1]);
|
|
111
|
+
break;
|
|
112
|
+
case 3:
|
|
113
|
+
emitTwo(handler, isFn, this, arguments[1], arguments[2]);
|
|
114
|
+
break;
|
|
115
|
+
case 4:
|
|
116
|
+
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
args = new Array(len - 1);
|
|
120
|
+
for (i = 1; i < len; i++) args[i - 1] = arguments[i];
|
|
121
|
+
emitMany(handler, isFn, this, args);
|
|
122
|
+
}
|
|
123
|
+
if (needDomainExit) domain.exit();
|
|
124
|
+
return true;
|
|
125
|
+
};
|
|
126
|
+
function _addListener(target, type, listener, prepend) {
|
|
127
|
+
var m;
|
|
128
|
+
var events;
|
|
129
|
+
var existing;
|
|
130
|
+
if (typeof listener !== "function") throw new TypeError("\"listener\" argument must be a function");
|
|
131
|
+
events = target._events;
|
|
132
|
+
if (!events) {
|
|
133
|
+
events = target._events = new EventHandlers();
|
|
134
|
+
target._eventsCount = 0;
|
|
135
|
+
} else {
|
|
136
|
+
if (events.newListener) {
|
|
137
|
+
target.emit("newListener", type, listener.listener ? listener.listener : listener);
|
|
138
|
+
events = target._events;
|
|
139
|
+
}
|
|
140
|
+
existing = events[type];
|
|
141
|
+
}
|
|
142
|
+
if (!existing) {
|
|
143
|
+
existing = events[type] = listener;
|
|
144
|
+
++target._eventsCount;
|
|
145
|
+
} else {
|
|
146
|
+
if (typeof existing === "function") existing = events[type] = prepend ? [listener, existing] : [existing, listener];
|
|
147
|
+
else if (prepend) existing.unshift(listener);
|
|
148
|
+
else existing.push(listener);
|
|
149
|
+
if (!existing.warned) {
|
|
150
|
+
m = $getMaxListeners(target);
|
|
151
|
+
if (m && m > 0 && existing.length > m) {
|
|
152
|
+
existing.warned = true;
|
|
153
|
+
var w = /* @__PURE__ */ new Error("Possible EventEmitter memory leak detected. " + existing.length + " " + type + " listeners added. Use emitter.setMaxListeners() to increase limit");
|
|
154
|
+
w.name = "MaxListenersExceededWarning";
|
|
155
|
+
w.emitter = target;
|
|
156
|
+
w.type = type;
|
|
157
|
+
w.count = existing.length;
|
|
158
|
+
emitWarning(w);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return target;
|
|
163
|
+
}
|
|
164
|
+
function emitWarning(e) {
|
|
165
|
+
typeof console.warn === "function" ? console.warn(e) : console.log(e);
|
|
166
|
+
}
|
|
167
|
+
EventEmitter.prototype.addListener = function addListener(type, listener) {
|
|
168
|
+
return _addListener(this, type, listener, false);
|
|
169
|
+
};
|
|
170
|
+
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
|
171
|
+
EventEmitter.prototype.prependListener = function prependListener(type, listener) {
|
|
172
|
+
return _addListener(this, type, listener, true);
|
|
173
|
+
};
|
|
174
|
+
function _onceWrap(target, type, listener) {
|
|
175
|
+
var fired = false;
|
|
176
|
+
function g() {
|
|
177
|
+
target.removeListener(type, g);
|
|
178
|
+
if (!fired) {
|
|
179
|
+
fired = true;
|
|
180
|
+
listener.apply(target, arguments);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
g.listener = listener;
|
|
184
|
+
return g;
|
|
185
|
+
}
|
|
186
|
+
EventEmitter.prototype.once = function once(type, listener) {
|
|
187
|
+
if (typeof listener !== "function") throw new TypeError("\"listener\" argument must be a function");
|
|
188
|
+
this.on(type, _onceWrap(this, type, listener));
|
|
189
|
+
return this;
|
|
190
|
+
};
|
|
191
|
+
EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) {
|
|
192
|
+
if (typeof listener !== "function") throw new TypeError("\"listener\" argument must be a function");
|
|
193
|
+
this.prependListener(type, _onceWrap(this, type, listener));
|
|
194
|
+
return this;
|
|
195
|
+
};
|
|
196
|
+
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
|
|
197
|
+
var list, events, position, i, originalListener;
|
|
198
|
+
if (typeof listener !== "function") throw new TypeError("\"listener\" argument must be a function");
|
|
199
|
+
events = this._events;
|
|
200
|
+
if (!events) return this;
|
|
201
|
+
list = events[type];
|
|
202
|
+
if (!list) return this;
|
|
203
|
+
if (list === listener || list.listener && list.listener === listener) if (--this._eventsCount === 0) this._events = new EventHandlers();
|
|
204
|
+
else {
|
|
205
|
+
delete events[type];
|
|
206
|
+
if (events.removeListener) this.emit("removeListener", type, list.listener || listener);
|
|
207
|
+
}
|
|
208
|
+
else if (typeof list !== "function") {
|
|
209
|
+
position = -1;
|
|
210
|
+
for (i = list.length; i-- > 0;) if (list[i] === listener || list[i].listener && list[i].listener === listener) {
|
|
211
|
+
originalListener = list[i].listener;
|
|
212
|
+
position = i;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
if (position < 0) return this;
|
|
216
|
+
if (list.length === 1) {
|
|
217
|
+
list[0] = void 0;
|
|
218
|
+
if (--this._eventsCount === 0) {
|
|
219
|
+
this._events = new EventHandlers();
|
|
220
|
+
return this;
|
|
221
|
+
} else delete events[type];
|
|
222
|
+
} else spliceOne(list, position);
|
|
223
|
+
if (events.removeListener) this.emit("removeListener", type, originalListener || listener);
|
|
224
|
+
}
|
|
225
|
+
return this;
|
|
226
|
+
};
|
|
227
|
+
EventEmitter.prototype.off = function(type, listener) {
|
|
228
|
+
return this.removeListener(type, listener);
|
|
229
|
+
};
|
|
230
|
+
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
|
|
231
|
+
var listeners, events = this._events;
|
|
232
|
+
if (!events) return this;
|
|
233
|
+
if (!events.removeListener) {
|
|
234
|
+
if (arguments.length === 0) {
|
|
235
|
+
this._events = new EventHandlers();
|
|
236
|
+
this._eventsCount = 0;
|
|
237
|
+
} else if (events[type]) if (--this._eventsCount === 0) this._events = new EventHandlers();
|
|
238
|
+
else delete events[type];
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
if (arguments.length === 0) {
|
|
242
|
+
var keys = Object.keys(events);
|
|
243
|
+
for (var i = 0, key; i < keys.length; ++i) {
|
|
244
|
+
key = keys[i];
|
|
245
|
+
if (key === "removeListener") continue;
|
|
246
|
+
this.removeAllListeners(key);
|
|
247
|
+
}
|
|
248
|
+
this.removeAllListeners("removeListener");
|
|
249
|
+
this._events = new EventHandlers();
|
|
250
|
+
this._eventsCount = 0;
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
listeners = events[type];
|
|
254
|
+
if (typeof listeners === "function") this.removeListener(type, listeners);
|
|
255
|
+
else if (listeners) do
|
|
256
|
+
this.removeListener(type, listeners[listeners.length - 1]);
|
|
257
|
+
while (listeners[0]);
|
|
258
|
+
return this;
|
|
259
|
+
};
|
|
260
|
+
EventEmitter.prototype.listeners = function listeners(type) {
|
|
261
|
+
var evlistener;
|
|
262
|
+
var ret;
|
|
263
|
+
var events = this._events;
|
|
264
|
+
if (!events) ret = [];
|
|
265
|
+
else {
|
|
266
|
+
evlistener = events[type];
|
|
267
|
+
if (!evlistener) ret = [];
|
|
268
|
+
else if (typeof evlistener === "function") ret = [evlistener.listener || evlistener];
|
|
269
|
+
else ret = unwrapListeners(evlistener);
|
|
270
|
+
}
|
|
271
|
+
return ret;
|
|
272
|
+
};
|
|
273
|
+
EventEmitter.listenerCount = function(emitter, type) {
|
|
274
|
+
if (typeof emitter.listenerCount === "function") return emitter.listenerCount(type);
|
|
275
|
+
else return listenerCount.call(emitter, type);
|
|
276
|
+
};
|
|
277
|
+
EventEmitter.prototype.listenerCount = listenerCount;
|
|
278
|
+
function listenerCount(type) {
|
|
279
|
+
var events = this._events;
|
|
280
|
+
if (events) {
|
|
281
|
+
var evlistener = events[type];
|
|
282
|
+
if (typeof evlistener === "function") return 1;
|
|
283
|
+
else if (evlistener) return evlistener.length;
|
|
284
|
+
}
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
EventEmitter.prototype.eventNames = function eventNames() {
|
|
288
|
+
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : [];
|
|
289
|
+
};
|
|
290
|
+
function spliceOne(list, index) {
|
|
291
|
+
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) list[i] = list[k];
|
|
292
|
+
list.pop();
|
|
293
|
+
}
|
|
294
|
+
function arrayClone(arr, i) {
|
|
295
|
+
var copy = new Array(i);
|
|
296
|
+
while (i--) copy[i] = arr[i];
|
|
297
|
+
return copy;
|
|
298
|
+
}
|
|
299
|
+
function unwrapListeners(arr) {
|
|
300
|
+
var ret = new Array(arr.length);
|
|
301
|
+
for (var i = 0; i < ret.length; ++i) ret[i] = arr[i].listener || arr[i];
|
|
302
|
+
return ret;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region ../../node_modules/.pnpm/graphology@0.26.0_graphology-types@0.24.8/node_modules/graphology/dist/graphology.mjs
|
|
307
|
+
/**
|
|
308
|
+
* Graphology Utilities
|
|
309
|
+
* =====================
|
|
310
|
+
*
|
|
311
|
+
* Collection of helpful functions used by the implementation.
|
|
312
|
+
*/
|
|
313
|
+
/**
|
|
314
|
+
* Object.assign-like polyfill.
|
|
315
|
+
*
|
|
316
|
+
* @param {object} target - First object.
|
|
317
|
+
* @param {object} [...objects] - Objects to merge.
|
|
318
|
+
* @return {object}
|
|
319
|
+
*/
|
|
320
|
+
function assignPolyfill() {
|
|
321
|
+
const target = arguments[0];
|
|
322
|
+
for (let i = 1, l = arguments.length; i < l; i++) {
|
|
323
|
+
if (!arguments[i]) continue;
|
|
324
|
+
for (const k in arguments[i]) target[k] = arguments[i][k];
|
|
325
|
+
}
|
|
326
|
+
return target;
|
|
327
|
+
}
|
|
328
|
+
let assign = assignPolyfill;
|
|
329
|
+
if (typeof Object.assign === "function") assign = Object.assign;
|
|
330
|
+
/**
|
|
331
|
+
* Function returning the first matching edge for given path.
|
|
332
|
+
* Note: this function does not check the existence of source & target. This
|
|
333
|
+
* must be performed by the caller.
|
|
334
|
+
*
|
|
335
|
+
* @param {Graph} graph - Target graph.
|
|
336
|
+
* @param {any} source - Source node.
|
|
337
|
+
* @param {any} target - Target node.
|
|
338
|
+
* @param {string} type - Type of the edge (mixed, directed or undirected).
|
|
339
|
+
* @return {string|null}
|
|
340
|
+
*/
|
|
341
|
+
function getMatchingEdge(graph, source, target, type) {
|
|
342
|
+
const sourceData = graph._nodes.get(source);
|
|
343
|
+
let edge = null;
|
|
344
|
+
if (!sourceData) return edge;
|
|
345
|
+
if (type === "mixed") edge = sourceData.out && sourceData.out[target] || sourceData.undirected && sourceData.undirected[target];
|
|
346
|
+
else if (type === "directed") edge = sourceData.out && sourceData.out[target];
|
|
347
|
+
else edge = sourceData.undirected && sourceData.undirected[target];
|
|
348
|
+
return edge;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Checks whether the given value is a plain object.
|
|
352
|
+
*
|
|
353
|
+
* @param {mixed} value - Target value.
|
|
354
|
+
* @return {boolean}
|
|
355
|
+
*/
|
|
356
|
+
function isPlainObject(value) {
|
|
357
|
+
return typeof value === "object" && value !== null;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Checks whether the given object is empty.
|
|
361
|
+
*
|
|
362
|
+
* @param {object} o - Target Object.
|
|
363
|
+
* @return {boolean}
|
|
364
|
+
*/
|
|
365
|
+
function isEmpty(o) {
|
|
366
|
+
let k;
|
|
367
|
+
for (k in o) return false;
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Creates a "private" property for the given member name by concealing it
|
|
372
|
+
* using the `enumerable` option.
|
|
373
|
+
*
|
|
374
|
+
* @param {object} target - Target object.
|
|
375
|
+
* @param {string} name - Member name.
|
|
376
|
+
*/
|
|
377
|
+
function privateProperty(target, name, value) {
|
|
378
|
+
Object.defineProperty(target, name, {
|
|
379
|
+
enumerable: false,
|
|
380
|
+
configurable: false,
|
|
381
|
+
writable: true,
|
|
382
|
+
value
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Creates a read-only property for the given member name & the given getter.
|
|
387
|
+
*
|
|
388
|
+
* @param {object} target - Target object.
|
|
389
|
+
* @param {string} name - Member name.
|
|
390
|
+
* @param {mixed} value - The attached getter or fixed value.
|
|
391
|
+
*/
|
|
392
|
+
function readOnlyProperty(target, name, value) {
|
|
393
|
+
const descriptor = {
|
|
394
|
+
enumerable: true,
|
|
395
|
+
configurable: true
|
|
396
|
+
};
|
|
397
|
+
if (typeof value === "function") descriptor.get = value;
|
|
398
|
+
else {
|
|
399
|
+
descriptor.value = value;
|
|
400
|
+
descriptor.writable = false;
|
|
401
|
+
}
|
|
402
|
+
Object.defineProperty(target, name, descriptor);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Returns whether the given object constitute valid hints.
|
|
406
|
+
*
|
|
407
|
+
* @param {object} hints - Target object.
|
|
408
|
+
*/
|
|
409
|
+
function validateHints(hints) {
|
|
410
|
+
if (!isPlainObject(hints)) return false;
|
|
411
|
+
if (hints.attributes && !Array.isArray(hints.attributes)) return false;
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Creates a function generating incremental ids for edges.
|
|
416
|
+
*
|
|
417
|
+
* @return {function}
|
|
418
|
+
*/
|
|
419
|
+
function incrementalIdStartingFromRandomByte() {
|
|
420
|
+
let i = Math.floor(Math.random() * 256) & 255;
|
|
421
|
+
return () => {
|
|
422
|
+
return i++;
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Chains multiple iterators into a single iterator.
|
|
427
|
+
*
|
|
428
|
+
* @param {...Iterator} iterables
|
|
429
|
+
* @returns {Iterator}
|
|
430
|
+
*/
|
|
431
|
+
function chain() {
|
|
432
|
+
const iterables = arguments;
|
|
433
|
+
let current = null;
|
|
434
|
+
let i = -1;
|
|
435
|
+
return {
|
|
436
|
+
[Symbol.iterator]() {
|
|
437
|
+
return this;
|
|
438
|
+
},
|
|
439
|
+
next() {
|
|
440
|
+
let step = null;
|
|
441
|
+
do {
|
|
442
|
+
if (current === null) {
|
|
443
|
+
i++;
|
|
444
|
+
if (i >= iterables.length) return { done: true };
|
|
445
|
+
current = iterables[i][Symbol.iterator]();
|
|
446
|
+
}
|
|
447
|
+
step = current.next();
|
|
448
|
+
if (step.done) {
|
|
449
|
+
current = null;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
} while (true);
|
|
454
|
+
return step;
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function emptyIterator() {
|
|
459
|
+
return {
|
|
460
|
+
[Symbol.iterator]() {
|
|
461
|
+
return this;
|
|
462
|
+
},
|
|
463
|
+
next() {
|
|
464
|
+
return { done: true };
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Graphology Custom Errors
|
|
470
|
+
* =========================
|
|
471
|
+
*
|
|
472
|
+
* Defining custom errors for ease of use & easy unit tests across
|
|
473
|
+
* implementations (normalized typology rather than relying on error
|
|
474
|
+
* messages to check whether the correct error was found).
|
|
475
|
+
*/
|
|
476
|
+
var GraphError = class extends Error {
|
|
477
|
+
constructor(message) {
|
|
478
|
+
super();
|
|
479
|
+
this.name = "GraphError";
|
|
480
|
+
this.message = message;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
var InvalidArgumentsGraphError = class InvalidArgumentsGraphError extends GraphError {
|
|
484
|
+
constructor(message) {
|
|
485
|
+
super(message);
|
|
486
|
+
this.name = "InvalidArgumentsGraphError";
|
|
487
|
+
if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, InvalidArgumentsGraphError.prototype.constructor);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
var NotFoundGraphError = class NotFoundGraphError extends GraphError {
|
|
491
|
+
constructor(message) {
|
|
492
|
+
super(message);
|
|
493
|
+
this.name = "NotFoundGraphError";
|
|
494
|
+
if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, NotFoundGraphError.prototype.constructor);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
var UsageGraphError = class UsageGraphError extends GraphError {
|
|
498
|
+
constructor(message) {
|
|
499
|
+
super(message);
|
|
500
|
+
this.name = "UsageGraphError";
|
|
501
|
+
if (typeof Error.captureStackTrace === "function") Error.captureStackTrace(this, UsageGraphError.prototype.constructor);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
/**
|
|
505
|
+
* Graphology Internal Data Classes
|
|
506
|
+
* =================================
|
|
507
|
+
*
|
|
508
|
+
* Internal classes hopefully reduced to structs by engines & storing
|
|
509
|
+
* necessary information for nodes & edges.
|
|
510
|
+
*
|
|
511
|
+
* Note that those classes don't rely on the `class` keyword to avoid some
|
|
512
|
+
* cruft introduced by most of ES2015 transpilers.
|
|
513
|
+
*/
|
|
514
|
+
/**
|
|
515
|
+
* MixedNodeData class.
|
|
516
|
+
*
|
|
517
|
+
* @constructor
|
|
518
|
+
* @param {string} string - The node's key.
|
|
519
|
+
* @param {object} attributes - Node's attributes.
|
|
520
|
+
*/
|
|
521
|
+
function MixedNodeData(key, attributes) {
|
|
522
|
+
this.key = key;
|
|
523
|
+
this.attributes = attributes;
|
|
524
|
+
this.clear();
|
|
525
|
+
}
|
|
526
|
+
MixedNodeData.prototype.clear = function() {
|
|
527
|
+
this.inDegree = 0;
|
|
528
|
+
this.outDegree = 0;
|
|
529
|
+
this.undirectedDegree = 0;
|
|
530
|
+
this.undirectedLoops = 0;
|
|
531
|
+
this.directedLoops = 0;
|
|
532
|
+
this.in = {};
|
|
533
|
+
this.out = {};
|
|
534
|
+
this.undirected = {};
|
|
535
|
+
};
|
|
536
|
+
/**
|
|
537
|
+
* DirectedNodeData class.
|
|
538
|
+
*
|
|
539
|
+
* @constructor
|
|
540
|
+
* @param {string} string - The node's key.
|
|
541
|
+
* @param {object} attributes - Node's attributes.
|
|
542
|
+
*/
|
|
543
|
+
function DirectedNodeData(key, attributes) {
|
|
544
|
+
this.key = key;
|
|
545
|
+
this.attributes = attributes;
|
|
546
|
+
this.clear();
|
|
547
|
+
}
|
|
548
|
+
DirectedNodeData.prototype.clear = function() {
|
|
549
|
+
this.inDegree = 0;
|
|
550
|
+
this.outDegree = 0;
|
|
551
|
+
this.directedLoops = 0;
|
|
552
|
+
this.in = {};
|
|
553
|
+
this.out = {};
|
|
554
|
+
};
|
|
555
|
+
/**
|
|
556
|
+
* UndirectedNodeData class.
|
|
557
|
+
*
|
|
558
|
+
* @constructor
|
|
559
|
+
* @param {string} string - The node's key.
|
|
560
|
+
* @param {object} attributes - Node's attributes.
|
|
561
|
+
*/
|
|
562
|
+
function UndirectedNodeData(key, attributes) {
|
|
563
|
+
this.key = key;
|
|
564
|
+
this.attributes = attributes;
|
|
565
|
+
this.clear();
|
|
566
|
+
}
|
|
567
|
+
UndirectedNodeData.prototype.clear = function() {
|
|
568
|
+
this.undirectedDegree = 0;
|
|
569
|
+
this.undirectedLoops = 0;
|
|
570
|
+
this.undirected = {};
|
|
571
|
+
};
|
|
572
|
+
/**
|
|
573
|
+
* EdgeData class.
|
|
574
|
+
*
|
|
575
|
+
* @constructor
|
|
576
|
+
* @param {boolean} undirected - Whether the edge is undirected.
|
|
577
|
+
* @param {string} string - The edge's key.
|
|
578
|
+
* @param {string} source - Source of the edge.
|
|
579
|
+
* @param {string} target - Target of the edge.
|
|
580
|
+
* @param {object} attributes - Edge's attributes.
|
|
581
|
+
*/
|
|
582
|
+
function EdgeData(undirected, key, source, target, attributes) {
|
|
583
|
+
this.key = key;
|
|
584
|
+
this.attributes = attributes;
|
|
585
|
+
this.undirected = undirected;
|
|
586
|
+
this.source = source;
|
|
587
|
+
this.target = target;
|
|
588
|
+
}
|
|
589
|
+
EdgeData.prototype.attach = function() {
|
|
590
|
+
let outKey = "out";
|
|
591
|
+
let inKey = "in";
|
|
592
|
+
if (this.undirected) outKey = inKey = "undirected";
|
|
593
|
+
const source = this.source.key;
|
|
594
|
+
const target = this.target.key;
|
|
595
|
+
this.source[outKey][target] = this;
|
|
596
|
+
if (this.undirected && source === target) return;
|
|
597
|
+
this.target[inKey][source] = this;
|
|
598
|
+
};
|
|
599
|
+
EdgeData.prototype.attachMulti = function() {
|
|
600
|
+
let outKey = "out";
|
|
601
|
+
let inKey = "in";
|
|
602
|
+
const source = this.source.key;
|
|
603
|
+
const target = this.target.key;
|
|
604
|
+
if (this.undirected) outKey = inKey = "undirected";
|
|
605
|
+
const adj = this.source[outKey];
|
|
606
|
+
const head = adj[target];
|
|
607
|
+
if (typeof head === "undefined") {
|
|
608
|
+
adj[target] = this;
|
|
609
|
+
if (!(this.undirected && source === target)) this.target[inKey][source] = this;
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
head.previous = this;
|
|
613
|
+
this.next = head;
|
|
614
|
+
adj[target] = this;
|
|
615
|
+
this.target[inKey][source] = this;
|
|
616
|
+
};
|
|
617
|
+
EdgeData.prototype.detach = function() {
|
|
618
|
+
const source = this.source.key;
|
|
619
|
+
const target = this.target.key;
|
|
620
|
+
let outKey = "out";
|
|
621
|
+
let inKey = "in";
|
|
622
|
+
if (this.undirected) outKey = inKey = "undirected";
|
|
623
|
+
delete this.source[outKey][target];
|
|
624
|
+
delete this.target[inKey][source];
|
|
625
|
+
};
|
|
626
|
+
EdgeData.prototype.detachMulti = function() {
|
|
627
|
+
const source = this.source.key;
|
|
628
|
+
const target = this.target.key;
|
|
629
|
+
let outKey = "out";
|
|
630
|
+
let inKey = "in";
|
|
631
|
+
if (this.undirected) outKey = inKey = "undirected";
|
|
632
|
+
if (this.previous === void 0) if (this.next === void 0) {
|
|
633
|
+
delete this.source[outKey][target];
|
|
634
|
+
delete this.target[inKey][source];
|
|
635
|
+
} else {
|
|
636
|
+
this.next.previous = void 0;
|
|
637
|
+
this.source[outKey][target] = this.next;
|
|
638
|
+
this.target[inKey][source] = this.next;
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
this.previous.next = this.next;
|
|
642
|
+
if (this.next !== void 0) this.next.previous = this.previous;
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
/**
|
|
646
|
+
* Graphology Node Attributes methods
|
|
647
|
+
* ===================================
|
|
648
|
+
*/
|
|
649
|
+
const NODE = 0;
|
|
650
|
+
const SOURCE = 1;
|
|
651
|
+
const TARGET = 2;
|
|
652
|
+
const OPPOSITE = 3;
|
|
653
|
+
function findRelevantNodeData(graph, method, mode, nodeOrEdge, nameOrEdge, add1, add2) {
|
|
654
|
+
let nodeData, edgeData, arg1, arg2;
|
|
655
|
+
nodeOrEdge = "" + nodeOrEdge;
|
|
656
|
+
if (mode === NODE) {
|
|
657
|
+
nodeData = graph._nodes.get(nodeOrEdge);
|
|
658
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.${method}: could not find the "${nodeOrEdge}" node in the graph.`);
|
|
659
|
+
arg1 = nameOrEdge;
|
|
660
|
+
arg2 = add1;
|
|
661
|
+
} else if (mode === OPPOSITE) {
|
|
662
|
+
nameOrEdge = "" + nameOrEdge;
|
|
663
|
+
edgeData = graph._edges.get(nameOrEdge);
|
|
664
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.${method}: could not find the "${nameOrEdge}" edge in the graph.`);
|
|
665
|
+
const source = edgeData.source.key;
|
|
666
|
+
const target = edgeData.target.key;
|
|
667
|
+
if (nodeOrEdge === source) nodeData = edgeData.target;
|
|
668
|
+
else if (nodeOrEdge === target) nodeData = edgeData.source;
|
|
669
|
+
else throw new NotFoundGraphError(`Graph.${method}: the "${nodeOrEdge}" node is not attached to the "${nameOrEdge}" edge (${source}, ${target}).`);
|
|
670
|
+
arg1 = add1;
|
|
671
|
+
arg2 = add2;
|
|
672
|
+
} else {
|
|
673
|
+
edgeData = graph._edges.get(nodeOrEdge);
|
|
674
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.${method}: could not find the "${nodeOrEdge}" edge in the graph.`);
|
|
675
|
+
if (mode === SOURCE) nodeData = edgeData.source;
|
|
676
|
+
else nodeData = edgeData.target;
|
|
677
|
+
arg1 = nameOrEdge;
|
|
678
|
+
arg2 = add1;
|
|
679
|
+
}
|
|
680
|
+
return [
|
|
681
|
+
nodeData,
|
|
682
|
+
arg1,
|
|
683
|
+
arg2
|
|
684
|
+
];
|
|
685
|
+
}
|
|
686
|
+
function attachNodeAttributeGetter(Class, method, mode) {
|
|
687
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
|
688
|
+
const [data, name] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1);
|
|
689
|
+
return data.attributes[name];
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function attachNodeAttributesGetter(Class, method, mode) {
|
|
693
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge) {
|
|
694
|
+
const [data] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge);
|
|
695
|
+
return data.attributes;
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function attachNodeAttributeChecker(Class, method, mode) {
|
|
699
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
|
700
|
+
const [data, name] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1);
|
|
701
|
+
return data.attributes.hasOwnProperty(name);
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function attachNodeAttributeSetter(Class, method, mode) {
|
|
705
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1, add2) {
|
|
706
|
+
const [data, name, value] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1, add2);
|
|
707
|
+
data.attributes[name] = value;
|
|
708
|
+
this.emit("nodeAttributesUpdated", {
|
|
709
|
+
key: data.key,
|
|
710
|
+
type: "set",
|
|
711
|
+
attributes: data.attributes,
|
|
712
|
+
name
|
|
713
|
+
});
|
|
714
|
+
return this;
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function attachNodeAttributeUpdater(Class, method, mode) {
|
|
718
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1, add2) {
|
|
719
|
+
const [data, name, updater] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1, add2);
|
|
720
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError(`Graph.${method}: updater should be a function.`);
|
|
721
|
+
const attributes = data.attributes;
|
|
722
|
+
attributes[name] = updater(attributes[name]);
|
|
723
|
+
this.emit("nodeAttributesUpdated", {
|
|
724
|
+
key: data.key,
|
|
725
|
+
type: "set",
|
|
726
|
+
attributes: data.attributes,
|
|
727
|
+
name
|
|
728
|
+
});
|
|
729
|
+
return this;
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function attachNodeAttributeRemover(Class, method, mode) {
|
|
733
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
|
734
|
+
const [data, name] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1);
|
|
735
|
+
delete data.attributes[name];
|
|
736
|
+
this.emit("nodeAttributesUpdated", {
|
|
737
|
+
key: data.key,
|
|
738
|
+
type: "remove",
|
|
739
|
+
attributes: data.attributes,
|
|
740
|
+
name
|
|
741
|
+
});
|
|
742
|
+
return this;
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function attachNodeAttributesReplacer(Class, method, mode) {
|
|
746
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
|
747
|
+
const [data, attributes] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1);
|
|
748
|
+
if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.${method}: provided attributes are not a plain object.`);
|
|
749
|
+
data.attributes = attributes;
|
|
750
|
+
this.emit("nodeAttributesUpdated", {
|
|
751
|
+
key: data.key,
|
|
752
|
+
type: "replace",
|
|
753
|
+
attributes: data.attributes
|
|
754
|
+
});
|
|
755
|
+
return this;
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function attachNodeAttributesMerger(Class, method, mode) {
|
|
759
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
|
760
|
+
const [data, attributes] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1);
|
|
761
|
+
if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.${method}: provided attributes are not a plain object.`);
|
|
762
|
+
assign(data.attributes, attributes);
|
|
763
|
+
this.emit("nodeAttributesUpdated", {
|
|
764
|
+
key: data.key,
|
|
765
|
+
type: "merge",
|
|
766
|
+
attributes: data.attributes,
|
|
767
|
+
data: attributes
|
|
768
|
+
});
|
|
769
|
+
return this;
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function attachNodeAttributesUpdater(Class, method, mode) {
|
|
773
|
+
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
|
774
|
+
const [data, updater] = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1);
|
|
775
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError(`Graph.${method}: provided updater is not a function.`);
|
|
776
|
+
data.attributes = updater(data.attributes);
|
|
777
|
+
this.emit("nodeAttributesUpdated", {
|
|
778
|
+
key: data.key,
|
|
779
|
+
type: "update",
|
|
780
|
+
attributes: data.attributes
|
|
781
|
+
});
|
|
782
|
+
return this;
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* List of methods to attach.
|
|
787
|
+
*/
|
|
788
|
+
const NODE_ATTRIBUTES_METHODS = [
|
|
789
|
+
{
|
|
790
|
+
name: (element) => `get${element}Attribute`,
|
|
791
|
+
attacher: attachNodeAttributeGetter
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: (element) => `get${element}Attributes`,
|
|
795
|
+
attacher: attachNodeAttributesGetter
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
name: (element) => `has${element}Attribute`,
|
|
799
|
+
attacher: attachNodeAttributeChecker
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
name: (element) => `set${element}Attribute`,
|
|
803
|
+
attacher: attachNodeAttributeSetter
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
name: (element) => `update${element}Attribute`,
|
|
807
|
+
attacher: attachNodeAttributeUpdater
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
name: (element) => `remove${element}Attribute`,
|
|
811
|
+
attacher: attachNodeAttributeRemover
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
name: (element) => `replace${element}Attributes`,
|
|
815
|
+
attacher: attachNodeAttributesReplacer
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: (element) => `merge${element}Attributes`,
|
|
819
|
+
attacher: attachNodeAttributesMerger
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
name: (element) => `update${element}Attributes`,
|
|
823
|
+
attacher: attachNodeAttributesUpdater
|
|
824
|
+
}
|
|
825
|
+
];
|
|
826
|
+
/**
|
|
827
|
+
* Attach every attributes-related methods to a Graph class.
|
|
828
|
+
*
|
|
829
|
+
* @param {function} Graph - Target class.
|
|
830
|
+
*/
|
|
831
|
+
function attachNodeAttributesMethods(Graph) {
|
|
832
|
+
NODE_ATTRIBUTES_METHODS.forEach(function({ name, attacher }) {
|
|
833
|
+
attacher(Graph, name("Node"), NODE);
|
|
834
|
+
attacher(Graph, name("Source"), SOURCE);
|
|
835
|
+
attacher(Graph, name("Target"), TARGET);
|
|
836
|
+
attacher(Graph, name("Opposite"), OPPOSITE);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Graphology Edge Attributes methods
|
|
841
|
+
* ===================================
|
|
842
|
+
*/
|
|
843
|
+
/**
|
|
844
|
+
* Attach an attribute getter method onto the provided class.
|
|
845
|
+
*
|
|
846
|
+
* @param {function} Class - Target class.
|
|
847
|
+
* @param {string} method - Method name.
|
|
848
|
+
* @param {string} type - Type of the edge to find.
|
|
849
|
+
*/
|
|
850
|
+
function attachEdgeAttributeGetter(Class, method, type) {
|
|
851
|
+
/**
|
|
852
|
+
* Get the desired attribute for the given element (node or edge).
|
|
853
|
+
*
|
|
854
|
+
* Arity 2:
|
|
855
|
+
* @param {any} element - Target element.
|
|
856
|
+
* @param {string} name - Attribute's name.
|
|
857
|
+
*
|
|
858
|
+
* Arity 3 (only for edges):
|
|
859
|
+
* @param {any} source - Source element.
|
|
860
|
+
* @param {any} target - Target element.
|
|
861
|
+
* @param {string} name - Attribute's name.
|
|
862
|
+
*
|
|
863
|
+
* @return {mixed} - The attribute's value.
|
|
864
|
+
*
|
|
865
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
866
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
867
|
+
*/
|
|
868
|
+
Class.prototype[method] = function(element, name) {
|
|
869
|
+
let data;
|
|
870
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
871
|
+
if (arguments.length > 2) {
|
|
872
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
873
|
+
const source = "" + element;
|
|
874
|
+
const target = "" + name;
|
|
875
|
+
name = arguments[2];
|
|
876
|
+
data = getMatchingEdge(this, source, target, type);
|
|
877
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
878
|
+
} else {
|
|
879
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
880
|
+
element = "" + element;
|
|
881
|
+
data = this._edges.get(element);
|
|
882
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
883
|
+
}
|
|
884
|
+
return data.attributes[name];
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Attach an attributes getter method onto the provided class.
|
|
889
|
+
*
|
|
890
|
+
* @param {function} Class - Target class.
|
|
891
|
+
* @param {string} method - Method name.
|
|
892
|
+
* @param {string} type - Type of the edge to find.
|
|
893
|
+
*/
|
|
894
|
+
function attachEdgeAttributesGetter(Class, method, type) {
|
|
895
|
+
/**
|
|
896
|
+
* Retrieves all the target element's attributes.
|
|
897
|
+
*
|
|
898
|
+
* Arity 2:
|
|
899
|
+
* @param {any} element - Target element.
|
|
900
|
+
*
|
|
901
|
+
* Arity 3 (only for edges):
|
|
902
|
+
* @param {any} source - Source element.
|
|
903
|
+
* @param {any} target - Target element.
|
|
904
|
+
*
|
|
905
|
+
* @return {object} - The element's attributes.
|
|
906
|
+
*
|
|
907
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
908
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
909
|
+
*/
|
|
910
|
+
Class.prototype[method] = function(element) {
|
|
911
|
+
let data;
|
|
912
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
913
|
+
if (arguments.length > 1) {
|
|
914
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
915
|
+
const source = "" + element, target = "" + arguments[1];
|
|
916
|
+
data = getMatchingEdge(this, source, target, type);
|
|
917
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
918
|
+
} else {
|
|
919
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
920
|
+
element = "" + element;
|
|
921
|
+
data = this._edges.get(element);
|
|
922
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
923
|
+
}
|
|
924
|
+
return data.attributes;
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
/**
|
|
928
|
+
* Attach an attribute checker method onto the provided class.
|
|
929
|
+
*
|
|
930
|
+
* @param {function} Class - Target class.
|
|
931
|
+
* @param {string} method - Method name.
|
|
932
|
+
* @param {string} type - Type of the edge to find.
|
|
933
|
+
*/
|
|
934
|
+
function attachEdgeAttributeChecker(Class, method, type) {
|
|
935
|
+
/**
|
|
936
|
+
* Checks whether the desired attribute is set for the given element (node or edge).
|
|
937
|
+
*
|
|
938
|
+
* Arity 2:
|
|
939
|
+
* @param {any} element - Target element.
|
|
940
|
+
* @param {string} name - Attribute's name.
|
|
941
|
+
*
|
|
942
|
+
* Arity 3 (only for edges):
|
|
943
|
+
* @param {any} source - Source element.
|
|
944
|
+
* @param {any} target - Target element.
|
|
945
|
+
* @param {string} name - Attribute's name.
|
|
946
|
+
*
|
|
947
|
+
* @return {boolean}
|
|
948
|
+
*
|
|
949
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
950
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
951
|
+
*/
|
|
952
|
+
Class.prototype[method] = function(element, name) {
|
|
953
|
+
let data;
|
|
954
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
955
|
+
if (arguments.length > 2) {
|
|
956
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
957
|
+
const source = "" + element;
|
|
958
|
+
const target = "" + name;
|
|
959
|
+
name = arguments[2];
|
|
960
|
+
data = getMatchingEdge(this, source, target, type);
|
|
961
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
962
|
+
} else {
|
|
963
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
964
|
+
element = "" + element;
|
|
965
|
+
data = this._edges.get(element);
|
|
966
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
967
|
+
}
|
|
968
|
+
return data.attributes.hasOwnProperty(name);
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Attach an attribute setter method onto the provided class.
|
|
973
|
+
*
|
|
974
|
+
* @param {function} Class - Target class.
|
|
975
|
+
* @param {string} method - Method name.
|
|
976
|
+
* @param {string} type - Type of the edge to find.
|
|
977
|
+
*/
|
|
978
|
+
function attachEdgeAttributeSetter(Class, method, type) {
|
|
979
|
+
/**
|
|
980
|
+
* Set the desired attribute for the given element (node or edge).
|
|
981
|
+
*
|
|
982
|
+
* Arity 2:
|
|
983
|
+
* @param {any} element - Target element.
|
|
984
|
+
* @param {string} name - Attribute's name.
|
|
985
|
+
* @param {mixed} value - New attribute value.
|
|
986
|
+
*
|
|
987
|
+
* Arity 3 (only for edges):
|
|
988
|
+
* @param {any} source - Source element.
|
|
989
|
+
* @param {any} target - Target element.
|
|
990
|
+
* @param {string} name - Attribute's name.
|
|
991
|
+
* @param {mixed} value - New attribute value.
|
|
992
|
+
*
|
|
993
|
+
* @return {Graph} - Returns itself for chaining.
|
|
994
|
+
*
|
|
995
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
996
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
997
|
+
*/
|
|
998
|
+
Class.prototype[method] = function(element, name, value) {
|
|
999
|
+
let data;
|
|
1000
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
1001
|
+
if (arguments.length > 3) {
|
|
1002
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
1003
|
+
const source = "" + element;
|
|
1004
|
+
const target = "" + name;
|
|
1005
|
+
name = arguments[2];
|
|
1006
|
+
value = arguments[3];
|
|
1007
|
+
data = getMatchingEdge(this, source, target, type);
|
|
1008
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
1009
|
+
} else {
|
|
1010
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
1011
|
+
element = "" + element;
|
|
1012
|
+
data = this._edges.get(element);
|
|
1013
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
1014
|
+
}
|
|
1015
|
+
data.attributes[name] = value;
|
|
1016
|
+
this.emit("edgeAttributesUpdated", {
|
|
1017
|
+
key: data.key,
|
|
1018
|
+
type: "set",
|
|
1019
|
+
attributes: data.attributes,
|
|
1020
|
+
name
|
|
1021
|
+
});
|
|
1022
|
+
return this;
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Attach an attribute updater method onto the provided class.
|
|
1027
|
+
*
|
|
1028
|
+
* @param {function} Class - Target class.
|
|
1029
|
+
* @param {string} method - Method name.
|
|
1030
|
+
* @param {string} type - Type of the edge to find.
|
|
1031
|
+
*/
|
|
1032
|
+
function attachEdgeAttributeUpdater(Class, method, type) {
|
|
1033
|
+
/**
|
|
1034
|
+
* Update the desired attribute for the given element (node or edge) using
|
|
1035
|
+
* the provided function.
|
|
1036
|
+
*
|
|
1037
|
+
* Arity 2:
|
|
1038
|
+
* @param {any} element - Target element.
|
|
1039
|
+
* @param {string} name - Attribute's name.
|
|
1040
|
+
* @param {function} updater - Updater function.
|
|
1041
|
+
*
|
|
1042
|
+
* Arity 3 (only for edges):
|
|
1043
|
+
* @param {any} source - Source element.
|
|
1044
|
+
* @param {any} target - Target element.
|
|
1045
|
+
* @param {string} name - Attribute's name.
|
|
1046
|
+
* @param {function} updater - Updater function.
|
|
1047
|
+
*
|
|
1048
|
+
* @return {Graph} - Returns itself for chaining.
|
|
1049
|
+
*
|
|
1050
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
1051
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
1052
|
+
*/
|
|
1053
|
+
Class.prototype[method] = function(element, name, updater) {
|
|
1054
|
+
let data;
|
|
1055
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
1056
|
+
if (arguments.length > 3) {
|
|
1057
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
1058
|
+
const source = "" + element;
|
|
1059
|
+
const target = "" + name;
|
|
1060
|
+
name = arguments[2];
|
|
1061
|
+
updater = arguments[3];
|
|
1062
|
+
data = getMatchingEdge(this, source, target, type);
|
|
1063
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
1064
|
+
} else {
|
|
1065
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
1066
|
+
element = "" + element;
|
|
1067
|
+
data = this._edges.get(element);
|
|
1068
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
1069
|
+
}
|
|
1070
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError(`Graph.${method}: updater should be a function.`);
|
|
1071
|
+
data.attributes[name] = updater(data.attributes[name]);
|
|
1072
|
+
this.emit("edgeAttributesUpdated", {
|
|
1073
|
+
key: data.key,
|
|
1074
|
+
type: "set",
|
|
1075
|
+
attributes: data.attributes,
|
|
1076
|
+
name
|
|
1077
|
+
});
|
|
1078
|
+
return this;
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Attach an attribute remover method onto the provided class.
|
|
1083
|
+
*
|
|
1084
|
+
* @param {function} Class - Target class.
|
|
1085
|
+
* @param {string} method - Method name.
|
|
1086
|
+
* @param {string} type - Type of the edge to find.
|
|
1087
|
+
*/
|
|
1088
|
+
function attachEdgeAttributeRemover(Class, method, type) {
|
|
1089
|
+
/**
|
|
1090
|
+
* Remove the desired attribute for the given element (node or edge).
|
|
1091
|
+
*
|
|
1092
|
+
* Arity 2:
|
|
1093
|
+
* @param {any} element - Target element.
|
|
1094
|
+
* @param {string} name - Attribute's name.
|
|
1095
|
+
*
|
|
1096
|
+
* Arity 3 (only for edges):
|
|
1097
|
+
* @param {any} source - Source element.
|
|
1098
|
+
* @param {any} target - Target element.
|
|
1099
|
+
* @param {string} name - Attribute's name.
|
|
1100
|
+
*
|
|
1101
|
+
* @return {Graph} - Returns itself for chaining.
|
|
1102
|
+
*
|
|
1103
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
1104
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
1105
|
+
*/
|
|
1106
|
+
Class.prototype[method] = function(element, name) {
|
|
1107
|
+
let data;
|
|
1108
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
1109
|
+
if (arguments.length > 2) {
|
|
1110
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
1111
|
+
const source = "" + element;
|
|
1112
|
+
const target = "" + name;
|
|
1113
|
+
name = arguments[2];
|
|
1114
|
+
data = getMatchingEdge(this, source, target, type);
|
|
1115
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
1116
|
+
} else {
|
|
1117
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
1118
|
+
element = "" + element;
|
|
1119
|
+
data = this._edges.get(element);
|
|
1120
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
1121
|
+
}
|
|
1122
|
+
delete data.attributes[name];
|
|
1123
|
+
this.emit("edgeAttributesUpdated", {
|
|
1124
|
+
key: data.key,
|
|
1125
|
+
type: "remove",
|
|
1126
|
+
attributes: data.attributes,
|
|
1127
|
+
name
|
|
1128
|
+
});
|
|
1129
|
+
return this;
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Attach an attribute replacer method onto the provided class.
|
|
1134
|
+
*
|
|
1135
|
+
* @param {function} Class - Target class.
|
|
1136
|
+
* @param {string} method - Method name.
|
|
1137
|
+
* @param {string} type - Type of the edge to find.
|
|
1138
|
+
*/
|
|
1139
|
+
function attachEdgeAttributesReplacer(Class, method, type) {
|
|
1140
|
+
/**
|
|
1141
|
+
* Replace the attributes for the given element (node or edge).
|
|
1142
|
+
*
|
|
1143
|
+
* Arity 2:
|
|
1144
|
+
* @param {any} element - Target element.
|
|
1145
|
+
* @param {object} attributes - New attributes.
|
|
1146
|
+
*
|
|
1147
|
+
* Arity 3 (only for edges):
|
|
1148
|
+
* @param {any} source - Source element.
|
|
1149
|
+
* @param {any} target - Target element.
|
|
1150
|
+
* @param {object} attributes - New attributes.
|
|
1151
|
+
*
|
|
1152
|
+
* @return {Graph} - Returns itself for chaining.
|
|
1153
|
+
*
|
|
1154
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
1155
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
1156
|
+
*/
|
|
1157
|
+
Class.prototype[method] = function(element, attributes) {
|
|
1158
|
+
let data;
|
|
1159
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
1160
|
+
if (arguments.length > 2) {
|
|
1161
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
1162
|
+
const source = "" + element, target = "" + attributes;
|
|
1163
|
+
attributes = arguments[2];
|
|
1164
|
+
data = getMatchingEdge(this, source, target, type);
|
|
1165
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
1166
|
+
} else {
|
|
1167
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
1168
|
+
element = "" + element;
|
|
1169
|
+
data = this._edges.get(element);
|
|
1170
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
1171
|
+
}
|
|
1172
|
+
if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.${method}: provided attributes are not a plain object.`);
|
|
1173
|
+
data.attributes = attributes;
|
|
1174
|
+
this.emit("edgeAttributesUpdated", {
|
|
1175
|
+
key: data.key,
|
|
1176
|
+
type: "replace",
|
|
1177
|
+
attributes: data.attributes
|
|
1178
|
+
});
|
|
1179
|
+
return this;
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Attach an attribute merger method onto the provided class.
|
|
1184
|
+
*
|
|
1185
|
+
* @param {function} Class - Target class.
|
|
1186
|
+
* @param {string} method - Method name.
|
|
1187
|
+
* @param {string} type - Type of the edge to find.
|
|
1188
|
+
*/
|
|
1189
|
+
function attachEdgeAttributesMerger(Class, method, type) {
|
|
1190
|
+
/**
|
|
1191
|
+
* Merge the attributes for the given element (node or edge).
|
|
1192
|
+
*
|
|
1193
|
+
* Arity 2:
|
|
1194
|
+
* @param {any} element - Target element.
|
|
1195
|
+
* @param {object} attributes - Attributes to merge.
|
|
1196
|
+
*
|
|
1197
|
+
* Arity 3 (only for edges):
|
|
1198
|
+
* @param {any} source - Source element.
|
|
1199
|
+
* @param {any} target - Target element.
|
|
1200
|
+
* @param {object} attributes - Attributes to merge.
|
|
1201
|
+
*
|
|
1202
|
+
* @return {Graph} - Returns itself for chaining.
|
|
1203
|
+
*
|
|
1204
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
1205
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
1206
|
+
*/
|
|
1207
|
+
Class.prototype[method] = function(element, attributes) {
|
|
1208
|
+
let data;
|
|
1209
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
1210
|
+
if (arguments.length > 2) {
|
|
1211
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
1212
|
+
const source = "" + element, target = "" + attributes;
|
|
1213
|
+
attributes = arguments[2];
|
|
1214
|
+
data = getMatchingEdge(this, source, target, type);
|
|
1215
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
1216
|
+
} else {
|
|
1217
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
1218
|
+
element = "" + element;
|
|
1219
|
+
data = this._edges.get(element);
|
|
1220
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
1221
|
+
}
|
|
1222
|
+
if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.${method}: provided attributes are not a plain object.`);
|
|
1223
|
+
assign(data.attributes, attributes);
|
|
1224
|
+
this.emit("edgeAttributesUpdated", {
|
|
1225
|
+
key: data.key,
|
|
1226
|
+
type: "merge",
|
|
1227
|
+
attributes: data.attributes,
|
|
1228
|
+
data: attributes
|
|
1229
|
+
});
|
|
1230
|
+
return this;
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Attach an attribute updater method onto the provided class.
|
|
1235
|
+
*
|
|
1236
|
+
* @param {function} Class - Target class.
|
|
1237
|
+
* @param {string} method - Method name.
|
|
1238
|
+
* @param {string} type - Type of the edge to find.
|
|
1239
|
+
*/
|
|
1240
|
+
function attachEdgeAttributesUpdater(Class, method, type) {
|
|
1241
|
+
/**
|
|
1242
|
+
* Update the attributes of the given element (node or edge).
|
|
1243
|
+
*
|
|
1244
|
+
* Arity 2:
|
|
1245
|
+
* @param {any} element - Target element.
|
|
1246
|
+
* @param {function} updater - Updater function.
|
|
1247
|
+
*
|
|
1248
|
+
* Arity 3 (only for edges):
|
|
1249
|
+
* @param {any} source - Source element.
|
|
1250
|
+
* @param {any} target - Target element.
|
|
1251
|
+
* @param {function} updater - Updater function.
|
|
1252
|
+
*
|
|
1253
|
+
* @return {Graph} - Returns itself for chaining.
|
|
1254
|
+
*
|
|
1255
|
+
* @throws {Error} - Will throw if too many arguments are provided.
|
|
1256
|
+
* @throws {Error} - Will throw if any of the elements is not found.
|
|
1257
|
+
*/
|
|
1258
|
+
Class.prototype[method] = function(element, updater) {
|
|
1259
|
+
let data;
|
|
1260
|
+
if (this.type !== "mixed" && type !== "mixed" && type !== this.type) throw new UsageGraphError(`Graph.${method}: cannot find this type of edges in your ${this.type} graph.`);
|
|
1261
|
+
if (arguments.length > 2) {
|
|
1262
|
+
if (this.multi) throw new UsageGraphError(`Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`);
|
|
1263
|
+
const source = "" + element, target = "" + updater;
|
|
1264
|
+
updater = arguments[2];
|
|
1265
|
+
data = getMatchingEdge(this, source, target, type);
|
|
1266
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`);
|
|
1267
|
+
} else {
|
|
1268
|
+
if (type !== "mixed") throw new UsageGraphError(`Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`);
|
|
1269
|
+
element = "" + element;
|
|
1270
|
+
data = this._edges.get(element);
|
|
1271
|
+
if (!data) throw new NotFoundGraphError(`Graph.${method}: could not find the "${element}" edge in the graph.`);
|
|
1272
|
+
}
|
|
1273
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError(`Graph.${method}: provided updater is not a function.`);
|
|
1274
|
+
data.attributes = updater(data.attributes);
|
|
1275
|
+
this.emit("edgeAttributesUpdated", {
|
|
1276
|
+
key: data.key,
|
|
1277
|
+
type: "update",
|
|
1278
|
+
attributes: data.attributes
|
|
1279
|
+
});
|
|
1280
|
+
return this;
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* List of methods to attach.
|
|
1285
|
+
*/
|
|
1286
|
+
const EDGE_ATTRIBUTES_METHODS = [
|
|
1287
|
+
{
|
|
1288
|
+
name: (element) => `get${element}Attribute`,
|
|
1289
|
+
attacher: attachEdgeAttributeGetter
|
|
1290
|
+
},
|
|
1291
|
+
{
|
|
1292
|
+
name: (element) => `get${element}Attributes`,
|
|
1293
|
+
attacher: attachEdgeAttributesGetter
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
name: (element) => `has${element}Attribute`,
|
|
1297
|
+
attacher: attachEdgeAttributeChecker
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
name: (element) => `set${element}Attribute`,
|
|
1301
|
+
attacher: attachEdgeAttributeSetter
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
name: (element) => `update${element}Attribute`,
|
|
1305
|
+
attacher: attachEdgeAttributeUpdater
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
name: (element) => `remove${element}Attribute`,
|
|
1309
|
+
attacher: attachEdgeAttributeRemover
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
name: (element) => `replace${element}Attributes`,
|
|
1313
|
+
attacher: attachEdgeAttributesReplacer
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
name: (element) => `merge${element}Attributes`,
|
|
1317
|
+
attacher: attachEdgeAttributesMerger
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
name: (element) => `update${element}Attributes`,
|
|
1321
|
+
attacher: attachEdgeAttributesUpdater
|
|
1322
|
+
}
|
|
1323
|
+
];
|
|
1324
|
+
/**
|
|
1325
|
+
* Attach every attributes-related methods to a Graph class.
|
|
1326
|
+
*
|
|
1327
|
+
* @param {function} Graph - Target class.
|
|
1328
|
+
*/
|
|
1329
|
+
function attachEdgeAttributesMethods(Graph) {
|
|
1330
|
+
EDGE_ATTRIBUTES_METHODS.forEach(function({ name, attacher }) {
|
|
1331
|
+
attacher(Graph, name("Edge"), "mixed");
|
|
1332
|
+
attacher(Graph, name("DirectedEdge"), "directed");
|
|
1333
|
+
attacher(Graph, name("UndirectedEdge"), "undirected");
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Graphology Edge Iteration
|
|
1338
|
+
* ==========================
|
|
1339
|
+
*
|
|
1340
|
+
* Attaching some methods to the Graph class to be able to iterate over a
|
|
1341
|
+
* graph's edges.
|
|
1342
|
+
*/
|
|
1343
|
+
/**
|
|
1344
|
+
* Definitions.
|
|
1345
|
+
*/
|
|
1346
|
+
const EDGES_ITERATION = [
|
|
1347
|
+
{
|
|
1348
|
+
name: "edges",
|
|
1349
|
+
type: "mixed"
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
name: "inEdges",
|
|
1353
|
+
type: "directed",
|
|
1354
|
+
direction: "in"
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
name: "outEdges",
|
|
1358
|
+
type: "directed",
|
|
1359
|
+
direction: "out"
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
name: "inboundEdges",
|
|
1363
|
+
type: "mixed",
|
|
1364
|
+
direction: "in"
|
|
1365
|
+
},
|
|
1366
|
+
{
|
|
1367
|
+
name: "outboundEdges",
|
|
1368
|
+
type: "mixed",
|
|
1369
|
+
direction: "out"
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
name: "directedEdges",
|
|
1373
|
+
type: "directed"
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
name: "undirectedEdges",
|
|
1377
|
+
type: "undirected"
|
|
1378
|
+
}
|
|
1379
|
+
];
|
|
1380
|
+
/**
|
|
1381
|
+
* Function iterating over edges from the given object to match one of them.
|
|
1382
|
+
*
|
|
1383
|
+
* @param {object} object - Target object.
|
|
1384
|
+
* @param {function} callback - Function to call.
|
|
1385
|
+
*/
|
|
1386
|
+
function forEachSimple(breakable, object, callback, avoid) {
|
|
1387
|
+
let shouldBreak = false;
|
|
1388
|
+
for (const k in object) {
|
|
1389
|
+
if (k === avoid) continue;
|
|
1390
|
+
const edgeData = object[k];
|
|
1391
|
+
shouldBreak = callback(edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes, edgeData.undirected);
|
|
1392
|
+
if (breakable && shouldBreak) return edgeData.key;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function forEachMulti(breakable, object, callback, avoid) {
|
|
1396
|
+
let edgeData, source, target;
|
|
1397
|
+
let shouldBreak = false;
|
|
1398
|
+
for (const k in object) {
|
|
1399
|
+
if (k === avoid) continue;
|
|
1400
|
+
edgeData = object[k];
|
|
1401
|
+
do {
|
|
1402
|
+
source = edgeData.source;
|
|
1403
|
+
target = edgeData.target;
|
|
1404
|
+
shouldBreak = callback(edgeData.key, edgeData.attributes, source.key, target.key, source.attributes, target.attributes, edgeData.undirected);
|
|
1405
|
+
if (breakable && shouldBreak) return edgeData.key;
|
|
1406
|
+
edgeData = edgeData.next;
|
|
1407
|
+
} while (edgeData !== void 0);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Function returning an iterator over edges from the given object.
|
|
1412
|
+
*
|
|
1413
|
+
* @param {object} object - Target object.
|
|
1414
|
+
* @return {Iterator}
|
|
1415
|
+
*/
|
|
1416
|
+
function createIterator(object, avoid) {
|
|
1417
|
+
const keys = Object.keys(object);
|
|
1418
|
+
const l = keys.length;
|
|
1419
|
+
let edgeData;
|
|
1420
|
+
let i = 0;
|
|
1421
|
+
return {
|
|
1422
|
+
[Symbol.iterator]() {
|
|
1423
|
+
return this;
|
|
1424
|
+
},
|
|
1425
|
+
next() {
|
|
1426
|
+
do
|
|
1427
|
+
if (!edgeData) {
|
|
1428
|
+
if (i >= l) return { done: true };
|
|
1429
|
+
const k = keys[i++];
|
|
1430
|
+
if (k === avoid) {
|
|
1431
|
+
edgeData = void 0;
|
|
1432
|
+
continue;
|
|
1433
|
+
}
|
|
1434
|
+
edgeData = object[k];
|
|
1435
|
+
} else edgeData = edgeData.next;
|
|
1436
|
+
while (!edgeData);
|
|
1437
|
+
return {
|
|
1438
|
+
done: false,
|
|
1439
|
+
value: {
|
|
1440
|
+
edge: edgeData.key,
|
|
1441
|
+
attributes: edgeData.attributes,
|
|
1442
|
+
source: edgeData.source.key,
|
|
1443
|
+
target: edgeData.target.key,
|
|
1444
|
+
sourceAttributes: edgeData.source.attributes,
|
|
1445
|
+
targetAttributes: edgeData.target.attributes,
|
|
1446
|
+
undirected: edgeData.undirected
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Function iterating over the egdes from the object at given key to match
|
|
1454
|
+
* one of them.
|
|
1455
|
+
*
|
|
1456
|
+
* @param {object} object - Target object.
|
|
1457
|
+
* @param {mixed} k - Neighbor key.
|
|
1458
|
+
* @param {function} callback - Callback to use.
|
|
1459
|
+
*/
|
|
1460
|
+
function forEachForKeySimple(breakable, object, k, callback) {
|
|
1461
|
+
const edgeData = object[k];
|
|
1462
|
+
if (!edgeData) return;
|
|
1463
|
+
const sourceData = edgeData.source;
|
|
1464
|
+
const targetData = edgeData.target;
|
|
1465
|
+
if (callback(edgeData.key, edgeData.attributes, sourceData.key, targetData.key, sourceData.attributes, targetData.attributes, edgeData.undirected) && breakable) return edgeData.key;
|
|
1466
|
+
}
|
|
1467
|
+
function forEachForKeyMulti(breakable, object, k, callback) {
|
|
1468
|
+
let edgeData = object[k];
|
|
1469
|
+
if (!edgeData) return;
|
|
1470
|
+
let shouldBreak = false;
|
|
1471
|
+
do {
|
|
1472
|
+
shouldBreak = callback(edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes, edgeData.undirected);
|
|
1473
|
+
if (breakable && shouldBreak) return edgeData.key;
|
|
1474
|
+
edgeData = edgeData.next;
|
|
1475
|
+
} while (edgeData !== void 0);
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Function returning an iterator over the egdes from the object at given key.
|
|
1479
|
+
*
|
|
1480
|
+
* @param {object} object - Target object.
|
|
1481
|
+
* @param {mixed} k - Neighbor key.
|
|
1482
|
+
* @return {Iterator}
|
|
1483
|
+
*/
|
|
1484
|
+
function createIteratorForKey(object, k) {
|
|
1485
|
+
let edgeData = object[k];
|
|
1486
|
+
if (edgeData.next !== void 0) return {
|
|
1487
|
+
[Symbol.iterator]() {
|
|
1488
|
+
return this;
|
|
1489
|
+
},
|
|
1490
|
+
next() {
|
|
1491
|
+
if (!edgeData) return { done: true };
|
|
1492
|
+
const value = {
|
|
1493
|
+
edge: edgeData.key,
|
|
1494
|
+
attributes: edgeData.attributes,
|
|
1495
|
+
source: edgeData.source.key,
|
|
1496
|
+
target: edgeData.target.key,
|
|
1497
|
+
sourceAttributes: edgeData.source.attributes,
|
|
1498
|
+
targetAttributes: edgeData.target.attributes,
|
|
1499
|
+
undirected: edgeData.undirected
|
|
1500
|
+
};
|
|
1501
|
+
edgeData = edgeData.next;
|
|
1502
|
+
return {
|
|
1503
|
+
done: false,
|
|
1504
|
+
value
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
let done = false;
|
|
1509
|
+
return {
|
|
1510
|
+
[Symbol.iterator]() {
|
|
1511
|
+
return this;
|
|
1512
|
+
},
|
|
1513
|
+
next() {
|
|
1514
|
+
if (done === true) return { done: true };
|
|
1515
|
+
done = true;
|
|
1516
|
+
return {
|
|
1517
|
+
done: false,
|
|
1518
|
+
value: {
|
|
1519
|
+
edge: edgeData.key,
|
|
1520
|
+
attributes: edgeData.attributes,
|
|
1521
|
+
source: edgeData.source.key,
|
|
1522
|
+
target: edgeData.target.key,
|
|
1523
|
+
sourceAttributes: edgeData.source.attributes,
|
|
1524
|
+
targetAttributes: edgeData.target.attributes,
|
|
1525
|
+
undirected: edgeData.undirected
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Function creating an array of edges for the given type.
|
|
1533
|
+
*
|
|
1534
|
+
* @param {Graph} graph - Target Graph instance.
|
|
1535
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1536
|
+
* @return {array} - Array of edges.
|
|
1537
|
+
*/
|
|
1538
|
+
function createEdgeArray(graph, type) {
|
|
1539
|
+
if (graph.size === 0) return [];
|
|
1540
|
+
if (type === "mixed" || type === graph.type) return Array.from(graph._edges.keys());
|
|
1541
|
+
const size = type === "undirected" ? graph.undirectedSize : graph.directedSize;
|
|
1542
|
+
const list = new Array(size), mask = type === "undirected";
|
|
1543
|
+
const iterator = graph._edges.values();
|
|
1544
|
+
let i = 0;
|
|
1545
|
+
let step, data;
|
|
1546
|
+
while (step = iterator.next(), step.done !== true) {
|
|
1547
|
+
data = step.value;
|
|
1548
|
+
if (data.undirected === mask) list[i++] = data.key;
|
|
1549
|
+
}
|
|
1550
|
+
return list;
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Function iterating over a graph's edges using a callback to match one of
|
|
1554
|
+
* them.
|
|
1555
|
+
*
|
|
1556
|
+
* @param {Graph} graph - Target Graph instance.
|
|
1557
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1558
|
+
* @param {function} callback - Function to call.
|
|
1559
|
+
*/
|
|
1560
|
+
function forEachEdge(breakable, graph, type, callback) {
|
|
1561
|
+
if (graph.size === 0) return;
|
|
1562
|
+
const shouldFilter = type !== "mixed" && type !== graph.type;
|
|
1563
|
+
const mask = type === "undirected";
|
|
1564
|
+
let step, data;
|
|
1565
|
+
let shouldBreak = false;
|
|
1566
|
+
const iterator = graph._edges.values();
|
|
1567
|
+
while (step = iterator.next(), step.done !== true) {
|
|
1568
|
+
data = step.value;
|
|
1569
|
+
if (shouldFilter && data.undirected !== mask) continue;
|
|
1570
|
+
const { key, attributes, source, target } = data;
|
|
1571
|
+
shouldBreak = callback(key, attributes, source.key, target.key, source.attributes, target.attributes, data.undirected);
|
|
1572
|
+
if (breakable && shouldBreak) return key;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Function creating an iterator of edges for the given type.
|
|
1577
|
+
*
|
|
1578
|
+
* @param {Graph} graph - Target Graph instance.
|
|
1579
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1580
|
+
* @return {Iterator}
|
|
1581
|
+
*/
|
|
1582
|
+
function createEdgeIterator(graph, type) {
|
|
1583
|
+
if (graph.size === 0) return emptyIterator();
|
|
1584
|
+
const shouldFilter = type !== "mixed" && type !== graph.type;
|
|
1585
|
+
const mask = type === "undirected";
|
|
1586
|
+
const iterator = graph._edges.values();
|
|
1587
|
+
return {
|
|
1588
|
+
[Symbol.iterator]() {
|
|
1589
|
+
return this;
|
|
1590
|
+
},
|
|
1591
|
+
next() {
|
|
1592
|
+
let step, data;
|
|
1593
|
+
while (true) {
|
|
1594
|
+
step = iterator.next();
|
|
1595
|
+
if (step.done) return step;
|
|
1596
|
+
data = step.value;
|
|
1597
|
+
if (shouldFilter && data.undirected !== mask) continue;
|
|
1598
|
+
break;
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
value: {
|
|
1602
|
+
edge: data.key,
|
|
1603
|
+
attributes: data.attributes,
|
|
1604
|
+
source: data.source.key,
|
|
1605
|
+
target: data.target.key,
|
|
1606
|
+
sourceAttributes: data.source.attributes,
|
|
1607
|
+
targetAttributes: data.target.attributes,
|
|
1608
|
+
undirected: data.undirected
|
|
1609
|
+
},
|
|
1610
|
+
done: false
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Function iterating over a node's edges using a callback to match one of them.
|
|
1617
|
+
*
|
|
1618
|
+
* @param {boolean} multi - Whether the graph is multi or not.
|
|
1619
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1620
|
+
* @param {string} direction - In or out?
|
|
1621
|
+
* @param {any} nodeData - Target node's data.
|
|
1622
|
+
* @param {function} callback - Function to call.
|
|
1623
|
+
*/
|
|
1624
|
+
function forEachEdgeForNode(breakable, multi, type, direction, nodeData, callback) {
|
|
1625
|
+
const fn = multi ? forEachMulti : forEachSimple;
|
|
1626
|
+
let found;
|
|
1627
|
+
if (type !== "undirected") {
|
|
1628
|
+
if (direction !== "out") {
|
|
1629
|
+
found = fn(breakable, nodeData.in, callback);
|
|
1630
|
+
if (breakable && found) return found;
|
|
1631
|
+
}
|
|
1632
|
+
if (direction !== "in") {
|
|
1633
|
+
found = fn(breakable, nodeData.out, callback, !direction ? nodeData.key : void 0);
|
|
1634
|
+
if (breakable && found) return found;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (type !== "directed") {
|
|
1638
|
+
found = fn(breakable, nodeData.undirected, callback);
|
|
1639
|
+
if (breakable && found) return found;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Function creating an array of edges for the given type & the given node.
|
|
1644
|
+
*
|
|
1645
|
+
* @param {boolean} multi - Whether the graph is multi or not.
|
|
1646
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1647
|
+
* @param {string} direction - In or out?
|
|
1648
|
+
* @param {any} nodeData - Target node's data.
|
|
1649
|
+
* @return {array} - Array of edges.
|
|
1650
|
+
*/
|
|
1651
|
+
function createEdgeArrayForNode(multi, type, direction, nodeData) {
|
|
1652
|
+
const edges = [];
|
|
1653
|
+
forEachEdgeForNode(false, multi, type, direction, nodeData, function(key) {
|
|
1654
|
+
edges.push(key);
|
|
1655
|
+
});
|
|
1656
|
+
return edges;
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Function iterating over a node's edges using a callback.
|
|
1660
|
+
*
|
|
1661
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1662
|
+
* @param {string} direction - In or out?
|
|
1663
|
+
* @param {any} nodeData - Target node's data.
|
|
1664
|
+
* @return {Iterator}
|
|
1665
|
+
*/
|
|
1666
|
+
function createEdgeIteratorForNode(type, direction, nodeData) {
|
|
1667
|
+
let iterator = emptyIterator();
|
|
1668
|
+
if (type !== "undirected") {
|
|
1669
|
+
if (direction !== "out" && typeof nodeData.in !== "undefined") iterator = chain(iterator, createIterator(nodeData.in));
|
|
1670
|
+
if (direction !== "in" && typeof nodeData.out !== "undefined") iterator = chain(iterator, createIterator(nodeData.out, !direction ? nodeData.key : void 0));
|
|
1671
|
+
}
|
|
1672
|
+
if (type !== "directed" && typeof nodeData.undirected !== "undefined") iterator = chain(iterator, createIterator(nodeData.undirected));
|
|
1673
|
+
return iterator;
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Function iterating over edges for the given path using a callback to match
|
|
1677
|
+
* one of them.
|
|
1678
|
+
*
|
|
1679
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1680
|
+
* @param {boolean} multi - Whether the graph is multi.
|
|
1681
|
+
* @param {string} direction - In or out?
|
|
1682
|
+
* @param {NodeData} sourceData - Source node's data.
|
|
1683
|
+
* @param {string} target - Target node.
|
|
1684
|
+
* @param {function} callback - Function to call.
|
|
1685
|
+
*/
|
|
1686
|
+
function forEachEdgeForPath(breakable, type, multi, direction, sourceData, target, callback) {
|
|
1687
|
+
const fn = multi ? forEachForKeyMulti : forEachForKeySimple;
|
|
1688
|
+
let found;
|
|
1689
|
+
if (type !== "undirected") {
|
|
1690
|
+
if (typeof sourceData.in !== "undefined" && direction !== "out") {
|
|
1691
|
+
found = fn(breakable, sourceData.in, target, callback);
|
|
1692
|
+
if (breakable && found) return found;
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof sourceData.out !== "undefined" && direction !== "in" && (direction || sourceData.key !== target)) {
|
|
1695
|
+
found = fn(breakable, sourceData.out, target, callback);
|
|
1696
|
+
if (breakable && found) return found;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (type !== "directed") {
|
|
1700
|
+
if (typeof sourceData.undirected !== "undefined") {
|
|
1701
|
+
found = fn(breakable, sourceData.undirected, target, callback);
|
|
1702
|
+
if (breakable && found) return found;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Function creating an array of edges for the given path.
|
|
1708
|
+
*
|
|
1709
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1710
|
+
* @param {boolean} multi - Whether the graph is multi.
|
|
1711
|
+
* @param {string} direction - In or out?
|
|
1712
|
+
* @param {NodeData} sourceData - Source node's data.
|
|
1713
|
+
* @param {any} target - Target node.
|
|
1714
|
+
* @return {array} - Array of edges.
|
|
1715
|
+
*/
|
|
1716
|
+
function createEdgeArrayForPath(type, multi, direction, sourceData, target) {
|
|
1717
|
+
const edges = [];
|
|
1718
|
+
forEachEdgeForPath(false, type, multi, direction, sourceData, target, function(key) {
|
|
1719
|
+
edges.push(key);
|
|
1720
|
+
});
|
|
1721
|
+
return edges;
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Function returning an iterator over edges for the given path.
|
|
1725
|
+
*
|
|
1726
|
+
* @param {string} type - Type of edges to retrieve.
|
|
1727
|
+
* @param {string} direction - In or out?
|
|
1728
|
+
* @param {NodeData} sourceData - Source node's data.
|
|
1729
|
+
* @param {string} target - Target node.
|
|
1730
|
+
* @param {function} callback - Function to call.
|
|
1731
|
+
*/
|
|
1732
|
+
function createEdgeIteratorForPath(type, direction, sourceData, target) {
|
|
1733
|
+
let iterator = emptyIterator();
|
|
1734
|
+
if (type !== "undirected") {
|
|
1735
|
+
if (typeof sourceData.in !== "undefined" && direction !== "out" && target in sourceData.in) iterator = chain(iterator, createIteratorForKey(sourceData.in, target));
|
|
1736
|
+
if (typeof sourceData.out !== "undefined" && direction !== "in" && target in sourceData.out && (direction || sourceData.key !== target)) iterator = chain(iterator, createIteratorForKey(sourceData.out, target));
|
|
1737
|
+
}
|
|
1738
|
+
if (type !== "directed") {
|
|
1739
|
+
if (typeof sourceData.undirected !== "undefined" && target in sourceData.undirected) iterator = chain(iterator, createIteratorForKey(sourceData.undirected, target));
|
|
1740
|
+
}
|
|
1741
|
+
return iterator;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Function attaching an edge array creator method to the Graph prototype.
|
|
1745
|
+
*
|
|
1746
|
+
* @param {function} Class - Target class.
|
|
1747
|
+
* @param {object} description - Method description.
|
|
1748
|
+
*/
|
|
1749
|
+
function attachEdgeArrayCreator(Class, description) {
|
|
1750
|
+
const { name, type, direction } = description;
|
|
1751
|
+
/**
|
|
1752
|
+
* Function returning an array of certain edges.
|
|
1753
|
+
*
|
|
1754
|
+
* Arity 0: Return all the relevant edges.
|
|
1755
|
+
*
|
|
1756
|
+
* Arity 1: Return all of a node's relevant edges.
|
|
1757
|
+
* @param {any} node - Target node.
|
|
1758
|
+
*
|
|
1759
|
+
* Arity 2: Return the relevant edges across the given path.
|
|
1760
|
+
* @param {any} source - Source node.
|
|
1761
|
+
* @param {any} target - Target node.
|
|
1762
|
+
*
|
|
1763
|
+
* @return {array|number} - The edges or the number of edges.
|
|
1764
|
+
*
|
|
1765
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
1766
|
+
*/
|
|
1767
|
+
Class.prototype[name] = function(source, target) {
|
|
1768
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return [];
|
|
1769
|
+
if (!arguments.length) return createEdgeArray(this, type);
|
|
1770
|
+
if (arguments.length === 1) {
|
|
1771
|
+
source = "" + source;
|
|
1772
|
+
const nodeData = this._nodes.get(source);
|
|
1773
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${name}: could not find the "${source}" node in the graph.`);
|
|
1774
|
+
return createEdgeArrayForNode(this.multi, type === "mixed" ? this.type : type, direction, nodeData);
|
|
1775
|
+
}
|
|
1776
|
+
if (arguments.length === 2) {
|
|
1777
|
+
source = "" + source;
|
|
1778
|
+
target = "" + target;
|
|
1779
|
+
const sourceData = this._nodes.get(source);
|
|
1780
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.${name}: could not find the "${source}" source node in the graph.`);
|
|
1781
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.${name}: could not find the "${target}" target node in the graph.`);
|
|
1782
|
+
return createEdgeArrayForPath(type, this.multi, direction, sourceData, target);
|
|
1783
|
+
}
|
|
1784
|
+
throw new InvalidArgumentsGraphError(`Graph.${name}: too many arguments (expecting 0, 1 or 2 and got ${arguments.length}).`);
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Function attaching a edge callback iterator method to the Graph prototype.
|
|
1789
|
+
*
|
|
1790
|
+
* @param {function} Class - Target class.
|
|
1791
|
+
* @param {object} description - Method description.
|
|
1792
|
+
*/
|
|
1793
|
+
function attachForEachEdge(Class, description) {
|
|
1794
|
+
const { name, type, direction } = description;
|
|
1795
|
+
const forEachName = "forEach" + name[0].toUpperCase() + name.slice(1, -1);
|
|
1796
|
+
/**
|
|
1797
|
+
* Function iterating over the graph's relevant edges by applying the given
|
|
1798
|
+
* callback.
|
|
1799
|
+
*
|
|
1800
|
+
* Arity 1: Iterate over all the relevant edges.
|
|
1801
|
+
* @param {function} callback - Callback to use.
|
|
1802
|
+
*
|
|
1803
|
+
* Arity 2: Iterate over all of a node's relevant edges.
|
|
1804
|
+
* @param {any} node - Target node.
|
|
1805
|
+
* @param {function} callback - Callback to use.
|
|
1806
|
+
*
|
|
1807
|
+
* Arity 3: Iterate over the relevant edges across the given path.
|
|
1808
|
+
* @param {any} source - Source node.
|
|
1809
|
+
* @param {any} target - Target node.
|
|
1810
|
+
* @param {function} callback - Callback to use.
|
|
1811
|
+
*
|
|
1812
|
+
* @return {undefined}
|
|
1813
|
+
*
|
|
1814
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
1815
|
+
*/
|
|
1816
|
+
Class.prototype[forEachName] = function(source, target, callback) {
|
|
1817
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return;
|
|
1818
|
+
if (arguments.length === 1) {
|
|
1819
|
+
callback = source;
|
|
1820
|
+
return forEachEdge(false, this, type, callback);
|
|
1821
|
+
}
|
|
1822
|
+
if (arguments.length === 2) {
|
|
1823
|
+
source = "" + source;
|
|
1824
|
+
callback = target;
|
|
1825
|
+
const nodeData = this._nodes.get(source);
|
|
1826
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${forEachName}: could not find the "${source}" node in the graph.`);
|
|
1827
|
+
return forEachEdgeForNode(false, this.multi, type === "mixed" ? this.type : type, direction, nodeData, callback);
|
|
1828
|
+
}
|
|
1829
|
+
if (arguments.length === 3) {
|
|
1830
|
+
source = "" + source;
|
|
1831
|
+
target = "" + target;
|
|
1832
|
+
const sourceData = this._nodes.get(source);
|
|
1833
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.${forEachName}: could not find the "${source}" source node in the graph.`);
|
|
1834
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.${forEachName}: could not find the "${target}" target node in the graph.`);
|
|
1835
|
+
return forEachEdgeForPath(false, type, this.multi, direction, sourceData, target, callback);
|
|
1836
|
+
}
|
|
1837
|
+
throw new InvalidArgumentsGraphError(`Graph.${forEachName}: too many arguments (expecting 1, 2 or 3 and got ${arguments.length}).`);
|
|
1838
|
+
};
|
|
1839
|
+
/**
|
|
1840
|
+
* Function mapping the graph's relevant edges by applying the given
|
|
1841
|
+
* callback.
|
|
1842
|
+
*
|
|
1843
|
+
* Arity 1: Map all the relevant edges.
|
|
1844
|
+
* @param {function} callback - Callback to use.
|
|
1845
|
+
*
|
|
1846
|
+
* Arity 2: Map all of a node's relevant edges.
|
|
1847
|
+
* @param {any} node - Target node.
|
|
1848
|
+
* @param {function} callback - Callback to use.
|
|
1849
|
+
*
|
|
1850
|
+
* Arity 3: Map the relevant edges across the given path.
|
|
1851
|
+
* @param {any} source - Source node.
|
|
1852
|
+
* @param {any} target - Target node.
|
|
1853
|
+
* @param {function} callback - Callback to use.
|
|
1854
|
+
*
|
|
1855
|
+
* @return {undefined}
|
|
1856
|
+
*
|
|
1857
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
1858
|
+
*/
|
|
1859
|
+
const mapName = "map" + name[0].toUpperCase() + name.slice(1);
|
|
1860
|
+
Class.prototype[mapName] = function() {
|
|
1861
|
+
const args = Array.prototype.slice.call(arguments);
|
|
1862
|
+
const callback = args.pop();
|
|
1863
|
+
let result;
|
|
1864
|
+
if (args.length === 0) {
|
|
1865
|
+
let length = 0;
|
|
1866
|
+
if (type !== "directed") length += this.undirectedSize;
|
|
1867
|
+
if (type !== "undirected") length += this.directedSize;
|
|
1868
|
+
result = new Array(length);
|
|
1869
|
+
let i = 0;
|
|
1870
|
+
args.push((e, ea, s, t, sa, ta, u) => {
|
|
1871
|
+
result[i++] = callback(e, ea, s, t, sa, ta, u);
|
|
1872
|
+
});
|
|
1873
|
+
} else {
|
|
1874
|
+
result = [];
|
|
1875
|
+
args.push((e, ea, s, t, sa, ta, u) => {
|
|
1876
|
+
result.push(callback(e, ea, s, t, sa, ta, u));
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
this[forEachName].apply(this, args);
|
|
1880
|
+
return result;
|
|
1881
|
+
};
|
|
1882
|
+
/**
|
|
1883
|
+
* Function filtering the graph's relevant edges using the provided predicate
|
|
1884
|
+
* function.
|
|
1885
|
+
*
|
|
1886
|
+
* Arity 1: Filter all the relevant edges.
|
|
1887
|
+
* @param {function} predicate - Predicate to use.
|
|
1888
|
+
*
|
|
1889
|
+
* Arity 2: Filter all of a node's relevant edges.
|
|
1890
|
+
* @param {any} node - Target node.
|
|
1891
|
+
* @param {function} predicate - Predicate to use.
|
|
1892
|
+
*
|
|
1893
|
+
* Arity 3: Filter the relevant edges across the given path.
|
|
1894
|
+
* @param {any} source - Source node.
|
|
1895
|
+
* @param {any} target - Target node.
|
|
1896
|
+
* @param {function} predicate - Predicate to use.
|
|
1897
|
+
*
|
|
1898
|
+
* @return {undefined}
|
|
1899
|
+
*
|
|
1900
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
1901
|
+
*/
|
|
1902
|
+
const filterName = "filter" + name[0].toUpperCase() + name.slice(1);
|
|
1903
|
+
Class.prototype[filterName] = function() {
|
|
1904
|
+
const args = Array.prototype.slice.call(arguments);
|
|
1905
|
+
const callback = args.pop();
|
|
1906
|
+
const result = [];
|
|
1907
|
+
args.push((e, ea, s, t, sa, ta, u) => {
|
|
1908
|
+
if (callback(e, ea, s, t, sa, ta, u)) result.push(e);
|
|
1909
|
+
});
|
|
1910
|
+
this[forEachName].apply(this, args);
|
|
1911
|
+
return result;
|
|
1912
|
+
};
|
|
1913
|
+
/**
|
|
1914
|
+
* Function reducing the graph's relevant edges using the provided accumulator
|
|
1915
|
+
* function.
|
|
1916
|
+
*
|
|
1917
|
+
* Arity 1: Reduce all the relevant edges.
|
|
1918
|
+
* @param {function} accumulator - Accumulator to use.
|
|
1919
|
+
* @param {any} initialValue - Initial value.
|
|
1920
|
+
*
|
|
1921
|
+
* Arity 2: Reduce all of a node's relevant edges.
|
|
1922
|
+
* @param {any} node - Target node.
|
|
1923
|
+
* @param {function} accumulator - Accumulator to use.
|
|
1924
|
+
* @param {any} initialValue - Initial value.
|
|
1925
|
+
*
|
|
1926
|
+
* Arity 3: Reduce the relevant edges across the given path.
|
|
1927
|
+
* @param {any} source - Source node.
|
|
1928
|
+
* @param {any} target - Target node.
|
|
1929
|
+
* @param {function} accumulator - Accumulator to use.
|
|
1930
|
+
* @param {any} initialValue - Initial value.
|
|
1931
|
+
*
|
|
1932
|
+
* @return {undefined}
|
|
1933
|
+
*
|
|
1934
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
1935
|
+
*/
|
|
1936
|
+
const reduceName = "reduce" + name[0].toUpperCase() + name.slice(1);
|
|
1937
|
+
Class.prototype[reduceName] = function() {
|
|
1938
|
+
let args = Array.prototype.slice.call(arguments);
|
|
1939
|
+
if (args.length < 2 || args.length > 4) throw new InvalidArgumentsGraphError(`Graph.${reduceName}: invalid number of arguments (expecting 2, 3 or 4 and got ${args.length}).`);
|
|
1940
|
+
if (typeof args[args.length - 1] === "function" && typeof args[args.length - 2] !== "function") throw new InvalidArgumentsGraphError(`Graph.${reduceName}: missing initial value. You must provide it because the callback takes more than one argument and we cannot infer the initial value from the first iteration, as you could with a simple array.`);
|
|
1941
|
+
let callback;
|
|
1942
|
+
let initialValue;
|
|
1943
|
+
if (args.length === 2) {
|
|
1944
|
+
callback = args[0];
|
|
1945
|
+
initialValue = args[1];
|
|
1946
|
+
args = [];
|
|
1947
|
+
} else if (args.length === 3) {
|
|
1948
|
+
callback = args[1];
|
|
1949
|
+
initialValue = args[2];
|
|
1950
|
+
args = [args[0]];
|
|
1951
|
+
} else if (args.length === 4) {
|
|
1952
|
+
callback = args[2];
|
|
1953
|
+
initialValue = args[3];
|
|
1954
|
+
args = [args[0], args[1]];
|
|
1955
|
+
}
|
|
1956
|
+
let accumulator = initialValue;
|
|
1957
|
+
args.push((e, ea, s, t, sa, ta, u) => {
|
|
1958
|
+
accumulator = callback(accumulator, e, ea, s, t, sa, ta, u);
|
|
1959
|
+
});
|
|
1960
|
+
this[forEachName].apply(this, args);
|
|
1961
|
+
return accumulator;
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Function attaching a breakable edge callback iterator method to the Graph
|
|
1966
|
+
* prototype.
|
|
1967
|
+
*
|
|
1968
|
+
* @param {function} Class - Target class.
|
|
1969
|
+
* @param {object} description - Method description.
|
|
1970
|
+
*/
|
|
1971
|
+
function attachFindEdge(Class, description) {
|
|
1972
|
+
const { name, type, direction } = description;
|
|
1973
|
+
const findEdgeName = "find" + name[0].toUpperCase() + name.slice(1, -1);
|
|
1974
|
+
/**
|
|
1975
|
+
* Function iterating over the graph's relevant edges in order to match
|
|
1976
|
+
* one of them using the provided predicate function.
|
|
1977
|
+
*
|
|
1978
|
+
* Arity 1: Iterate over all the relevant edges.
|
|
1979
|
+
* @param {function} callback - Callback to use.
|
|
1980
|
+
*
|
|
1981
|
+
* Arity 2: Iterate over all of a node's relevant edges.
|
|
1982
|
+
* @param {any} node - Target node.
|
|
1983
|
+
* @param {function} callback - Callback to use.
|
|
1984
|
+
*
|
|
1985
|
+
* Arity 3: Iterate over the relevant edges across the given path.
|
|
1986
|
+
* @param {any} source - Source node.
|
|
1987
|
+
* @param {any} target - Target node.
|
|
1988
|
+
* @param {function} callback - Callback to use.
|
|
1989
|
+
*
|
|
1990
|
+
* @return {undefined}
|
|
1991
|
+
*
|
|
1992
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
1993
|
+
*/
|
|
1994
|
+
Class.prototype[findEdgeName] = function(source, target, callback) {
|
|
1995
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return false;
|
|
1996
|
+
if (arguments.length === 1) {
|
|
1997
|
+
callback = source;
|
|
1998
|
+
return forEachEdge(true, this, type, callback);
|
|
1999
|
+
}
|
|
2000
|
+
if (arguments.length === 2) {
|
|
2001
|
+
source = "" + source;
|
|
2002
|
+
callback = target;
|
|
2003
|
+
const nodeData = this._nodes.get(source);
|
|
2004
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${findEdgeName}: could not find the "${source}" node in the graph.`);
|
|
2005
|
+
return forEachEdgeForNode(true, this.multi, type === "mixed" ? this.type : type, direction, nodeData, callback);
|
|
2006
|
+
}
|
|
2007
|
+
if (arguments.length === 3) {
|
|
2008
|
+
source = "" + source;
|
|
2009
|
+
target = "" + target;
|
|
2010
|
+
const sourceData = this._nodes.get(source);
|
|
2011
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.${findEdgeName}: could not find the "${source}" source node in the graph.`);
|
|
2012
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.${findEdgeName}: could not find the "${target}" target node in the graph.`);
|
|
2013
|
+
return forEachEdgeForPath(true, type, this.multi, direction, sourceData, target, callback);
|
|
2014
|
+
}
|
|
2015
|
+
throw new InvalidArgumentsGraphError(`Graph.${findEdgeName}: too many arguments (expecting 1, 2 or 3 and got ${arguments.length}).`);
|
|
2016
|
+
};
|
|
2017
|
+
/**
|
|
2018
|
+
* Function iterating over the graph's relevant edges in order to assert
|
|
2019
|
+
* whether any one of them matches the provided predicate function.
|
|
2020
|
+
*
|
|
2021
|
+
* Arity 1: Iterate over all the relevant edges.
|
|
2022
|
+
* @param {function} callback - Callback to use.
|
|
2023
|
+
*
|
|
2024
|
+
* Arity 2: Iterate over all of a node's relevant edges.
|
|
2025
|
+
* @param {any} node - Target node.
|
|
2026
|
+
* @param {function} callback - Callback to use.
|
|
2027
|
+
*
|
|
2028
|
+
* Arity 3: Iterate over the relevant edges across the given path.
|
|
2029
|
+
* @param {any} source - Source node.
|
|
2030
|
+
* @param {any} target - Target node.
|
|
2031
|
+
* @param {function} callback - Callback to use.
|
|
2032
|
+
*
|
|
2033
|
+
* @return {undefined}
|
|
2034
|
+
*
|
|
2035
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2036
|
+
*/
|
|
2037
|
+
const someName = "some" + name[0].toUpperCase() + name.slice(1, -1);
|
|
2038
|
+
Class.prototype[someName] = function() {
|
|
2039
|
+
const args = Array.prototype.slice.call(arguments);
|
|
2040
|
+
const callback = args.pop();
|
|
2041
|
+
args.push((e, ea, s, t, sa, ta, u) => {
|
|
2042
|
+
return callback(e, ea, s, t, sa, ta, u);
|
|
2043
|
+
});
|
|
2044
|
+
if (this[findEdgeName].apply(this, args)) return true;
|
|
2045
|
+
return false;
|
|
2046
|
+
};
|
|
2047
|
+
/**
|
|
2048
|
+
* Function iterating over the graph's relevant edges in order to assert
|
|
2049
|
+
* whether all of them matche the provided predicate function.
|
|
2050
|
+
*
|
|
2051
|
+
* Arity 1: Iterate over all the relevant edges.
|
|
2052
|
+
* @param {function} callback - Callback to use.
|
|
2053
|
+
*
|
|
2054
|
+
* Arity 2: Iterate over all of a node's relevant edges.
|
|
2055
|
+
* @param {any} node - Target node.
|
|
2056
|
+
* @param {function} callback - Callback to use.
|
|
2057
|
+
*
|
|
2058
|
+
* Arity 3: Iterate over the relevant edges across the given path.
|
|
2059
|
+
* @param {any} source - Source node.
|
|
2060
|
+
* @param {any} target - Target node.
|
|
2061
|
+
* @param {function} callback - Callback to use.
|
|
2062
|
+
*
|
|
2063
|
+
* @return {undefined}
|
|
2064
|
+
*
|
|
2065
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2066
|
+
*/
|
|
2067
|
+
const everyName = "every" + name[0].toUpperCase() + name.slice(1, -1);
|
|
2068
|
+
Class.prototype[everyName] = function() {
|
|
2069
|
+
const args = Array.prototype.slice.call(arguments);
|
|
2070
|
+
const callback = args.pop();
|
|
2071
|
+
args.push((e, ea, s, t, sa, ta, u) => {
|
|
2072
|
+
return !callback(e, ea, s, t, sa, ta, u);
|
|
2073
|
+
});
|
|
2074
|
+
if (this[findEdgeName].apply(this, args)) return false;
|
|
2075
|
+
return true;
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Function attaching an edge iterator method to the Graph prototype.
|
|
2080
|
+
*
|
|
2081
|
+
* @param {function} Class - Target class.
|
|
2082
|
+
* @param {object} description - Method description.
|
|
2083
|
+
*/
|
|
2084
|
+
function attachEdgeIteratorCreator(Class, description) {
|
|
2085
|
+
const { name: originalName, type, direction } = description;
|
|
2086
|
+
const name = originalName.slice(0, -1) + "Entries";
|
|
2087
|
+
/**
|
|
2088
|
+
* Function returning an iterator over the graph's edges.
|
|
2089
|
+
*
|
|
2090
|
+
* Arity 0: Iterate over all the relevant edges.
|
|
2091
|
+
*
|
|
2092
|
+
* Arity 1: Iterate over all of a node's relevant edges.
|
|
2093
|
+
* @param {any} node - Target node.
|
|
2094
|
+
*
|
|
2095
|
+
* Arity 2: Iterate over the relevant edges across the given path.
|
|
2096
|
+
* @param {any} source - Source node.
|
|
2097
|
+
* @param {any} target - Target node.
|
|
2098
|
+
*
|
|
2099
|
+
* @return {array|number} - The edges or the number of edges.
|
|
2100
|
+
*
|
|
2101
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2102
|
+
*/
|
|
2103
|
+
Class.prototype[name] = function(source, target) {
|
|
2104
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return emptyIterator();
|
|
2105
|
+
if (!arguments.length) return createEdgeIterator(this, type);
|
|
2106
|
+
if (arguments.length === 1) {
|
|
2107
|
+
source = "" + source;
|
|
2108
|
+
const sourceData = this._nodes.get(source);
|
|
2109
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.${name}: could not find the "${source}" node in the graph.`);
|
|
2110
|
+
return createEdgeIteratorForNode(type, direction, sourceData);
|
|
2111
|
+
}
|
|
2112
|
+
if (arguments.length === 2) {
|
|
2113
|
+
source = "" + source;
|
|
2114
|
+
target = "" + target;
|
|
2115
|
+
const sourceData = this._nodes.get(source);
|
|
2116
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.${name}: could not find the "${source}" source node in the graph.`);
|
|
2117
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.${name}: could not find the "${target}" target node in the graph.`);
|
|
2118
|
+
return createEdgeIteratorForPath(type, direction, sourceData, target);
|
|
2119
|
+
}
|
|
2120
|
+
throw new InvalidArgumentsGraphError(`Graph.${name}: too many arguments (expecting 0, 1 or 2 and got ${arguments.length}).`);
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* Function attaching every edge iteration method to the Graph class.
|
|
2125
|
+
*
|
|
2126
|
+
* @param {function} Graph - Graph class.
|
|
2127
|
+
*/
|
|
2128
|
+
function attachEdgeIterationMethods(Graph) {
|
|
2129
|
+
EDGES_ITERATION.forEach((description) => {
|
|
2130
|
+
attachEdgeArrayCreator(Graph, description);
|
|
2131
|
+
attachForEachEdge(Graph, description);
|
|
2132
|
+
attachFindEdge(Graph, description);
|
|
2133
|
+
attachEdgeIteratorCreator(Graph, description);
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Graphology Neighbor Iteration
|
|
2138
|
+
* ==============================
|
|
2139
|
+
*
|
|
2140
|
+
* Attaching some methods to the Graph class to be able to iterate over
|
|
2141
|
+
* neighbors.
|
|
2142
|
+
*/
|
|
2143
|
+
/**
|
|
2144
|
+
* Definitions.
|
|
2145
|
+
*/
|
|
2146
|
+
const NEIGHBORS_ITERATION = [
|
|
2147
|
+
{
|
|
2148
|
+
name: "neighbors",
|
|
2149
|
+
type: "mixed"
|
|
2150
|
+
},
|
|
2151
|
+
{
|
|
2152
|
+
name: "inNeighbors",
|
|
2153
|
+
type: "directed",
|
|
2154
|
+
direction: "in"
|
|
2155
|
+
},
|
|
2156
|
+
{
|
|
2157
|
+
name: "outNeighbors",
|
|
2158
|
+
type: "directed",
|
|
2159
|
+
direction: "out"
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
name: "inboundNeighbors",
|
|
2163
|
+
type: "mixed",
|
|
2164
|
+
direction: "in"
|
|
2165
|
+
},
|
|
2166
|
+
{
|
|
2167
|
+
name: "outboundNeighbors",
|
|
2168
|
+
type: "mixed",
|
|
2169
|
+
direction: "out"
|
|
2170
|
+
},
|
|
2171
|
+
{
|
|
2172
|
+
name: "directedNeighbors",
|
|
2173
|
+
type: "directed"
|
|
2174
|
+
},
|
|
2175
|
+
{
|
|
2176
|
+
name: "undirectedNeighbors",
|
|
2177
|
+
type: "undirected"
|
|
2178
|
+
}
|
|
2179
|
+
];
|
|
2180
|
+
/**
|
|
2181
|
+
* Helpers.
|
|
2182
|
+
*/
|
|
2183
|
+
function CompositeSetWrapper() {
|
|
2184
|
+
this.A = null;
|
|
2185
|
+
this.B = null;
|
|
2186
|
+
}
|
|
2187
|
+
CompositeSetWrapper.prototype.wrap = function(set) {
|
|
2188
|
+
if (this.A === null) this.A = set;
|
|
2189
|
+
else if (this.B === null) this.B = set;
|
|
2190
|
+
};
|
|
2191
|
+
CompositeSetWrapper.prototype.has = function(key) {
|
|
2192
|
+
if (this.A !== null && key in this.A) return true;
|
|
2193
|
+
if (this.B !== null && key in this.B) return true;
|
|
2194
|
+
return false;
|
|
2195
|
+
};
|
|
2196
|
+
/**
|
|
2197
|
+
* Function iterating over the given node's relevant neighbors to match
|
|
2198
|
+
* one of them using a predicated function.
|
|
2199
|
+
*
|
|
2200
|
+
* @param {string} type - Type of neighbors.
|
|
2201
|
+
* @param {string} direction - Direction.
|
|
2202
|
+
* @param {any} nodeData - Target node's data.
|
|
2203
|
+
* @param {function} callback - Callback to use.
|
|
2204
|
+
*/
|
|
2205
|
+
function forEachInObjectOnce(breakable, visited, nodeData, object, callback) {
|
|
2206
|
+
for (const k in object) {
|
|
2207
|
+
const edgeData = object[k];
|
|
2208
|
+
const sourceData = edgeData.source;
|
|
2209
|
+
const targetData = edgeData.target;
|
|
2210
|
+
const neighborData = sourceData === nodeData ? targetData : sourceData;
|
|
2211
|
+
if (visited && visited.has(neighborData.key)) continue;
|
|
2212
|
+
const shouldBreak = callback(neighborData.key, neighborData.attributes);
|
|
2213
|
+
if (breakable && shouldBreak) return neighborData.key;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
function forEachNeighbor(breakable, type, direction, nodeData, callback) {
|
|
2217
|
+
if (type !== "mixed") {
|
|
2218
|
+
if (type === "undirected") return forEachInObjectOnce(breakable, null, nodeData, nodeData.undirected, callback);
|
|
2219
|
+
if (typeof direction === "string") return forEachInObjectOnce(breakable, null, nodeData, nodeData[direction], callback);
|
|
2220
|
+
}
|
|
2221
|
+
const visited = new CompositeSetWrapper();
|
|
2222
|
+
let found;
|
|
2223
|
+
if (type !== "undirected") {
|
|
2224
|
+
if (direction !== "out") {
|
|
2225
|
+
found = forEachInObjectOnce(breakable, null, nodeData, nodeData.in, callback);
|
|
2226
|
+
if (breakable && found) return found;
|
|
2227
|
+
visited.wrap(nodeData.in);
|
|
2228
|
+
}
|
|
2229
|
+
if (direction !== "in") {
|
|
2230
|
+
found = forEachInObjectOnce(breakable, visited, nodeData, nodeData.out, callback);
|
|
2231
|
+
if (breakable && found) return found;
|
|
2232
|
+
visited.wrap(nodeData.out);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
if (type !== "directed") {
|
|
2236
|
+
found = forEachInObjectOnce(breakable, visited, nodeData, nodeData.undirected, callback);
|
|
2237
|
+
if (breakable && found) return found;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Function creating an array of relevant neighbors for the given node.
|
|
2242
|
+
*
|
|
2243
|
+
* @param {string} type - Type of neighbors.
|
|
2244
|
+
* @param {string} direction - Direction.
|
|
2245
|
+
* @param {any} nodeData - Target node's data.
|
|
2246
|
+
* @return {Array} - The list of neighbors.
|
|
2247
|
+
*/
|
|
2248
|
+
function createNeighborArrayForNode(type, direction, nodeData) {
|
|
2249
|
+
if (type !== "mixed") {
|
|
2250
|
+
if (type === "undirected") return Object.keys(nodeData.undirected);
|
|
2251
|
+
if (typeof direction === "string") return Object.keys(nodeData[direction]);
|
|
2252
|
+
}
|
|
2253
|
+
const neighbors = [];
|
|
2254
|
+
forEachNeighbor(false, type, direction, nodeData, function(key) {
|
|
2255
|
+
neighbors.push(key);
|
|
2256
|
+
});
|
|
2257
|
+
return neighbors;
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Function returning an iterator over the given node's relevant neighbors.
|
|
2261
|
+
*
|
|
2262
|
+
* @param {string} type - Type of neighbors.
|
|
2263
|
+
* @param {string} direction - Direction.
|
|
2264
|
+
* @param {any} nodeData - Target node's data.
|
|
2265
|
+
* @return {Iterator}
|
|
2266
|
+
*/
|
|
2267
|
+
function createDedupedObjectIterator(visited, nodeData, object) {
|
|
2268
|
+
const keys = Object.keys(object);
|
|
2269
|
+
const l = keys.length;
|
|
2270
|
+
let i = 0;
|
|
2271
|
+
return {
|
|
2272
|
+
[Symbol.iterator]() {
|
|
2273
|
+
return this;
|
|
2274
|
+
},
|
|
2275
|
+
next() {
|
|
2276
|
+
let neighborData = null;
|
|
2277
|
+
do {
|
|
2278
|
+
if (i >= l) {
|
|
2279
|
+
if (visited) visited.wrap(object);
|
|
2280
|
+
return { done: true };
|
|
2281
|
+
}
|
|
2282
|
+
const edgeData = object[keys[i++]];
|
|
2283
|
+
const sourceData = edgeData.source;
|
|
2284
|
+
const targetData = edgeData.target;
|
|
2285
|
+
neighborData = sourceData === nodeData ? targetData : sourceData;
|
|
2286
|
+
if (visited && visited.has(neighborData.key)) {
|
|
2287
|
+
neighborData = null;
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
} while (neighborData === null);
|
|
2291
|
+
return {
|
|
2292
|
+
done: false,
|
|
2293
|
+
value: {
|
|
2294
|
+
neighbor: neighborData.key,
|
|
2295
|
+
attributes: neighborData.attributes
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
function createNeighborIterator(type, direction, nodeData) {
|
|
2302
|
+
if (type !== "mixed") {
|
|
2303
|
+
if (type === "undirected") return createDedupedObjectIterator(null, nodeData, nodeData.undirected);
|
|
2304
|
+
if (typeof direction === "string") return createDedupedObjectIterator(null, nodeData, nodeData[direction]);
|
|
2305
|
+
}
|
|
2306
|
+
let iterator = emptyIterator();
|
|
2307
|
+
const visited = new CompositeSetWrapper();
|
|
2308
|
+
if (type !== "undirected") {
|
|
2309
|
+
if (direction !== "out") iterator = chain(iterator, createDedupedObjectIterator(visited, nodeData, nodeData.in));
|
|
2310
|
+
if (direction !== "in") iterator = chain(iterator, createDedupedObjectIterator(visited, nodeData, nodeData.out));
|
|
2311
|
+
}
|
|
2312
|
+
if (type !== "directed") iterator = chain(iterator, createDedupedObjectIterator(visited, nodeData, nodeData.undirected));
|
|
2313
|
+
return iterator;
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Function attaching a neighbors array creator method to the Graph prototype.
|
|
2317
|
+
*
|
|
2318
|
+
* @param {function} Class - Target class.
|
|
2319
|
+
* @param {object} description - Method description.
|
|
2320
|
+
*/
|
|
2321
|
+
function attachNeighborArrayCreator(Class, description) {
|
|
2322
|
+
const { name, type, direction } = description;
|
|
2323
|
+
/**
|
|
2324
|
+
* Function returning an array of certain neighbors.
|
|
2325
|
+
*
|
|
2326
|
+
* @param {any} node - Target node.
|
|
2327
|
+
* @return {array} - The neighbors of neighbors.
|
|
2328
|
+
*
|
|
2329
|
+
* @throws {Error} - Will throw if node is not found in the graph.
|
|
2330
|
+
*/
|
|
2331
|
+
Class.prototype[name] = function(node) {
|
|
2332
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return [];
|
|
2333
|
+
node = "" + node;
|
|
2334
|
+
const nodeData = this._nodes.get(node);
|
|
2335
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${name}: could not find the "${node}" node in the graph.`);
|
|
2336
|
+
return createNeighborArrayForNode(type === "mixed" ? this.type : type, direction, nodeData);
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* Function attaching a neighbors callback iterator method to the Graph prototype.
|
|
2341
|
+
*
|
|
2342
|
+
* @param {function} Class - Target class.
|
|
2343
|
+
* @param {object} description - Method description.
|
|
2344
|
+
*/
|
|
2345
|
+
function attachForEachNeighbor(Class, description) {
|
|
2346
|
+
const { name, type, direction } = description;
|
|
2347
|
+
const forEachName = "forEach" + name[0].toUpperCase() + name.slice(1, -1);
|
|
2348
|
+
/**
|
|
2349
|
+
* Function iterating over all the relevant neighbors using a callback.
|
|
2350
|
+
*
|
|
2351
|
+
* @param {any} node - Target node.
|
|
2352
|
+
* @param {function} callback - Callback to use.
|
|
2353
|
+
* @return {undefined}
|
|
2354
|
+
*
|
|
2355
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2356
|
+
*/
|
|
2357
|
+
Class.prototype[forEachName] = function(node, callback) {
|
|
2358
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return;
|
|
2359
|
+
node = "" + node;
|
|
2360
|
+
const nodeData = this._nodes.get(node);
|
|
2361
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${forEachName}: could not find the "${node}" node in the graph.`);
|
|
2362
|
+
forEachNeighbor(false, type === "mixed" ? this.type : type, direction, nodeData, callback);
|
|
2363
|
+
};
|
|
2364
|
+
/**
|
|
2365
|
+
* Function mapping the relevant neighbors using a callback.
|
|
2366
|
+
*
|
|
2367
|
+
* @param {any} node - Target node.
|
|
2368
|
+
* @param {function} callback - Callback to use.
|
|
2369
|
+
*
|
|
2370
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2371
|
+
*/
|
|
2372
|
+
const mapName = "map" + name[0].toUpperCase() + name.slice(1);
|
|
2373
|
+
Class.prototype[mapName] = function(node, callback) {
|
|
2374
|
+
const result = [];
|
|
2375
|
+
this[forEachName](node, (n, a) => {
|
|
2376
|
+
result.push(callback(n, a));
|
|
2377
|
+
});
|
|
2378
|
+
return result;
|
|
2379
|
+
};
|
|
2380
|
+
/**
|
|
2381
|
+
* Function filtering the relevant neighbors using a callback.
|
|
2382
|
+
*
|
|
2383
|
+
* @param {any} node - Target node.
|
|
2384
|
+
* @param {function} callback - Callback to use.
|
|
2385
|
+
*
|
|
2386
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2387
|
+
*/
|
|
2388
|
+
const filterName = "filter" + name[0].toUpperCase() + name.slice(1);
|
|
2389
|
+
Class.prototype[filterName] = function(node, callback) {
|
|
2390
|
+
const result = [];
|
|
2391
|
+
this[forEachName](node, (n, a) => {
|
|
2392
|
+
if (callback(n, a)) result.push(n);
|
|
2393
|
+
});
|
|
2394
|
+
return result;
|
|
2395
|
+
};
|
|
2396
|
+
/**
|
|
2397
|
+
* Function reducing the relevant neighbors using a callback.
|
|
2398
|
+
*
|
|
2399
|
+
* @param {any} node - Target node.
|
|
2400
|
+
* @param {function} callback - Callback to use.
|
|
2401
|
+
*
|
|
2402
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2403
|
+
*/
|
|
2404
|
+
const reduceName = "reduce" + name[0].toUpperCase() + name.slice(1);
|
|
2405
|
+
Class.prototype[reduceName] = function(node, callback, initialValue) {
|
|
2406
|
+
if (arguments.length < 3) throw new InvalidArgumentsGraphError(`Graph.${reduceName}: missing initial value. You must provide it because the callback takes more than one argument and we cannot infer the initial value from the first iteration, as you could with a simple array.`);
|
|
2407
|
+
let accumulator = initialValue;
|
|
2408
|
+
this[forEachName](node, (n, a) => {
|
|
2409
|
+
accumulator = callback(accumulator, n, a);
|
|
2410
|
+
});
|
|
2411
|
+
return accumulator;
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
/**
|
|
2415
|
+
* Function attaching a breakable neighbors callback iterator method to the
|
|
2416
|
+
* Graph prototype.
|
|
2417
|
+
*
|
|
2418
|
+
* @param {function} Class - Target class.
|
|
2419
|
+
* @param {object} description - Method description.
|
|
2420
|
+
*/
|
|
2421
|
+
function attachFindNeighbor(Class, description) {
|
|
2422
|
+
const { name, type, direction } = description;
|
|
2423
|
+
const capitalizedSingular = name[0].toUpperCase() + name.slice(1, -1);
|
|
2424
|
+
const findName = "find" + capitalizedSingular;
|
|
2425
|
+
/**
|
|
2426
|
+
* Function iterating over all the relevant neighbors using a callback.
|
|
2427
|
+
*
|
|
2428
|
+
* @param {any} node - Target node.
|
|
2429
|
+
* @param {function} callback - Callback to use.
|
|
2430
|
+
* @return {undefined}
|
|
2431
|
+
*
|
|
2432
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2433
|
+
*/
|
|
2434
|
+
Class.prototype[findName] = function(node, callback) {
|
|
2435
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return;
|
|
2436
|
+
node = "" + node;
|
|
2437
|
+
const nodeData = this._nodes.get(node);
|
|
2438
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${findName}: could not find the "${node}" node in the graph.`);
|
|
2439
|
+
return forEachNeighbor(true, type === "mixed" ? this.type : type, direction, nodeData, callback);
|
|
2440
|
+
};
|
|
2441
|
+
/**
|
|
2442
|
+
* Function iterating over all the relevant neighbors to find if any of them
|
|
2443
|
+
* matches the given predicate.
|
|
2444
|
+
*
|
|
2445
|
+
* @param {any} node - Target node.
|
|
2446
|
+
* @param {function} callback - Callback to use.
|
|
2447
|
+
* @return {boolean}
|
|
2448
|
+
*
|
|
2449
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2450
|
+
*/
|
|
2451
|
+
const someName = "some" + capitalizedSingular;
|
|
2452
|
+
Class.prototype[someName] = function(node, callback) {
|
|
2453
|
+
if (this[findName](node, callback)) return true;
|
|
2454
|
+
return false;
|
|
2455
|
+
};
|
|
2456
|
+
/**
|
|
2457
|
+
* Function iterating over all the relevant neighbors to find if all of them
|
|
2458
|
+
* matche the given predicate.
|
|
2459
|
+
*
|
|
2460
|
+
* @param {any} node - Target node.
|
|
2461
|
+
* @param {function} callback - Callback to use.
|
|
2462
|
+
* @return {boolean}
|
|
2463
|
+
*
|
|
2464
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2465
|
+
*/
|
|
2466
|
+
const everyName = "every" + capitalizedSingular;
|
|
2467
|
+
Class.prototype[everyName] = function(node, callback) {
|
|
2468
|
+
if (this[findName](node, (n, a) => {
|
|
2469
|
+
return !callback(n, a);
|
|
2470
|
+
})) return false;
|
|
2471
|
+
return true;
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Function attaching a neighbors callback iterator method to the Graph prototype.
|
|
2476
|
+
*
|
|
2477
|
+
* @param {function} Class - Target class.
|
|
2478
|
+
* @param {object} description - Method description.
|
|
2479
|
+
*/
|
|
2480
|
+
function attachNeighborIteratorCreator(Class, description) {
|
|
2481
|
+
const { name, type, direction } = description;
|
|
2482
|
+
const iteratorName = name.slice(0, -1) + "Entries";
|
|
2483
|
+
/**
|
|
2484
|
+
* Function returning an iterator over all the relevant neighbors.
|
|
2485
|
+
*
|
|
2486
|
+
* @param {any} node - Target node.
|
|
2487
|
+
* @return {Iterator}
|
|
2488
|
+
*
|
|
2489
|
+
* @throws {Error} - Will throw if there are too many arguments.
|
|
2490
|
+
*/
|
|
2491
|
+
Class.prototype[iteratorName] = function(node) {
|
|
2492
|
+
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return emptyIterator();
|
|
2493
|
+
node = "" + node;
|
|
2494
|
+
const nodeData = this._nodes.get(node);
|
|
2495
|
+
if (typeof nodeData === "undefined") throw new NotFoundGraphError(`Graph.${iteratorName}: could not find the "${node}" node in the graph.`);
|
|
2496
|
+
return createNeighborIterator(type === "mixed" ? this.type : type, direction, nodeData);
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Function attaching every neighbor iteration method to the Graph class.
|
|
2501
|
+
*
|
|
2502
|
+
* @param {function} Graph - Graph class.
|
|
2503
|
+
*/
|
|
2504
|
+
function attachNeighborIterationMethods(Graph) {
|
|
2505
|
+
NEIGHBORS_ITERATION.forEach((description) => {
|
|
2506
|
+
attachNeighborArrayCreator(Graph, description);
|
|
2507
|
+
attachForEachNeighbor(Graph, description);
|
|
2508
|
+
attachFindNeighbor(Graph, description);
|
|
2509
|
+
attachNeighborIteratorCreator(Graph, description);
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Graphology Adjacency Iteration
|
|
2514
|
+
* ===============================
|
|
2515
|
+
*
|
|
2516
|
+
* Attaching some methods to the Graph class to be able to iterate over a
|
|
2517
|
+
* graph's adjacency.
|
|
2518
|
+
*/
|
|
2519
|
+
/**
|
|
2520
|
+
* Function iterating over a simple graph's adjacency using a callback.
|
|
2521
|
+
*
|
|
2522
|
+
* @param {boolean} breakable - Can we break?
|
|
2523
|
+
* @param {boolean} assymetric - Whether to emit undirected edges only once.
|
|
2524
|
+
* @param {boolean} disconnectedNodes - Whether to emit disconnected nodes.
|
|
2525
|
+
* @param {Graph} graph - Target Graph instance.
|
|
2526
|
+
* @param {callback} function - Iteration callback.
|
|
2527
|
+
*/
|
|
2528
|
+
function forEachAdjacency(breakable, assymetric, disconnectedNodes, graph, callback) {
|
|
2529
|
+
const iterator = graph._nodes.values();
|
|
2530
|
+
const type = graph.type;
|
|
2531
|
+
let step, sourceData, neighbor, adj, edgeData, targetData, shouldBreak;
|
|
2532
|
+
while (step = iterator.next(), step.done !== true) {
|
|
2533
|
+
let hasEdges = false;
|
|
2534
|
+
sourceData = step.value;
|
|
2535
|
+
if (type !== "undirected") {
|
|
2536
|
+
adj = sourceData.out;
|
|
2537
|
+
for (neighbor in adj) {
|
|
2538
|
+
edgeData = adj[neighbor];
|
|
2539
|
+
do {
|
|
2540
|
+
targetData = edgeData.target;
|
|
2541
|
+
hasEdges = true;
|
|
2542
|
+
shouldBreak = callback(sourceData.key, targetData.key, sourceData.attributes, targetData.attributes, edgeData.key, edgeData.attributes, edgeData.undirected);
|
|
2543
|
+
if (breakable && shouldBreak) return edgeData;
|
|
2544
|
+
edgeData = edgeData.next;
|
|
2545
|
+
} while (edgeData);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (type !== "directed") {
|
|
2549
|
+
adj = sourceData.undirected;
|
|
2550
|
+
for (neighbor in adj) {
|
|
2551
|
+
if (assymetric && sourceData.key > neighbor) continue;
|
|
2552
|
+
edgeData = adj[neighbor];
|
|
2553
|
+
do {
|
|
2554
|
+
targetData = edgeData.target;
|
|
2555
|
+
if (targetData.key !== neighbor) targetData = edgeData.source;
|
|
2556
|
+
hasEdges = true;
|
|
2557
|
+
shouldBreak = callback(sourceData.key, targetData.key, sourceData.attributes, targetData.attributes, edgeData.key, edgeData.attributes, edgeData.undirected);
|
|
2558
|
+
if (breakable && shouldBreak) return edgeData;
|
|
2559
|
+
edgeData = edgeData.next;
|
|
2560
|
+
} while (edgeData);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
if (disconnectedNodes && !hasEdges) {
|
|
2564
|
+
shouldBreak = callback(sourceData.key, null, sourceData.attributes, null, null, null, null);
|
|
2565
|
+
if (breakable && shouldBreak) return null;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Graphology Serialization Utilities
|
|
2571
|
+
* ===================================
|
|
2572
|
+
*
|
|
2573
|
+
* Collection of functions used by the graph serialization schemes.
|
|
2574
|
+
*/
|
|
2575
|
+
/**
|
|
2576
|
+
* Formats internal node data into a serialized node.
|
|
2577
|
+
*
|
|
2578
|
+
* @param {any} key - The node's key.
|
|
2579
|
+
* @param {object} data - Internal node's data.
|
|
2580
|
+
* @return {array} - The serialized node.
|
|
2581
|
+
*/
|
|
2582
|
+
function serializeNode(key, data) {
|
|
2583
|
+
const serialized = { key };
|
|
2584
|
+
if (!isEmpty(data.attributes)) serialized.attributes = assign({}, data.attributes);
|
|
2585
|
+
return serialized;
|
|
2586
|
+
}
|
|
2587
|
+
/**
|
|
2588
|
+
* Formats internal edge data into a serialized edge.
|
|
2589
|
+
*
|
|
2590
|
+
* @param {string} type - The graph's type.
|
|
2591
|
+
* @param {any} key - The edge's key.
|
|
2592
|
+
* @param {object} data - Internal edge's data.
|
|
2593
|
+
* @return {array} - The serialized edge.
|
|
2594
|
+
*/
|
|
2595
|
+
function serializeEdge(type, key, data) {
|
|
2596
|
+
const serialized = {
|
|
2597
|
+
key,
|
|
2598
|
+
source: data.source.key,
|
|
2599
|
+
target: data.target.key
|
|
2600
|
+
};
|
|
2601
|
+
if (!isEmpty(data.attributes)) serialized.attributes = assign({}, data.attributes);
|
|
2602
|
+
if (type === "mixed" && data.undirected) serialized.undirected = true;
|
|
2603
|
+
return serialized;
|
|
2604
|
+
}
|
|
2605
|
+
/**
|
|
2606
|
+
* Checks whether the given value is a serialized node.
|
|
2607
|
+
*
|
|
2608
|
+
* @param {mixed} value - Target value.
|
|
2609
|
+
* @return {string|null}
|
|
2610
|
+
*/
|
|
2611
|
+
function validateSerializedNode(value) {
|
|
2612
|
+
if (!isPlainObject(value)) throw new InvalidArgumentsGraphError("Graph.import: invalid serialized node. A serialized node should be a plain object with at least a \"key\" property.");
|
|
2613
|
+
if (!("key" in value)) throw new InvalidArgumentsGraphError("Graph.import: serialized node is missing its key.");
|
|
2614
|
+
if ("attributes" in value && (!isPlainObject(value.attributes) || value.attributes === null)) throw new InvalidArgumentsGraphError("Graph.import: invalid attributes. Attributes should be a plain object, null or omitted.");
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Checks whether the given value is a serialized edge.
|
|
2618
|
+
*
|
|
2619
|
+
* @param {mixed} value - Target value.
|
|
2620
|
+
* @return {string|null}
|
|
2621
|
+
*/
|
|
2622
|
+
function validateSerializedEdge(value) {
|
|
2623
|
+
if (!isPlainObject(value)) throw new InvalidArgumentsGraphError("Graph.import: invalid serialized edge. A serialized edge should be a plain object with at least a \"source\" & \"target\" property.");
|
|
2624
|
+
if (!("source" in value)) throw new InvalidArgumentsGraphError("Graph.import: serialized edge is missing its source.");
|
|
2625
|
+
if (!("target" in value)) throw new InvalidArgumentsGraphError("Graph.import: serialized edge is missing its target.");
|
|
2626
|
+
if ("attributes" in value && (!isPlainObject(value.attributes) || value.attributes === null)) throw new InvalidArgumentsGraphError("Graph.import: invalid attributes. Attributes should be a plain object, null or omitted.");
|
|
2627
|
+
if ("undirected" in value && typeof value.undirected !== "boolean") throw new InvalidArgumentsGraphError("Graph.import: invalid undirectedness information. Undirected should be boolean or omitted.");
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Constants.
|
|
2631
|
+
*/
|
|
2632
|
+
const INSTANCE_ID = incrementalIdStartingFromRandomByte();
|
|
2633
|
+
/**
|
|
2634
|
+
* Enums.
|
|
2635
|
+
*/
|
|
2636
|
+
const TYPES = new Set([
|
|
2637
|
+
"directed",
|
|
2638
|
+
"undirected",
|
|
2639
|
+
"mixed"
|
|
2640
|
+
]);
|
|
2641
|
+
const EMITTER_PROPS = new Set([
|
|
2642
|
+
"domain",
|
|
2643
|
+
"_events",
|
|
2644
|
+
"_eventsCount",
|
|
2645
|
+
"_maxListeners"
|
|
2646
|
+
]);
|
|
2647
|
+
const EDGE_ADD_METHODS = [
|
|
2648
|
+
{
|
|
2649
|
+
name: (verb) => `${verb}Edge`,
|
|
2650
|
+
generateKey: true
|
|
2651
|
+
},
|
|
2652
|
+
{
|
|
2653
|
+
name: (verb) => `${verb}DirectedEdge`,
|
|
2654
|
+
generateKey: true,
|
|
2655
|
+
type: "directed"
|
|
2656
|
+
},
|
|
2657
|
+
{
|
|
2658
|
+
name: (verb) => `${verb}UndirectedEdge`,
|
|
2659
|
+
generateKey: true,
|
|
2660
|
+
type: "undirected"
|
|
2661
|
+
},
|
|
2662
|
+
{ name: (verb) => `${verb}EdgeWithKey` },
|
|
2663
|
+
{
|
|
2664
|
+
name: (verb) => `${verb}DirectedEdgeWithKey`,
|
|
2665
|
+
type: "directed"
|
|
2666
|
+
},
|
|
2667
|
+
{
|
|
2668
|
+
name: (verb) => `${verb}UndirectedEdgeWithKey`,
|
|
2669
|
+
type: "undirected"
|
|
2670
|
+
}
|
|
2671
|
+
];
|
|
2672
|
+
/**
|
|
2673
|
+
* Default options.
|
|
2674
|
+
*/
|
|
2675
|
+
const DEFAULTS = {
|
|
2676
|
+
allowSelfLoops: true,
|
|
2677
|
+
multi: false,
|
|
2678
|
+
type: "mixed"
|
|
2679
|
+
};
|
|
2680
|
+
/**
|
|
2681
|
+
* Abstract functions used by the Graph class for various methods.
|
|
2682
|
+
*/
|
|
2683
|
+
/**
|
|
2684
|
+
* Internal method used to add a node to the given graph
|
|
2685
|
+
*
|
|
2686
|
+
* @param {Graph} graph - Target graph.
|
|
2687
|
+
* @param {any} node - The node's key.
|
|
2688
|
+
* @param {object} [attributes] - Optional attributes.
|
|
2689
|
+
* @return {NodeData} - Created node data.
|
|
2690
|
+
*/
|
|
2691
|
+
function addNode(graph, node, attributes) {
|
|
2692
|
+
if (attributes && !isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.addNode: invalid attributes. Expecting an object but got "${attributes}"`);
|
|
2693
|
+
node = "" + node;
|
|
2694
|
+
attributes = attributes || {};
|
|
2695
|
+
if (graph._nodes.has(node)) throw new UsageGraphError(`Graph.addNode: the "${node}" node already exist in the graph.`);
|
|
2696
|
+
const data = new graph.NodeDataClass(node, attributes);
|
|
2697
|
+
graph._nodes.set(node, data);
|
|
2698
|
+
graph.emit("nodeAdded", {
|
|
2699
|
+
key: node,
|
|
2700
|
+
attributes
|
|
2701
|
+
});
|
|
2702
|
+
return data;
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* Same as the above but without sanity checks because we call this in contexts
|
|
2706
|
+
* where necessary checks were already done.
|
|
2707
|
+
*/
|
|
2708
|
+
function unsafeAddNode(graph, node, attributes) {
|
|
2709
|
+
const data = new graph.NodeDataClass(node, attributes);
|
|
2710
|
+
graph._nodes.set(node, data);
|
|
2711
|
+
graph.emit("nodeAdded", {
|
|
2712
|
+
key: node,
|
|
2713
|
+
attributes
|
|
2714
|
+
});
|
|
2715
|
+
return data;
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Internal method used to add an arbitrary edge to the given graph.
|
|
2719
|
+
*
|
|
2720
|
+
* @param {Graph} graph - Target graph.
|
|
2721
|
+
* @param {string} name - Name of the child method for errors.
|
|
2722
|
+
* @param {boolean} mustGenerateKey - Should the graph generate an id?
|
|
2723
|
+
* @param {boolean} undirected - Whether the edge is undirected.
|
|
2724
|
+
* @param {any} edge - The edge's key.
|
|
2725
|
+
* @param {any} source - The source node.
|
|
2726
|
+
* @param {any} target - The target node.
|
|
2727
|
+
* @param {object} [attributes] - Optional attributes.
|
|
2728
|
+
* @return {any} - The edge.
|
|
2729
|
+
*
|
|
2730
|
+
* @throws {Error} - Will throw if the graph is of the wrong type.
|
|
2731
|
+
* @throws {Error} - Will throw if the given attributes are not an object.
|
|
2732
|
+
* @throws {Error} - Will throw if source or target doesn't exist.
|
|
2733
|
+
* @throws {Error} - Will throw if the edge already exist.
|
|
2734
|
+
*/
|
|
2735
|
+
function addEdge(graph, name, mustGenerateKey, undirected, edge, source, target, attributes) {
|
|
2736
|
+
if (!undirected && graph.type === "undirected") throw new UsageGraphError(`Graph.${name}: you cannot add a directed edge to an undirected graph. Use the #.addEdge or #.addUndirectedEdge instead.`);
|
|
2737
|
+
if (undirected && graph.type === "directed") throw new UsageGraphError(`Graph.${name}: you cannot add an undirected edge to a directed graph. Use the #.addEdge or #.addDirectedEdge instead.`);
|
|
2738
|
+
if (attributes && !isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.${name}: invalid attributes. Expecting an object but got "${attributes}"`);
|
|
2739
|
+
source = "" + source;
|
|
2740
|
+
target = "" + target;
|
|
2741
|
+
attributes = attributes || {};
|
|
2742
|
+
if (!graph.allowSelfLoops && source === target) throw new UsageGraphError(`Graph.${name}: source & target are the same ("${source}"), thus creating a loop explicitly forbidden by this graph 'allowSelfLoops' option set to false.`);
|
|
2743
|
+
const sourceData = graph._nodes.get(source), targetData = graph._nodes.get(target);
|
|
2744
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.${name}: source node "${source}" not found.`);
|
|
2745
|
+
if (!targetData) throw new NotFoundGraphError(`Graph.${name}: target node "${target}" not found.`);
|
|
2746
|
+
const eventData = {
|
|
2747
|
+
key: null,
|
|
2748
|
+
undirected,
|
|
2749
|
+
source,
|
|
2750
|
+
target,
|
|
2751
|
+
attributes
|
|
2752
|
+
};
|
|
2753
|
+
if (mustGenerateKey) edge = graph._edgeKeyGenerator();
|
|
2754
|
+
else {
|
|
2755
|
+
edge = "" + edge;
|
|
2756
|
+
if (graph._edges.has(edge)) throw new UsageGraphError(`Graph.${name}: the "${edge}" edge already exists in the graph.`);
|
|
2757
|
+
}
|
|
2758
|
+
if (!graph.multi && (undirected ? typeof sourceData.undirected[target] !== "undefined" : typeof sourceData.out[target] !== "undefined")) throw new UsageGraphError(`Graph.${name}: an edge linking "${source}" to "${target}" already exists. If you really want to add multiple edges linking those nodes, you should create a multi graph by using the 'multi' option.`);
|
|
2759
|
+
const edgeData = new EdgeData(undirected, edge, sourceData, targetData, attributes);
|
|
2760
|
+
graph._edges.set(edge, edgeData);
|
|
2761
|
+
const isSelfLoop = source === target;
|
|
2762
|
+
if (undirected) {
|
|
2763
|
+
sourceData.undirectedDegree++;
|
|
2764
|
+
targetData.undirectedDegree++;
|
|
2765
|
+
if (isSelfLoop) {
|
|
2766
|
+
sourceData.undirectedLoops++;
|
|
2767
|
+
graph._undirectedSelfLoopCount++;
|
|
2768
|
+
}
|
|
2769
|
+
} else {
|
|
2770
|
+
sourceData.outDegree++;
|
|
2771
|
+
targetData.inDegree++;
|
|
2772
|
+
if (isSelfLoop) {
|
|
2773
|
+
sourceData.directedLoops++;
|
|
2774
|
+
graph._directedSelfLoopCount++;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
if (graph.multi) edgeData.attachMulti();
|
|
2778
|
+
else edgeData.attach();
|
|
2779
|
+
if (undirected) graph._undirectedSize++;
|
|
2780
|
+
else graph._directedSize++;
|
|
2781
|
+
eventData.key = edge;
|
|
2782
|
+
graph.emit("edgeAdded", eventData);
|
|
2783
|
+
return edge;
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Internal method used to add an arbitrary edge to the given graph.
|
|
2787
|
+
*
|
|
2788
|
+
* @param {Graph} graph - Target graph.
|
|
2789
|
+
* @param {string} name - Name of the child method for errors.
|
|
2790
|
+
* @param {boolean} mustGenerateKey - Should the graph generate an id?
|
|
2791
|
+
* @param {boolean} undirected - Whether the edge is undirected.
|
|
2792
|
+
* @param {any} edge - The edge's key.
|
|
2793
|
+
* @param {any} source - The source node.
|
|
2794
|
+
* @param {any} target - The target node.
|
|
2795
|
+
* @param {object} [attributes] - Optional attributes.
|
|
2796
|
+
* @param {boolean} [asUpdater] - Are we updating or merging?
|
|
2797
|
+
* @return {any} - The edge.
|
|
2798
|
+
*
|
|
2799
|
+
* @throws {Error} - Will throw if the graph is of the wrong type.
|
|
2800
|
+
* @throws {Error} - Will throw if the given attributes are not an object.
|
|
2801
|
+
* @throws {Error} - Will throw if source or target doesn't exist.
|
|
2802
|
+
* @throws {Error} - Will throw if the edge already exist.
|
|
2803
|
+
*/
|
|
2804
|
+
function mergeEdge(graph, name, mustGenerateKey, undirected, edge, source, target, attributes, asUpdater) {
|
|
2805
|
+
if (!undirected && graph.type === "undirected") throw new UsageGraphError(`Graph.${name}: you cannot merge/update a directed edge to an undirected graph. Use the #.mergeEdge/#.updateEdge or #.addUndirectedEdge instead.`);
|
|
2806
|
+
if (undirected && graph.type === "directed") throw new UsageGraphError(`Graph.${name}: you cannot merge/update an undirected edge to a directed graph. Use the #.mergeEdge/#.updateEdge or #.addDirectedEdge instead.`);
|
|
2807
|
+
if (attributes) {
|
|
2808
|
+
if (asUpdater) {
|
|
2809
|
+
if (typeof attributes !== "function") throw new InvalidArgumentsGraphError(`Graph.${name}: invalid updater function. Expecting a function but got "${attributes}"`);
|
|
2810
|
+
} else if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.${name}: invalid attributes. Expecting an object but got "${attributes}"`);
|
|
2811
|
+
}
|
|
2812
|
+
source = "" + source;
|
|
2813
|
+
target = "" + target;
|
|
2814
|
+
let updater;
|
|
2815
|
+
if (asUpdater) {
|
|
2816
|
+
updater = attributes;
|
|
2817
|
+
attributes = void 0;
|
|
2818
|
+
}
|
|
2819
|
+
if (!graph.allowSelfLoops && source === target) throw new UsageGraphError(`Graph.${name}: source & target are the same ("${source}"), thus creating a loop explicitly forbidden by this graph 'allowSelfLoops' option set to false.`);
|
|
2820
|
+
let sourceData = graph._nodes.get(source);
|
|
2821
|
+
let targetData = graph._nodes.get(target);
|
|
2822
|
+
let edgeData;
|
|
2823
|
+
let alreadyExistingEdgeData;
|
|
2824
|
+
if (!mustGenerateKey) {
|
|
2825
|
+
edgeData = graph._edges.get(edge);
|
|
2826
|
+
if (edgeData) {
|
|
2827
|
+
if (edgeData.source.key !== source || edgeData.target.key !== target) {
|
|
2828
|
+
if (!undirected || edgeData.source.key !== target || edgeData.target.key !== source) throw new UsageGraphError(`Graph.${name}: inconsistency detected when attempting to merge the "${edge}" edge with "${source}" source & "${target}" target vs. ("${edgeData.source.key}", "${edgeData.target.key}").`);
|
|
2829
|
+
}
|
|
2830
|
+
alreadyExistingEdgeData = edgeData;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
if (!alreadyExistingEdgeData && !graph.multi && sourceData) alreadyExistingEdgeData = undirected ? sourceData.undirected[target] : sourceData.out[target];
|
|
2834
|
+
if (alreadyExistingEdgeData) {
|
|
2835
|
+
const info = [
|
|
2836
|
+
alreadyExistingEdgeData.key,
|
|
2837
|
+
false,
|
|
2838
|
+
false,
|
|
2839
|
+
false
|
|
2840
|
+
];
|
|
2841
|
+
if (asUpdater ? !updater : !attributes) return info;
|
|
2842
|
+
if (asUpdater) {
|
|
2843
|
+
const oldAttributes = alreadyExistingEdgeData.attributes;
|
|
2844
|
+
alreadyExistingEdgeData.attributes = updater(oldAttributes);
|
|
2845
|
+
graph.emit("edgeAttributesUpdated", {
|
|
2846
|
+
type: "replace",
|
|
2847
|
+
key: alreadyExistingEdgeData.key,
|
|
2848
|
+
attributes: alreadyExistingEdgeData.attributes
|
|
2849
|
+
});
|
|
2850
|
+
} else {
|
|
2851
|
+
assign(alreadyExistingEdgeData.attributes, attributes);
|
|
2852
|
+
graph.emit("edgeAttributesUpdated", {
|
|
2853
|
+
type: "merge",
|
|
2854
|
+
key: alreadyExistingEdgeData.key,
|
|
2855
|
+
attributes: alreadyExistingEdgeData.attributes,
|
|
2856
|
+
data: attributes
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
return info;
|
|
2860
|
+
}
|
|
2861
|
+
attributes = attributes || {};
|
|
2862
|
+
if (asUpdater && updater) attributes = updater(attributes);
|
|
2863
|
+
const eventData = {
|
|
2864
|
+
key: null,
|
|
2865
|
+
undirected,
|
|
2866
|
+
source,
|
|
2867
|
+
target,
|
|
2868
|
+
attributes
|
|
2869
|
+
};
|
|
2870
|
+
if (mustGenerateKey) edge = graph._edgeKeyGenerator();
|
|
2871
|
+
else {
|
|
2872
|
+
edge = "" + edge;
|
|
2873
|
+
if (graph._edges.has(edge)) throw new UsageGraphError(`Graph.${name}: the "${edge}" edge already exists in the graph.`);
|
|
2874
|
+
}
|
|
2875
|
+
let sourceWasAdded = false;
|
|
2876
|
+
let targetWasAdded = false;
|
|
2877
|
+
if (!sourceData) {
|
|
2878
|
+
sourceData = unsafeAddNode(graph, source, {});
|
|
2879
|
+
sourceWasAdded = true;
|
|
2880
|
+
if (source === target) {
|
|
2881
|
+
targetData = sourceData;
|
|
2882
|
+
targetWasAdded = true;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
if (!targetData) {
|
|
2886
|
+
targetData = unsafeAddNode(graph, target, {});
|
|
2887
|
+
targetWasAdded = true;
|
|
2888
|
+
}
|
|
2889
|
+
edgeData = new EdgeData(undirected, edge, sourceData, targetData, attributes);
|
|
2890
|
+
graph._edges.set(edge, edgeData);
|
|
2891
|
+
const isSelfLoop = source === target;
|
|
2892
|
+
if (undirected) {
|
|
2893
|
+
sourceData.undirectedDegree++;
|
|
2894
|
+
targetData.undirectedDegree++;
|
|
2895
|
+
if (isSelfLoop) {
|
|
2896
|
+
sourceData.undirectedLoops++;
|
|
2897
|
+
graph._undirectedSelfLoopCount++;
|
|
2898
|
+
}
|
|
2899
|
+
} else {
|
|
2900
|
+
sourceData.outDegree++;
|
|
2901
|
+
targetData.inDegree++;
|
|
2902
|
+
if (isSelfLoop) {
|
|
2903
|
+
sourceData.directedLoops++;
|
|
2904
|
+
graph._directedSelfLoopCount++;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
if (graph.multi) edgeData.attachMulti();
|
|
2908
|
+
else edgeData.attach();
|
|
2909
|
+
if (undirected) graph._undirectedSize++;
|
|
2910
|
+
else graph._directedSize++;
|
|
2911
|
+
eventData.key = edge;
|
|
2912
|
+
graph.emit("edgeAdded", eventData);
|
|
2913
|
+
return [
|
|
2914
|
+
edge,
|
|
2915
|
+
true,
|
|
2916
|
+
sourceWasAdded,
|
|
2917
|
+
targetWasAdded
|
|
2918
|
+
];
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Internal method used to drop an edge.
|
|
2922
|
+
*
|
|
2923
|
+
* @param {Graph} graph - Target graph.
|
|
2924
|
+
* @param {EdgeData} edgeData - Data of the edge to drop.
|
|
2925
|
+
*/
|
|
2926
|
+
function dropEdgeFromData(graph, edgeData) {
|
|
2927
|
+
graph._edges.delete(edgeData.key);
|
|
2928
|
+
const { source: sourceData, target: targetData, attributes } = edgeData;
|
|
2929
|
+
const undirected = edgeData.undirected;
|
|
2930
|
+
const isSelfLoop = sourceData === targetData;
|
|
2931
|
+
if (undirected) {
|
|
2932
|
+
sourceData.undirectedDegree--;
|
|
2933
|
+
targetData.undirectedDegree--;
|
|
2934
|
+
if (isSelfLoop) {
|
|
2935
|
+
sourceData.undirectedLoops--;
|
|
2936
|
+
graph._undirectedSelfLoopCount--;
|
|
2937
|
+
}
|
|
2938
|
+
} else {
|
|
2939
|
+
sourceData.outDegree--;
|
|
2940
|
+
targetData.inDegree--;
|
|
2941
|
+
if (isSelfLoop) {
|
|
2942
|
+
sourceData.directedLoops--;
|
|
2943
|
+
graph._directedSelfLoopCount--;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
if (graph.multi) edgeData.detachMulti();
|
|
2947
|
+
else edgeData.detach();
|
|
2948
|
+
if (undirected) graph._undirectedSize--;
|
|
2949
|
+
else graph._directedSize--;
|
|
2950
|
+
graph.emit("edgeDropped", {
|
|
2951
|
+
key: edgeData.key,
|
|
2952
|
+
attributes,
|
|
2953
|
+
source: sourceData.key,
|
|
2954
|
+
target: targetData.key,
|
|
2955
|
+
undirected
|
|
2956
|
+
});
|
|
2957
|
+
}
|
|
2958
|
+
/**
|
|
2959
|
+
* Graph class
|
|
2960
|
+
*
|
|
2961
|
+
* @constructor
|
|
2962
|
+
* @param {object} [options] - Options:
|
|
2963
|
+
* @param {boolean} [allowSelfLoops] - Allow self loops?
|
|
2964
|
+
* @param {string} [type] - Type of the graph.
|
|
2965
|
+
* @param {boolean} [map] - Allow references as keys?
|
|
2966
|
+
* @param {boolean} [multi] - Allow parallel edges?
|
|
2967
|
+
*
|
|
2968
|
+
* @throws {Error} - Will throw if the arguments are not valid.
|
|
2969
|
+
*/
|
|
2970
|
+
var Graph = class Graph extends EventEmitter {
|
|
2971
|
+
constructor(options) {
|
|
2972
|
+
super();
|
|
2973
|
+
options = assign({}, DEFAULTS, options);
|
|
2974
|
+
if (typeof options.multi !== "boolean") throw new InvalidArgumentsGraphError(`Graph.constructor: invalid 'multi' option. Expecting a boolean but got "${options.multi}".`);
|
|
2975
|
+
if (!TYPES.has(options.type)) throw new InvalidArgumentsGraphError(`Graph.constructor: invalid 'type' option. Should be one of "mixed", "directed" or "undirected" but got "${options.type}".`);
|
|
2976
|
+
if (typeof options.allowSelfLoops !== "boolean") throw new InvalidArgumentsGraphError(`Graph.constructor: invalid 'allowSelfLoops' option. Expecting a boolean but got "${options.allowSelfLoops}".`);
|
|
2977
|
+
const NodeDataClass = options.type === "mixed" ? MixedNodeData : options.type === "directed" ? DirectedNodeData : UndirectedNodeData;
|
|
2978
|
+
privateProperty(this, "NodeDataClass", NodeDataClass);
|
|
2979
|
+
const instancePrefix = "geid_" + INSTANCE_ID() + "_";
|
|
2980
|
+
let edgeId = 0;
|
|
2981
|
+
const edgeKeyGenerator = () => {
|
|
2982
|
+
let availableEdgeKey;
|
|
2983
|
+
do
|
|
2984
|
+
availableEdgeKey = instancePrefix + edgeId++;
|
|
2985
|
+
while (this._edges.has(availableEdgeKey));
|
|
2986
|
+
return availableEdgeKey;
|
|
2987
|
+
};
|
|
2988
|
+
privateProperty(this, "_attributes", {});
|
|
2989
|
+
privateProperty(this, "_nodes", /* @__PURE__ */ new Map());
|
|
2990
|
+
privateProperty(this, "_edges", /* @__PURE__ */ new Map());
|
|
2991
|
+
privateProperty(this, "_directedSize", 0);
|
|
2992
|
+
privateProperty(this, "_undirectedSize", 0);
|
|
2993
|
+
privateProperty(this, "_directedSelfLoopCount", 0);
|
|
2994
|
+
privateProperty(this, "_undirectedSelfLoopCount", 0);
|
|
2995
|
+
privateProperty(this, "_edgeKeyGenerator", edgeKeyGenerator);
|
|
2996
|
+
privateProperty(this, "_options", options);
|
|
2997
|
+
EMITTER_PROPS.forEach((prop) => privateProperty(this, prop, this[prop]));
|
|
2998
|
+
readOnlyProperty(this, "order", () => this._nodes.size);
|
|
2999
|
+
readOnlyProperty(this, "size", () => this._edges.size);
|
|
3000
|
+
readOnlyProperty(this, "directedSize", () => this._directedSize);
|
|
3001
|
+
readOnlyProperty(this, "undirectedSize", () => this._undirectedSize);
|
|
3002
|
+
readOnlyProperty(this, "selfLoopCount", () => this._directedSelfLoopCount + this._undirectedSelfLoopCount);
|
|
3003
|
+
readOnlyProperty(this, "directedSelfLoopCount", () => this._directedSelfLoopCount);
|
|
3004
|
+
readOnlyProperty(this, "undirectedSelfLoopCount", () => this._undirectedSelfLoopCount);
|
|
3005
|
+
readOnlyProperty(this, "multi", this._options.multi);
|
|
3006
|
+
readOnlyProperty(this, "type", this._options.type);
|
|
3007
|
+
readOnlyProperty(this, "allowSelfLoops", this._options.allowSelfLoops);
|
|
3008
|
+
readOnlyProperty(this, "implementation", () => "graphology");
|
|
3009
|
+
}
|
|
3010
|
+
_resetInstanceCounters() {
|
|
3011
|
+
this._directedSize = 0;
|
|
3012
|
+
this._undirectedSize = 0;
|
|
3013
|
+
this._directedSelfLoopCount = 0;
|
|
3014
|
+
this._undirectedSelfLoopCount = 0;
|
|
3015
|
+
}
|
|
3016
|
+
/**---------------------------------------------------------------------------
|
|
3017
|
+
* Read
|
|
3018
|
+
**---------------------------------------------------------------------------
|
|
3019
|
+
*/
|
|
3020
|
+
/**
|
|
3021
|
+
* Method returning whether the given node is found in the graph.
|
|
3022
|
+
*
|
|
3023
|
+
* @param {any} node - The node.
|
|
3024
|
+
* @return {boolean}
|
|
3025
|
+
*/
|
|
3026
|
+
hasNode(node) {
|
|
3027
|
+
return this._nodes.has("" + node);
|
|
3028
|
+
}
|
|
3029
|
+
/**
|
|
3030
|
+
* Method returning whether the given directed edge is found in the graph.
|
|
3031
|
+
*
|
|
3032
|
+
* Arity 1:
|
|
3033
|
+
* @param {any} edge - The edge's key.
|
|
3034
|
+
*
|
|
3035
|
+
* Arity 2:
|
|
3036
|
+
* @param {any} source - The edge's source.
|
|
3037
|
+
* @param {any} target - The edge's target.
|
|
3038
|
+
*
|
|
3039
|
+
* @return {boolean}
|
|
3040
|
+
*
|
|
3041
|
+
* @throws {Error} - Will throw if the arguments are invalid.
|
|
3042
|
+
*/
|
|
3043
|
+
hasDirectedEdge(source, target) {
|
|
3044
|
+
if (this.type === "undirected") return false;
|
|
3045
|
+
if (arguments.length === 1) {
|
|
3046
|
+
const edge = "" + source;
|
|
3047
|
+
const edgeData = this._edges.get(edge);
|
|
3048
|
+
return !!edgeData && !edgeData.undirected;
|
|
3049
|
+
} else if (arguments.length === 2) {
|
|
3050
|
+
source = "" + source;
|
|
3051
|
+
target = "" + target;
|
|
3052
|
+
const nodeData = this._nodes.get(source);
|
|
3053
|
+
if (!nodeData) return false;
|
|
3054
|
+
return nodeData.out.hasOwnProperty(target);
|
|
3055
|
+
}
|
|
3056
|
+
throw new InvalidArgumentsGraphError(`Graph.hasDirectedEdge: invalid arity (${arguments.length}, instead of 1 or 2). You can either ask for an edge id or for the existence of an edge between a source & a target.`);
|
|
3057
|
+
}
|
|
3058
|
+
/**
|
|
3059
|
+
* Method returning whether the given undirected edge is found in the graph.
|
|
3060
|
+
*
|
|
3061
|
+
* Arity 1:
|
|
3062
|
+
* @param {any} edge - The edge's key.
|
|
3063
|
+
*
|
|
3064
|
+
* Arity 2:
|
|
3065
|
+
* @param {any} source - The edge's source.
|
|
3066
|
+
* @param {any} target - The edge's target.
|
|
3067
|
+
*
|
|
3068
|
+
* @return {boolean}
|
|
3069
|
+
*
|
|
3070
|
+
* @throws {Error} - Will throw if the arguments are invalid.
|
|
3071
|
+
*/
|
|
3072
|
+
hasUndirectedEdge(source, target) {
|
|
3073
|
+
if (this.type === "directed") return false;
|
|
3074
|
+
if (arguments.length === 1) {
|
|
3075
|
+
const edge = "" + source;
|
|
3076
|
+
const edgeData = this._edges.get(edge);
|
|
3077
|
+
return !!edgeData && edgeData.undirected;
|
|
3078
|
+
} else if (arguments.length === 2) {
|
|
3079
|
+
source = "" + source;
|
|
3080
|
+
target = "" + target;
|
|
3081
|
+
const nodeData = this._nodes.get(source);
|
|
3082
|
+
if (!nodeData) return false;
|
|
3083
|
+
return nodeData.undirected.hasOwnProperty(target);
|
|
3084
|
+
}
|
|
3085
|
+
throw new InvalidArgumentsGraphError(`Graph.hasDirectedEdge: invalid arity (${arguments.length}, instead of 1 or 2). You can either ask for an edge id or for the existence of an edge between a source & a target.`);
|
|
3086
|
+
}
|
|
3087
|
+
/**
|
|
3088
|
+
* Method returning whether the given edge is found in the graph.
|
|
3089
|
+
*
|
|
3090
|
+
* Arity 1:
|
|
3091
|
+
* @param {any} edge - The edge's key.
|
|
3092
|
+
*
|
|
3093
|
+
* Arity 2:
|
|
3094
|
+
* @param {any} source - The edge's source.
|
|
3095
|
+
* @param {any} target - The edge's target.
|
|
3096
|
+
*
|
|
3097
|
+
* @return {boolean}
|
|
3098
|
+
*
|
|
3099
|
+
* @throws {Error} - Will throw if the arguments are invalid.
|
|
3100
|
+
*/
|
|
3101
|
+
hasEdge(source, target) {
|
|
3102
|
+
if (arguments.length === 1) {
|
|
3103
|
+
const edge = "" + source;
|
|
3104
|
+
return this._edges.has(edge);
|
|
3105
|
+
} else if (arguments.length === 2) {
|
|
3106
|
+
source = "" + source;
|
|
3107
|
+
target = "" + target;
|
|
3108
|
+
const nodeData = this._nodes.get(source);
|
|
3109
|
+
if (!nodeData) return false;
|
|
3110
|
+
return typeof nodeData.out !== "undefined" && nodeData.out.hasOwnProperty(target) || typeof nodeData.undirected !== "undefined" && nodeData.undirected.hasOwnProperty(target);
|
|
3111
|
+
}
|
|
3112
|
+
throw new InvalidArgumentsGraphError(`Graph.hasEdge: invalid arity (${arguments.length}, instead of 1 or 2). You can either ask for an edge id or for the existence of an edge between a source & a target.`);
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* Method returning the edge matching source & target in a directed fashion.
|
|
3116
|
+
*
|
|
3117
|
+
* @param {any} source - The edge's source.
|
|
3118
|
+
* @param {any} target - The edge's target.
|
|
3119
|
+
*
|
|
3120
|
+
* @return {any|undefined}
|
|
3121
|
+
*
|
|
3122
|
+
* @throws {Error} - Will throw if the graph is multi.
|
|
3123
|
+
* @throws {Error} - Will throw if source or target doesn't exist.
|
|
3124
|
+
*/
|
|
3125
|
+
directedEdge(source, target) {
|
|
3126
|
+
if (this.type === "undirected") return;
|
|
3127
|
+
source = "" + source;
|
|
3128
|
+
target = "" + target;
|
|
3129
|
+
if (this.multi) throw new UsageGraphError("Graph.directedEdge: this method is irrelevant with multigraphs since there might be multiple edges between source & target. See #.directedEdges instead.");
|
|
3130
|
+
const sourceData = this._nodes.get(source);
|
|
3131
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.directedEdge: could not find the "${source}" source node in the graph.`);
|
|
3132
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.directedEdge: could not find the "${target}" target node in the graph.`);
|
|
3133
|
+
const edgeData = sourceData.out && sourceData.out[target] || void 0;
|
|
3134
|
+
if (edgeData) return edgeData.key;
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* Method returning the edge matching source & target in a undirected fashion.
|
|
3138
|
+
*
|
|
3139
|
+
* @param {any} source - The edge's source.
|
|
3140
|
+
* @param {any} target - The edge's target.
|
|
3141
|
+
*
|
|
3142
|
+
* @return {any|undefined}
|
|
3143
|
+
*
|
|
3144
|
+
* @throws {Error} - Will throw if the graph is multi.
|
|
3145
|
+
* @throws {Error} - Will throw if source or target doesn't exist.
|
|
3146
|
+
*/
|
|
3147
|
+
undirectedEdge(source, target) {
|
|
3148
|
+
if (this.type === "directed") return;
|
|
3149
|
+
source = "" + source;
|
|
3150
|
+
target = "" + target;
|
|
3151
|
+
if (this.multi) throw new UsageGraphError("Graph.undirectedEdge: this method is irrelevant with multigraphs since there might be multiple edges between source & target. See #.undirectedEdges instead.");
|
|
3152
|
+
const sourceData = this._nodes.get(source);
|
|
3153
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.undirectedEdge: could not find the "${source}" source node in the graph.`);
|
|
3154
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.undirectedEdge: could not find the "${target}" target node in the graph.`);
|
|
3155
|
+
const edgeData = sourceData.undirected && sourceData.undirected[target] || void 0;
|
|
3156
|
+
if (edgeData) return edgeData.key;
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Method returning the edge matching source & target in a mixed fashion.
|
|
3160
|
+
*
|
|
3161
|
+
* @param {any} source - The edge's source.
|
|
3162
|
+
* @param {any} target - The edge's target.
|
|
3163
|
+
*
|
|
3164
|
+
* @return {any|undefined}
|
|
3165
|
+
*
|
|
3166
|
+
* @throws {Error} - Will throw if the graph is multi.
|
|
3167
|
+
* @throws {Error} - Will throw if source or target doesn't exist.
|
|
3168
|
+
*/
|
|
3169
|
+
edge(source, target) {
|
|
3170
|
+
if (this.multi) throw new UsageGraphError("Graph.edge: this method is irrelevant with multigraphs since there might be multiple edges between source & target. See #.edges instead.");
|
|
3171
|
+
source = "" + source;
|
|
3172
|
+
target = "" + target;
|
|
3173
|
+
const sourceData = this._nodes.get(source);
|
|
3174
|
+
if (!sourceData) throw new NotFoundGraphError(`Graph.edge: could not find the "${source}" source node in the graph.`);
|
|
3175
|
+
if (!this._nodes.has(target)) throw new NotFoundGraphError(`Graph.edge: could not find the "${target}" target node in the graph.`);
|
|
3176
|
+
const edgeData = sourceData.out && sourceData.out[target] || sourceData.undirected && sourceData.undirected[target] || void 0;
|
|
3177
|
+
if (edgeData) return edgeData.key;
|
|
3178
|
+
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Method returning whether two nodes are directed neighbors.
|
|
3181
|
+
*
|
|
3182
|
+
* @param {any} node - The node's key.
|
|
3183
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3184
|
+
* @return {boolean}
|
|
3185
|
+
*
|
|
3186
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3187
|
+
*/
|
|
3188
|
+
areDirectedNeighbors(node, neighbor) {
|
|
3189
|
+
node = "" + node;
|
|
3190
|
+
neighbor = "" + neighbor;
|
|
3191
|
+
const nodeData = this._nodes.get(node);
|
|
3192
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areDirectedNeighbors: could not find the "${node}" node in the graph.`);
|
|
3193
|
+
if (this.type === "undirected") return false;
|
|
3194
|
+
return neighbor in nodeData.in || neighbor in nodeData.out;
|
|
3195
|
+
}
|
|
3196
|
+
/**
|
|
3197
|
+
* Method returning whether two nodes are out neighbors.
|
|
3198
|
+
*
|
|
3199
|
+
* @param {any} node - The node's key.
|
|
3200
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3201
|
+
* @return {boolean}
|
|
3202
|
+
*
|
|
3203
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3204
|
+
*/
|
|
3205
|
+
areOutNeighbors(node, neighbor) {
|
|
3206
|
+
node = "" + node;
|
|
3207
|
+
neighbor = "" + neighbor;
|
|
3208
|
+
const nodeData = this._nodes.get(node);
|
|
3209
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areOutNeighbors: could not find the "${node}" node in the graph.`);
|
|
3210
|
+
if (this.type === "undirected") return false;
|
|
3211
|
+
return neighbor in nodeData.out;
|
|
3212
|
+
}
|
|
3213
|
+
/**
|
|
3214
|
+
* Method returning whether two nodes are in neighbors.
|
|
3215
|
+
*
|
|
3216
|
+
* @param {any} node - The node's key.
|
|
3217
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3218
|
+
* @return {boolean}
|
|
3219
|
+
*
|
|
3220
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3221
|
+
*/
|
|
3222
|
+
areInNeighbors(node, neighbor) {
|
|
3223
|
+
node = "" + node;
|
|
3224
|
+
neighbor = "" + neighbor;
|
|
3225
|
+
const nodeData = this._nodes.get(node);
|
|
3226
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areInNeighbors: could not find the "${node}" node in the graph.`);
|
|
3227
|
+
if (this.type === "undirected") return false;
|
|
3228
|
+
return neighbor in nodeData.in;
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* Method returning whether two nodes are undirected neighbors.
|
|
3232
|
+
*
|
|
3233
|
+
* @param {any} node - The node's key.
|
|
3234
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3235
|
+
* @return {boolean}
|
|
3236
|
+
*
|
|
3237
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3238
|
+
*/
|
|
3239
|
+
areUndirectedNeighbors(node, neighbor) {
|
|
3240
|
+
node = "" + node;
|
|
3241
|
+
neighbor = "" + neighbor;
|
|
3242
|
+
const nodeData = this._nodes.get(node);
|
|
3243
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areUndirectedNeighbors: could not find the "${node}" node in the graph.`);
|
|
3244
|
+
if (this.type === "directed") return false;
|
|
3245
|
+
return neighbor in nodeData.undirected;
|
|
3246
|
+
}
|
|
3247
|
+
/**
|
|
3248
|
+
* Method returning whether two nodes are neighbors.
|
|
3249
|
+
*
|
|
3250
|
+
* @param {any} node - The node's key.
|
|
3251
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3252
|
+
* @return {boolean}
|
|
3253
|
+
*
|
|
3254
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3255
|
+
*/
|
|
3256
|
+
areNeighbors(node, neighbor) {
|
|
3257
|
+
node = "" + node;
|
|
3258
|
+
neighbor = "" + neighbor;
|
|
3259
|
+
const nodeData = this._nodes.get(node);
|
|
3260
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areNeighbors: could not find the "${node}" node in the graph.`);
|
|
3261
|
+
if (this.type !== "undirected") {
|
|
3262
|
+
if (neighbor in nodeData.in || neighbor in nodeData.out) return true;
|
|
3263
|
+
}
|
|
3264
|
+
if (this.type !== "directed") {
|
|
3265
|
+
if (neighbor in nodeData.undirected) return true;
|
|
3266
|
+
}
|
|
3267
|
+
return false;
|
|
3268
|
+
}
|
|
3269
|
+
/**
|
|
3270
|
+
* Method returning whether two nodes are inbound neighbors.
|
|
3271
|
+
*
|
|
3272
|
+
* @param {any} node - The node's key.
|
|
3273
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3274
|
+
* @return {boolean}
|
|
3275
|
+
*
|
|
3276
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3277
|
+
*/
|
|
3278
|
+
areInboundNeighbors(node, neighbor) {
|
|
3279
|
+
node = "" + node;
|
|
3280
|
+
neighbor = "" + neighbor;
|
|
3281
|
+
const nodeData = this._nodes.get(node);
|
|
3282
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areInboundNeighbors: could not find the "${node}" node in the graph.`);
|
|
3283
|
+
if (this.type !== "undirected") {
|
|
3284
|
+
if (neighbor in nodeData.in) return true;
|
|
3285
|
+
}
|
|
3286
|
+
if (this.type !== "directed") {
|
|
3287
|
+
if (neighbor in nodeData.undirected) return true;
|
|
3288
|
+
}
|
|
3289
|
+
return false;
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Method returning whether two nodes are outbound neighbors.
|
|
3293
|
+
*
|
|
3294
|
+
* @param {any} node - The node's key.
|
|
3295
|
+
* @param {any} neighbor - The neighbor's key.
|
|
3296
|
+
* @return {boolean}
|
|
3297
|
+
*
|
|
3298
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3299
|
+
*/
|
|
3300
|
+
areOutboundNeighbors(node, neighbor) {
|
|
3301
|
+
node = "" + node;
|
|
3302
|
+
neighbor = "" + neighbor;
|
|
3303
|
+
const nodeData = this._nodes.get(node);
|
|
3304
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.areOutboundNeighbors: could not find the "${node}" node in the graph.`);
|
|
3305
|
+
if (this.type !== "undirected") {
|
|
3306
|
+
if (neighbor in nodeData.out) return true;
|
|
3307
|
+
}
|
|
3308
|
+
if (this.type !== "directed") {
|
|
3309
|
+
if (neighbor in nodeData.undirected) return true;
|
|
3310
|
+
}
|
|
3311
|
+
return false;
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Method returning the given node's in degree.
|
|
3315
|
+
*
|
|
3316
|
+
* @param {any} node - The node's key.
|
|
3317
|
+
* @return {number} - The node's in degree.
|
|
3318
|
+
*
|
|
3319
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3320
|
+
*/
|
|
3321
|
+
inDegree(node) {
|
|
3322
|
+
node = "" + node;
|
|
3323
|
+
const nodeData = this._nodes.get(node);
|
|
3324
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.inDegree: could not find the "${node}" node in the graph.`);
|
|
3325
|
+
if (this.type === "undirected") return 0;
|
|
3326
|
+
return nodeData.inDegree;
|
|
3327
|
+
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Method returning the given node's out degree.
|
|
3330
|
+
*
|
|
3331
|
+
* @param {any} node - The node's key.
|
|
3332
|
+
* @return {number} - The node's in degree.
|
|
3333
|
+
*
|
|
3334
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3335
|
+
*/
|
|
3336
|
+
outDegree(node) {
|
|
3337
|
+
node = "" + node;
|
|
3338
|
+
const nodeData = this._nodes.get(node);
|
|
3339
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.outDegree: could not find the "${node}" node in the graph.`);
|
|
3340
|
+
if (this.type === "undirected") return 0;
|
|
3341
|
+
return nodeData.outDegree;
|
|
3342
|
+
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Method returning the given node's directed degree.
|
|
3345
|
+
*
|
|
3346
|
+
* @param {any} node - The node's key.
|
|
3347
|
+
* @return {number} - The node's in degree.
|
|
3348
|
+
*
|
|
3349
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3350
|
+
*/
|
|
3351
|
+
directedDegree(node) {
|
|
3352
|
+
node = "" + node;
|
|
3353
|
+
const nodeData = this._nodes.get(node);
|
|
3354
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.directedDegree: could not find the "${node}" node in the graph.`);
|
|
3355
|
+
if (this.type === "undirected") return 0;
|
|
3356
|
+
return nodeData.inDegree + nodeData.outDegree;
|
|
3357
|
+
}
|
|
3358
|
+
/**
|
|
3359
|
+
* Method returning the given node's undirected degree.
|
|
3360
|
+
*
|
|
3361
|
+
* @param {any} node - The node's key.
|
|
3362
|
+
* @return {number} - The node's in degree.
|
|
3363
|
+
*
|
|
3364
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3365
|
+
*/
|
|
3366
|
+
undirectedDegree(node) {
|
|
3367
|
+
node = "" + node;
|
|
3368
|
+
const nodeData = this._nodes.get(node);
|
|
3369
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.undirectedDegree: could not find the "${node}" node in the graph.`);
|
|
3370
|
+
if (this.type === "directed") return 0;
|
|
3371
|
+
return nodeData.undirectedDegree;
|
|
3372
|
+
}
|
|
3373
|
+
/**
|
|
3374
|
+
* Method returning the given node's inbound degree.
|
|
3375
|
+
*
|
|
3376
|
+
* @param {any} node - The node's key.
|
|
3377
|
+
* @return {number} - The node's inbound degree.
|
|
3378
|
+
*
|
|
3379
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3380
|
+
*/
|
|
3381
|
+
inboundDegree(node) {
|
|
3382
|
+
node = "" + node;
|
|
3383
|
+
const nodeData = this._nodes.get(node);
|
|
3384
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.inboundDegree: could not find the "${node}" node in the graph.`);
|
|
3385
|
+
let degree = 0;
|
|
3386
|
+
if (this.type !== "directed") degree += nodeData.undirectedDegree;
|
|
3387
|
+
if (this.type !== "undirected") degree += nodeData.inDegree;
|
|
3388
|
+
return degree;
|
|
3389
|
+
}
|
|
3390
|
+
/**
|
|
3391
|
+
* Method returning the given node's outbound degree.
|
|
3392
|
+
*
|
|
3393
|
+
* @param {any} node - The node's key.
|
|
3394
|
+
* @return {number} - The node's outbound degree.
|
|
3395
|
+
*
|
|
3396
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3397
|
+
*/
|
|
3398
|
+
outboundDegree(node) {
|
|
3399
|
+
node = "" + node;
|
|
3400
|
+
const nodeData = this._nodes.get(node);
|
|
3401
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.outboundDegree: could not find the "${node}" node in the graph.`);
|
|
3402
|
+
let degree = 0;
|
|
3403
|
+
if (this.type !== "directed") degree += nodeData.undirectedDegree;
|
|
3404
|
+
if (this.type !== "undirected") degree += nodeData.outDegree;
|
|
3405
|
+
return degree;
|
|
3406
|
+
}
|
|
3407
|
+
/**
|
|
3408
|
+
* Method returning the given node's directed degree.
|
|
3409
|
+
*
|
|
3410
|
+
* @param {any} node - The node's key.
|
|
3411
|
+
* @return {number} - The node's degree.
|
|
3412
|
+
*
|
|
3413
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3414
|
+
*/
|
|
3415
|
+
degree(node) {
|
|
3416
|
+
node = "" + node;
|
|
3417
|
+
const nodeData = this._nodes.get(node);
|
|
3418
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.degree: could not find the "${node}" node in the graph.`);
|
|
3419
|
+
let degree = 0;
|
|
3420
|
+
if (this.type !== "directed") degree += nodeData.undirectedDegree;
|
|
3421
|
+
if (this.type !== "undirected") degree += nodeData.inDegree + nodeData.outDegree;
|
|
3422
|
+
return degree;
|
|
3423
|
+
}
|
|
3424
|
+
/**
|
|
3425
|
+
* Method returning the given node's in degree without considering self loops.
|
|
3426
|
+
*
|
|
3427
|
+
* @param {any} node - The node's key.
|
|
3428
|
+
* @return {number} - The node's in degree.
|
|
3429
|
+
*
|
|
3430
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3431
|
+
*/
|
|
3432
|
+
inDegreeWithoutSelfLoops(node) {
|
|
3433
|
+
node = "" + node;
|
|
3434
|
+
const nodeData = this._nodes.get(node);
|
|
3435
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.inDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3436
|
+
if (this.type === "undirected") return 0;
|
|
3437
|
+
return nodeData.inDegree - nodeData.directedLoops;
|
|
3438
|
+
}
|
|
3439
|
+
/**
|
|
3440
|
+
* Method returning the given node's out degree without considering self loops.
|
|
3441
|
+
*
|
|
3442
|
+
* @param {any} node - The node's key.
|
|
3443
|
+
* @return {number} - The node's in degree.
|
|
3444
|
+
*
|
|
3445
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3446
|
+
*/
|
|
3447
|
+
outDegreeWithoutSelfLoops(node) {
|
|
3448
|
+
node = "" + node;
|
|
3449
|
+
const nodeData = this._nodes.get(node);
|
|
3450
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.outDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3451
|
+
if (this.type === "undirected") return 0;
|
|
3452
|
+
return nodeData.outDegree - nodeData.directedLoops;
|
|
3453
|
+
}
|
|
3454
|
+
/**
|
|
3455
|
+
* Method returning the given node's directed degree without considering self loops.
|
|
3456
|
+
*
|
|
3457
|
+
* @param {any} node - The node's key.
|
|
3458
|
+
* @return {number} - The node's in degree.
|
|
3459
|
+
*
|
|
3460
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3461
|
+
*/
|
|
3462
|
+
directedDegreeWithoutSelfLoops(node) {
|
|
3463
|
+
node = "" + node;
|
|
3464
|
+
const nodeData = this._nodes.get(node);
|
|
3465
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.directedDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3466
|
+
if (this.type === "undirected") return 0;
|
|
3467
|
+
return nodeData.inDegree + nodeData.outDegree - nodeData.directedLoops * 2;
|
|
3468
|
+
}
|
|
3469
|
+
/**
|
|
3470
|
+
* Method returning the given node's undirected degree without considering self loops.
|
|
3471
|
+
*
|
|
3472
|
+
* @param {any} node - The node's key.
|
|
3473
|
+
* @return {number} - The node's in degree.
|
|
3474
|
+
*
|
|
3475
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3476
|
+
*/
|
|
3477
|
+
undirectedDegreeWithoutSelfLoops(node) {
|
|
3478
|
+
node = "" + node;
|
|
3479
|
+
const nodeData = this._nodes.get(node);
|
|
3480
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.undirectedDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3481
|
+
if (this.type === "directed") return 0;
|
|
3482
|
+
return nodeData.undirectedDegree - nodeData.undirectedLoops * 2;
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Method returning the given node's inbound degree without considering self loops.
|
|
3486
|
+
*
|
|
3487
|
+
* @param {any} node - The node's key.
|
|
3488
|
+
* @return {number} - The node's inbound degree.
|
|
3489
|
+
*
|
|
3490
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3491
|
+
*/
|
|
3492
|
+
inboundDegreeWithoutSelfLoops(node) {
|
|
3493
|
+
node = "" + node;
|
|
3494
|
+
const nodeData = this._nodes.get(node);
|
|
3495
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.inboundDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3496
|
+
let degree = 0;
|
|
3497
|
+
let loops = 0;
|
|
3498
|
+
if (this.type !== "directed") {
|
|
3499
|
+
degree += nodeData.undirectedDegree;
|
|
3500
|
+
loops += nodeData.undirectedLoops * 2;
|
|
3501
|
+
}
|
|
3502
|
+
if (this.type !== "undirected") {
|
|
3503
|
+
degree += nodeData.inDegree;
|
|
3504
|
+
loops += nodeData.directedLoops;
|
|
3505
|
+
}
|
|
3506
|
+
return degree - loops;
|
|
3507
|
+
}
|
|
3508
|
+
/**
|
|
3509
|
+
* Method returning the given node's outbound degree without considering self loops.
|
|
3510
|
+
*
|
|
3511
|
+
* @param {any} node - The node's key.
|
|
3512
|
+
* @return {number} - The node's outbound degree.
|
|
3513
|
+
*
|
|
3514
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3515
|
+
*/
|
|
3516
|
+
outboundDegreeWithoutSelfLoops(node) {
|
|
3517
|
+
node = "" + node;
|
|
3518
|
+
const nodeData = this._nodes.get(node);
|
|
3519
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.outboundDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3520
|
+
let degree = 0;
|
|
3521
|
+
let loops = 0;
|
|
3522
|
+
if (this.type !== "directed") {
|
|
3523
|
+
degree += nodeData.undirectedDegree;
|
|
3524
|
+
loops += nodeData.undirectedLoops * 2;
|
|
3525
|
+
}
|
|
3526
|
+
if (this.type !== "undirected") {
|
|
3527
|
+
degree += nodeData.outDegree;
|
|
3528
|
+
loops += nodeData.directedLoops;
|
|
3529
|
+
}
|
|
3530
|
+
return degree - loops;
|
|
3531
|
+
}
|
|
3532
|
+
/**
|
|
3533
|
+
* Method returning the given node's directed degree without considering self loops.
|
|
3534
|
+
*
|
|
3535
|
+
* @param {any} node - The node's key.
|
|
3536
|
+
* @return {number} - The node's degree.
|
|
3537
|
+
*
|
|
3538
|
+
* @throws {Error} - Will throw if the node isn't in the graph.
|
|
3539
|
+
*/
|
|
3540
|
+
degreeWithoutSelfLoops(node) {
|
|
3541
|
+
node = "" + node;
|
|
3542
|
+
const nodeData = this._nodes.get(node);
|
|
3543
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.degreeWithoutSelfLoops: could not find the "${node}" node in the graph.`);
|
|
3544
|
+
let degree = 0;
|
|
3545
|
+
let loops = 0;
|
|
3546
|
+
if (this.type !== "directed") {
|
|
3547
|
+
degree += nodeData.undirectedDegree;
|
|
3548
|
+
loops += nodeData.undirectedLoops * 2;
|
|
3549
|
+
}
|
|
3550
|
+
if (this.type !== "undirected") {
|
|
3551
|
+
degree += nodeData.inDegree + nodeData.outDegree;
|
|
3552
|
+
loops += nodeData.directedLoops * 2;
|
|
3553
|
+
}
|
|
3554
|
+
return degree - loops;
|
|
3555
|
+
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Method returning the given edge's source.
|
|
3558
|
+
*
|
|
3559
|
+
* @param {any} edge - The edge's key.
|
|
3560
|
+
* @return {any} - The edge's source.
|
|
3561
|
+
*
|
|
3562
|
+
* @throws {Error} - Will throw if the edge isn't in the graph.
|
|
3563
|
+
*/
|
|
3564
|
+
source(edge) {
|
|
3565
|
+
edge = "" + edge;
|
|
3566
|
+
const data = this._edges.get(edge);
|
|
3567
|
+
if (!data) throw new NotFoundGraphError(`Graph.source: could not find the "${edge}" edge in the graph.`);
|
|
3568
|
+
return data.source.key;
|
|
3569
|
+
}
|
|
3570
|
+
/**
|
|
3571
|
+
* Method returning the given edge's target.
|
|
3572
|
+
*
|
|
3573
|
+
* @param {any} edge - The edge's key.
|
|
3574
|
+
* @return {any} - The edge's target.
|
|
3575
|
+
*
|
|
3576
|
+
* @throws {Error} - Will throw if the edge isn't in the graph.
|
|
3577
|
+
*/
|
|
3578
|
+
target(edge) {
|
|
3579
|
+
edge = "" + edge;
|
|
3580
|
+
const data = this._edges.get(edge);
|
|
3581
|
+
if (!data) throw new NotFoundGraphError(`Graph.target: could not find the "${edge}" edge in the graph.`);
|
|
3582
|
+
return data.target.key;
|
|
3583
|
+
}
|
|
3584
|
+
/**
|
|
3585
|
+
* Method returning the given edge's extremities.
|
|
3586
|
+
*
|
|
3587
|
+
* @param {any} edge - The edge's key.
|
|
3588
|
+
* @return {array} - The edge's extremities.
|
|
3589
|
+
*
|
|
3590
|
+
* @throws {Error} - Will throw if the edge isn't in the graph.
|
|
3591
|
+
*/
|
|
3592
|
+
extremities(edge) {
|
|
3593
|
+
edge = "" + edge;
|
|
3594
|
+
const edgeData = this._edges.get(edge);
|
|
3595
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.extremities: could not find the "${edge}" edge in the graph.`);
|
|
3596
|
+
return [edgeData.source.key, edgeData.target.key];
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Given a node & an edge, returns the other extremity of the edge.
|
|
3600
|
+
*
|
|
3601
|
+
* @param {any} node - The node's key.
|
|
3602
|
+
* @param {any} edge - The edge's key.
|
|
3603
|
+
* @return {any} - The related node.
|
|
3604
|
+
*
|
|
3605
|
+
* @throws {Error} - Will throw if the edge isn't in the graph or if the
|
|
3606
|
+
* edge & node are not related.
|
|
3607
|
+
*/
|
|
3608
|
+
opposite(node, edge) {
|
|
3609
|
+
node = "" + node;
|
|
3610
|
+
edge = "" + edge;
|
|
3611
|
+
const data = this._edges.get(edge);
|
|
3612
|
+
if (!data) throw new NotFoundGraphError(`Graph.opposite: could not find the "${edge}" edge in the graph.`);
|
|
3613
|
+
const source = data.source.key;
|
|
3614
|
+
const target = data.target.key;
|
|
3615
|
+
if (node === source) return target;
|
|
3616
|
+
if (node === target) return source;
|
|
3617
|
+
throw new NotFoundGraphError(`Graph.opposite: the "${node}" node is not attached to the "${edge}" edge (${source}, ${target}).`);
|
|
3618
|
+
}
|
|
3619
|
+
/**
|
|
3620
|
+
* Returns whether the given edge has the given node as extremity.
|
|
3621
|
+
*
|
|
3622
|
+
* @param {any} edge - The edge's key.
|
|
3623
|
+
* @param {any} node - The node's key.
|
|
3624
|
+
* @return {boolean} - The related node.
|
|
3625
|
+
*
|
|
3626
|
+
* @throws {Error} - Will throw if either the node or the edge isn't in the graph.
|
|
3627
|
+
*/
|
|
3628
|
+
hasExtremity(edge, node) {
|
|
3629
|
+
edge = "" + edge;
|
|
3630
|
+
node = "" + node;
|
|
3631
|
+
const data = this._edges.get(edge);
|
|
3632
|
+
if (!data) throw new NotFoundGraphError(`Graph.hasExtremity: could not find the "${edge}" edge in the graph.`);
|
|
3633
|
+
return data.source.key === node || data.target.key === node;
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* Method returning whether the given edge is undirected.
|
|
3637
|
+
*
|
|
3638
|
+
* @param {any} edge - The edge's key.
|
|
3639
|
+
* @return {boolean}
|
|
3640
|
+
*
|
|
3641
|
+
* @throws {Error} - Will throw if the edge isn't in the graph.
|
|
3642
|
+
*/
|
|
3643
|
+
isUndirected(edge) {
|
|
3644
|
+
edge = "" + edge;
|
|
3645
|
+
const data = this._edges.get(edge);
|
|
3646
|
+
if (!data) throw new NotFoundGraphError(`Graph.isUndirected: could not find the "${edge}" edge in the graph.`);
|
|
3647
|
+
return data.undirected;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Method returning whether the given edge is directed.
|
|
3651
|
+
*
|
|
3652
|
+
* @param {any} edge - The edge's key.
|
|
3653
|
+
* @return {boolean}
|
|
3654
|
+
*
|
|
3655
|
+
* @throws {Error} - Will throw if the edge isn't in the graph.
|
|
3656
|
+
*/
|
|
3657
|
+
isDirected(edge) {
|
|
3658
|
+
edge = "" + edge;
|
|
3659
|
+
const data = this._edges.get(edge);
|
|
3660
|
+
if (!data) throw new NotFoundGraphError(`Graph.isDirected: could not find the "${edge}" edge in the graph.`);
|
|
3661
|
+
return !data.undirected;
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* Method returning whether the given edge is a self loop.
|
|
3665
|
+
*
|
|
3666
|
+
* @param {any} edge - The edge's key.
|
|
3667
|
+
* @return {boolean}
|
|
3668
|
+
*
|
|
3669
|
+
* @throws {Error} - Will throw if the edge isn't in the graph.
|
|
3670
|
+
*/
|
|
3671
|
+
isSelfLoop(edge) {
|
|
3672
|
+
edge = "" + edge;
|
|
3673
|
+
const data = this._edges.get(edge);
|
|
3674
|
+
if (!data) throw new NotFoundGraphError(`Graph.isSelfLoop: could not find the "${edge}" edge in the graph.`);
|
|
3675
|
+
return data.source === data.target;
|
|
3676
|
+
}
|
|
3677
|
+
/**---------------------------------------------------------------------------
|
|
3678
|
+
* Mutation
|
|
3679
|
+
**---------------------------------------------------------------------------
|
|
3680
|
+
*/
|
|
3681
|
+
/**
|
|
3682
|
+
* Method used to add a node to the graph.
|
|
3683
|
+
*
|
|
3684
|
+
* @param {any} node - The node.
|
|
3685
|
+
* @param {object} [attributes] - Optional attributes.
|
|
3686
|
+
* @return {any} - The node.
|
|
3687
|
+
*
|
|
3688
|
+
* @throws {Error} - Will throw if the given node already exist.
|
|
3689
|
+
* @throws {Error} - Will throw if the given attributes are not an object.
|
|
3690
|
+
*/
|
|
3691
|
+
addNode(node, attributes) {
|
|
3692
|
+
return addNode(this, node, attributes).key;
|
|
3693
|
+
}
|
|
3694
|
+
/**
|
|
3695
|
+
* Method used to merge a node into the graph.
|
|
3696
|
+
*
|
|
3697
|
+
* @param {any} node - The node.
|
|
3698
|
+
* @param {object} [attributes] - Optional attributes.
|
|
3699
|
+
* @return {any} - The node.
|
|
3700
|
+
*/
|
|
3701
|
+
mergeNode(node, attributes) {
|
|
3702
|
+
if (attributes && !isPlainObject(attributes)) throw new InvalidArgumentsGraphError(`Graph.mergeNode: invalid attributes. Expecting an object but got "${attributes}"`);
|
|
3703
|
+
node = "" + node;
|
|
3704
|
+
attributes = attributes || {};
|
|
3705
|
+
let data = this._nodes.get(node);
|
|
3706
|
+
if (data) {
|
|
3707
|
+
if (attributes) {
|
|
3708
|
+
assign(data.attributes, attributes);
|
|
3709
|
+
this.emit("nodeAttributesUpdated", {
|
|
3710
|
+
type: "merge",
|
|
3711
|
+
key: node,
|
|
3712
|
+
attributes: data.attributes,
|
|
3713
|
+
data: attributes
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
return [node, false];
|
|
3717
|
+
}
|
|
3718
|
+
data = new this.NodeDataClass(node, attributes);
|
|
3719
|
+
this._nodes.set(node, data);
|
|
3720
|
+
this.emit("nodeAdded", {
|
|
3721
|
+
key: node,
|
|
3722
|
+
attributes
|
|
3723
|
+
});
|
|
3724
|
+
return [node, true];
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Method used to add a node if it does not exist in the graph or else to
|
|
3728
|
+
* update its attributes using a function.
|
|
3729
|
+
*
|
|
3730
|
+
* @param {any} node - The node.
|
|
3731
|
+
* @param {function} [updater] - Optional updater function.
|
|
3732
|
+
* @return {any} - The node.
|
|
3733
|
+
*/
|
|
3734
|
+
updateNode(node, updater) {
|
|
3735
|
+
if (updater && typeof updater !== "function") throw new InvalidArgumentsGraphError(`Graph.updateNode: invalid updater function. Expecting a function but got "${updater}"`);
|
|
3736
|
+
node = "" + node;
|
|
3737
|
+
let data = this._nodes.get(node);
|
|
3738
|
+
if (data) {
|
|
3739
|
+
if (updater) {
|
|
3740
|
+
const oldAttributes = data.attributes;
|
|
3741
|
+
data.attributes = updater(oldAttributes);
|
|
3742
|
+
this.emit("nodeAttributesUpdated", {
|
|
3743
|
+
type: "replace",
|
|
3744
|
+
key: node,
|
|
3745
|
+
attributes: data.attributes
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
return [node, false];
|
|
3749
|
+
}
|
|
3750
|
+
const attributes = updater ? updater({}) : {};
|
|
3751
|
+
data = new this.NodeDataClass(node, attributes);
|
|
3752
|
+
this._nodes.set(node, data);
|
|
3753
|
+
this.emit("nodeAdded", {
|
|
3754
|
+
key: node,
|
|
3755
|
+
attributes
|
|
3756
|
+
});
|
|
3757
|
+
return [node, true];
|
|
3758
|
+
}
|
|
3759
|
+
/**
|
|
3760
|
+
* Method used to drop a single node & all its attached edges from the graph.
|
|
3761
|
+
*
|
|
3762
|
+
* @param {any} node - The node.
|
|
3763
|
+
* @return {Graph}
|
|
3764
|
+
*
|
|
3765
|
+
* @throws {Error} - Will throw if the node doesn't exist.
|
|
3766
|
+
*/
|
|
3767
|
+
dropNode(node) {
|
|
3768
|
+
node = "" + node;
|
|
3769
|
+
const nodeData = this._nodes.get(node);
|
|
3770
|
+
if (!nodeData) throw new NotFoundGraphError(`Graph.dropNode: could not find the "${node}" node in the graph.`);
|
|
3771
|
+
let edgeData;
|
|
3772
|
+
if (this.type !== "undirected") {
|
|
3773
|
+
for (const neighbor in nodeData.out) {
|
|
3774
|
+
edgeData = nodeData.out[neighbor];
|
|
3775
|
+
do {
|
|
3776
|
+
dropEdgeFromData(this, edgeData);
|
|
3777
|
+
edgeData = edgeData.next;
|
|
3778
|
+
} while (edgeData);
|
|
3779
|
+
}
|
|
3780
|
+
for (const neighbor in nodeData.in) {
|
|
3781
|
+
edgeData = nodeData.in[neighbor];
|
|
3782
|
+
do {
|
|
3783
|
+
dropEdgeFromData(this, edgeData);
|
|
3784
|
+
edgeData = edgeData.next;
|
|
3785
|
+
} while (edgeData);
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
if (this.type !== "directed") for (const neighbor in nodeData.undirected) {
|
|
3789
|
+
edgeData = nodeData.undirected[neighbor];
|
|
3790
|
+
do {
|
|
3791
|
+
dropEdgeFromData(this, edgeData);
|
|
3792
|
+
edgeData = edgeData.next;
|
|
3793
|
+
} while (edgeData);
|
|
3794
|
+
}
|
|
3795
|
+
this._nodes.delete(node);
|
|
3796
|
+
this.emit("nodeDropped", {
|
|
3797
|
+
key: node,
|
|
3798
|
+
attributes: nodeData.attributes
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
/**
|
|
3802
|
+
* Method used to drop a single edge from the graph.
|
|
3803
|
+
*
|
|
3804
|
+
* Arity 1:
|
|
3805
|
+
* @param {any} edge - The edge.
|
|
3806
|
+
*
|
|
3807
|
+
* Arity 2:
|
|
3808
|
+
* @param {any} source - Source node.
|
|
3809
|
+
* @param {any} target - Target node.
|
|
3810
|
+
*
|
|
3811
|
+
* @return {Graph}
|
|
3812
|
+
*
|
|
3813
|
+
* @throws {Error} - Will throw if the edge doesn't exist.
|
|
3814
|
+
*/
|
|
3815
|
+
dropEdge(edge) {
|
|
3816
|
+
let edgeData;
|
|
3817
|
+
if (arguments.length > 1) {
|
|
3818
|
+
const source = "" + arguments[0];
|
|
3819
|
+
const target = "" + arguments[1];
|
|
3820
|
+
edgeData = getMatchingEdge(this, source, target, this.type);
|
|
3821
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.dropEdge: could not find the "${source}" -> "${target}" edge in the graph.`);
|
|
3822
|
+
} else {
|
|
3823
|
+
edge = "" + edge;
|
|
3824
|
+
edgeData = this._edges.get(edge);
|
|
3825
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.dropEdge: could not find the "${edge}" edge in the graph.`);
|
|
3826
|
+
}
|
|
3827
|
+
dropEdgeFromData(this, edgeData);
|
|
3828
|
+
return this;
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Method used to drop a single directed edge from the graph.
|
|
3832
|
+
*
|
|
3833
|
+
* @param {any} source - Source node.
|
|
3834
|
+
* @param {any} target - Target node.
|
|
3835
|
+
*
|
|
3836
|
+
* @return {Graph}
|
|
3837
|
+
*
|
|
3838
|
+
* @throws {Error} - Will throw if the edge doesn't exist.
|
|
3839
|
+
*/
|
|
3840
|
+
dropDirectedEdge(source, target) {
|
|
3841
|
+
if (arguments.length < 2) throw new UsageGraphError("Graph.dropDirectedEdge: it does not make sense to try and drop a directed edge by key. What if the edge with this key is undirected? Use #.dropEdge for this purpose instead.");
|
|
3842
|
+
if (this.multi) throw new UsageGraphError("Graph.dropDirectedEdge: cannot use a {source,target} combo when dropping an edge in a MultiGraph since we cannot infer the one you want to delete as there could be multiple ones.");
|
|
3843
|
+
source = "" + source;
|
|
3844
|
+
target = "" + target;
|
|
3845
|
+
const edgeData = getMatchingEdge(this, source, target, "directed");
|
|
3846
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.dropDirectedEdge: could not find a "${source}" -> "${target}" edge in the graph.`);
|
|
3847
|
+
dropEdgeFromData(this, edgeData);
|
|
3848
|
+
return this;
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* Method used to drop a single undirected edge from the graph.
|
|
3852
|
+
*
|
|
3853
|
+
* @param {any} source - Source node.
|
|
3854
|
+
* @param {any} target - Target node.
|
|
3855
|
+
*
|
|
3856
|
+
* @return {Graph}
|
|
3857
|
+
*
|
|
3858
|
+
* @throws {Error} - Will throw if the edge doesn't exist.
|
|
3859
|
+
*/
|
|
3860
|
+
dropUndirectedEdge(source, target) {
|
|
3861
|
+
if (arguments.length < 2) throw new UsageGraphError("Graph.dropUndirectedEdge: it does not make sense to drop a directed edge by key. What if the edge with this key is undirected? Use #.dropEdge for this purpose instead.");
|
|
3862
|
+
if (this.multi) throw new UsageGraphError("Graph.dropUndirectedEdge: cannot use a {source,target} combo when dropping an edge in a MultiGraph since we cannot infer the one you want to delete as there could be multiple ones.");
|
|
3863
|
+
const edgeData = getMatchingEdge(this, source, target, "undirected");
|
|
3864
|
+
if (!edgeData) throw new NotFoundGraphError(`Graph.dropUndirectedEdge: could not find a "${source}" -> "${target}" edge in the graph.`);
|
|
3865
|
+
dropEdgeFromData(this, edgeData);
|
|
3866
|
+
return this;
|
|
3867
|
+
}
|
|
3868
|
+
/**
|
|
3869
|
+
* Method used to remove every edge & every node from the graph.
|
|
3870
|
+
*
|
|
3871
|
+
* @return {Graph}
|
|
3872
|
+
*/
|
|
3873
|
+
clear() {
|
|
3874
|
+
this._edges.clear();
|
|
3875
|
+
this._nodes.clear();
|
|
3876
|
+
this._resetInstanceCounters();
|
|
3877
|
+
this.emit("cleared");
|
|
3878
|
+
}
|
|
3879
|
+
/**
|
|
3880
|
+
* Method used to remove every edge from the graph.
|
|
3881
|
+
*
|
|
3882
|
+
* @return {Graph}
|
|
3883
|
+
*/
|
|
3884
|
+
clearEdges() {
|
|
3885
|
+
const iterator = this._nodes.values();
|
|
3886
|
+
let step;
|
|
3887
|
+
while (step = iterator.next(), step.done !== true) step.value.clear();
|
|
3888
|
+
this._edges.clear();
|
|
3889
|
+
this._resetInstanceCounters();
|
|
3890
|
+
this.emit("edgesCleared");
|
|
3891
|
+
}
|
|
3892
|
+
/**---------------------------------------------------------------------------
|
|
3893
|
+
* Attributes-related methods
|
|
3894
|
+
**---------------------------------------------------------------------------
|
|
3895
|
+
*/
|
|
3896
|
+
/**
|
|
3897
|
+
* Method returning the desired graph's attribute.
|
|
3898
|
+
*
|
|
3899
|
+
* @param {string} name - Name of the attribute.
|
|
3900
|
+
* @return {any}
|
|
3901
|
+
*/
|
|
3902
|
+
getAttribute(name) {
|
|
3903
|
+
return this._attributes[name];
|
|
3904
|
+
}
|
|
3905
|
+
/**
|
|
3906
|
+
* Method returning the graph's attributes.
|
|
3907
|
+
*
|
|
3908
|
+
* @return {object}
|
|
3909
|
+
*/
|
|
3910
|
+
getAttributes() {
|
|
3911
|
+
return this._attributes;
|
|
3912
|
+
}
|
|
3913
|
+
/**
|
|
3914
|
+
* Method returning whether the graph has the desired attribute.
|
|
3915
|
+
*
|
|
3916
|
+
* @param {string} name - Name of the attribute.
|
|
3917
|
+
* @return {boolean}
|
|
3918
|
+
*/
|
|
3919
|
+
hasAttribute(name) {
|
|
3920
|
+
return this._attributes.hasOwnProperty(name);
|
|
3921
|
+
}
|
|
3922
|
+
/**
|
|
3923
|
+
* Method setting a value for the desired graph's attribute.
|
|
3924
|
+
*
|
|
3925
|
+
* @param {string} name - Name of the attribute.
|
|
3926
|
+
* @param {any} value - Value for the attribute.
|
|
3927
|
+
* @return {Graph}
|
|
3928
|
+
*/
|
|
3929
|
+
setAttribute(name, value) {
|
|
3930
|
+
this._attributes[name] = value;
|
|
3931
|
+
this.emit("attributesUpdated", {
|
|
3932
|
+
type: "set",
|
|
3933
|
+
attributes: this._attributes,
|
|
3934
|
+
name
|
|
3935
|
+
});
|
|
3936
|
+
return this;
|
|
3937
|
+
}
|
|
3938
|
+
/**
|
|
3939
|
+
* Method using a function to update the desired graph's attribute's value.
|
|
3940
|
+
*
|
|
3941
|
+
* @param {string} name - Name of the attribute.
|
|
3942
|
+
* @param {function} updater - Function use to update the attribute's value.
|
|
3943
|
+
* @return {Graph}
|
|
3944
|
+
*/
|
|
3945
|
+
updateAttribute(name, updater) {
|
|
3946
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError("Graph.updateAttribute: updater should be a function.");
|
|
3947
|
+
const value = this._attributes[name];
|
|
3948
|
+
this._attributes[name] = updater(value);
|
|
3949
|
+
this.emit("attributesUpdated", {
|
|
3950
|
+
type: "set",
|
|
3951
|
+
attributes: this._attributes,
|
|
3952
|
+
name
|
|
3953
|
+
});
|
|
3954
|
+
return this;
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Method removing the desired graph's attribute.
|
|
3958
|
+
*
|
|
3959
|
+
* @param {string} name - Name of the attribute.
|
|
3960
|
+
* @return {Graph}
|
|
3961
|
+
*/
|
|
3962
|
+
removeAttribute(name) {
|
|
3963
|
+
delete this._attributes[name];
|
|
3964
|
+
this.emit("attributesUpdated", {
|
|
3965
|
+
type: "remove",
|
|
3966
|
+
attributes: this._attributes,
|
|
3967
|
+
name
|
|
3968
|
+
});
|
|
3969
|
+
return this;
|
|
3970
|
+
}
|
|
3971
|
+
/**
|
|
3972
|
+
* Method replacing the graph's attributes.
|
|
3973
|
+
*
|
|
3974
|
+
* @param {object} attributes - New attributes.
|
|
3975
|
+
* @return {Graph}
|
|
3976
|
+
*
|
|
3977
|
+
* @throws {Error} - Will throw if given attributes are not a plain object.
|
|
3978
|
+
*/
|
|
3979
|
+
replaceAttributes(attributes) {
|
|
3980
|
+
if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.replaceAttributes: provided attributes are not a plain object.");
|
|
3981
|
+
this._attributes = attributes;
|
|
3982
|
+
this.emit("attributesUpdated", {
|
|
3983
|
+
type: "replace",
|
|
3984
|
+
attributes: this._attributes
|
|
3985
|
+
});
|
|
3986
|
+
return this;
|
|
3987
|
+
}
|
|
3988
|
+
/**
|
|
3989
|
+
* Method merging the graph's attributes.
|
|
3990
|
+
*
|
|
3991
|
+
* @param {object} attributes - Attributes to merge.
|
|
3992
|
+
* @return {Graph}
|
|
3993
|
+
*
|
|
3994
|
+
* @throws {Error} - Will throw if given attributes are not a plain object.
|
|
3995
|
+
*/
|
|
3996
|
+
mergeAttributes(attributes) {
|
|
3997
|
+
if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.mergeAttributes: provided attributes are not a plain object.");
|
|
3998
|
+
assign(this._attributes, attributes);
|
|
3999
|
+
this.emit("attributesUpdated", {
|
|
4000
|
+
type: "merge",
|
|
4001
|
+
attributes: this._attributes,
|
|
4002
|
+
data: attributes
|
|
4003
|
+
});
|
|
4004
|
+
return this;
|
|
4005
|
+
}
|
|
4006
|
+
/**
|
|
4007
|
+
* Method updating the graph's attributes.
|
|
4008
|
+
*
|
|
4009
|
+
* @param {function} updater - Function used to update the attributes.
|
|
4010
|
+
* @return {Graph}
|
|
4011
|
+
*
|
|
4012
|
+
* @throws {Error} - Will throw if given updater is not a function.
|
|
4013
|
+
*/
|
|
4014
|
+
updateAttributes(updater) {
|
|
4015
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError("Graph.updateAttributes: provided updater is not a function.");
|
|
4016
|
+
this._attributes = updater(this._attributes);
|
|
4017
|
+
this.emit("attributesUpdated", {
|
|
4018
|
+
type: "update",
|
|
4019
|
+
attributes: this._attributes
|
|
4020
|
+
});
|
|
4021
|
+
return this;
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
4024
|
+
* Method used to update each node's attributes using the given function.
|
|
4025
|
+
*
|
|
4026
|
+
* @param {function} updater - Updater function to use.
|
|
4027
|
+
* @param {object} [hints] - Optional hints.
|
|
4028
|
+
*/
|
|
4029
|
+
updateEachNodeAttributes(updater, hints) {
|
|
4030
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError("Graph.updateEachNodeAttributes: expecting an updater function.");
|
|
4031
|
+
if (hints && !validateHints(hints)) throw new InvalidArgumentsGraphError("Graph.updateEachNodeAttributes: invalid hints. Expecting an object having the following shape: {attributes?: [string]}");
|
|
4032
|
+
const iterator = this._nodes.values();
|
|
4033
|
+
let step, nodeData;
|
|
4034
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4035
|
+
nodeData = step.value;
|
|
4036
|
+
nodeData.attributes = updater(nodeData.key, nodeData.attributes);
|
|
4037
|
+
}
|
|
4038
|
+
this.emit("eachNodeAttributesUpdated", { hints: hints ? hints : null });
|
|
4039
|
+
}
|
|
4040
|
+
/**
|
|
4041
|
+
* Method used to update each edge's attributes using the given function.
|
|
4042
|
+
*
|
|
4043
|
+
* @param {function} updater - Updater function to use.
|
|
4044
|
+
* @param {object} [hints] - Optional hints.
|
|
4045
|
+
*/
|
|
4046
|
+
updateEachEdgeAttributes(updater, hints) {
|
|
4047
|
+
if (typeof updater !== "function") throw new InvalidArgumentsGraphError("Graph.updateEachEdgeAttributes: expecting an updater function.");
|
|
4048
|
+
if (hints && !validateHints(hints)) throw new InvalidArgumentsGraphError("Graph.updateEachEdgeAttributes: invalid hints. Expecting an object having the following shape: {attributes?: [string]}");
|
|
4049
|
+
const iterator = this._edges.values();
|
|
4050
|
+
let step, edgeData, sourceData, targetData;
|
|
4051
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4052
|
+
edgeData = step.value;
|
|
4053
|
+
sourceData = edgeData.source;
|
|
4054
|
+
targetData = edgeData.target;
|
|
4055
|
+
edgeData.attributes = updater(edgeData.key, edgeData.attributes, sourceData.key, targetData.key, sourceData.attributes, targetData.attributes, edgeData.undirected);
|
|
4056
|
+
}
|
|
4057
|
+
this.emit("eachEdgeAttributesUpdated", { hints: hints ? hints : null });
|
|
4058
|
+
}
|
|
4059
|
+
/**---------------------------------------------------------------------------
|
|
4060
|
+
* Iteration-related methods
|
|
4061
|
+
**---------------------------------------------------------------------------
|
|
4062
|
+
*/
|
|
4063
|
+
/**
|
|
4064
|
+
* Method iterating over the graph's adjacency using the given callback.
|
|
4065
|
+
*
|
|
4066
|
+
* @param {function} callback - Callback to use.
|
|
4067
|
+
*/
|
|
4068
|
+
forEachAdjacencyEntry(callback) {
|
|
4069
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.forEachAdjacencyEntry: expecting a callback.");
|
|
4070
|
+
forEachAdjacency(false, false, false, this, callback);
|
|
4071
|
+
}
|
|
4072
|
+
forEachAdjacencyEntryWithOrphans(callback) {
|
|
4073
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.forEachAdjacencyEntryWithOrphans: expecting a callback.");
|
|
4074
|
+
forEachAdjacency(false, false, true, this, callback);
|
|
4075
|
+
}
|
|
4076
|
+
/**
|
|
4077
|
+
* Method iterating over the graph's assymetric adjacency using the given callback.
|
|
4078
|
+
*
|
|
4079
|
+
* @param {function} callback - Callback to use.
|
|
4080
|
+
*/
|
|
4081
|
+
forEachAssymetricAdjacencyEntry(callback) {
|
|
4082
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.forEachAssymetricAdjacencyEntry: expecting a callback.");
|
|
4083
|
+
forEachAdjacency(false, true, false, this, callback);
|
|
4084
|
+
}
|
|
4085
|
+
forEachAssymetricAdjacencyEntryWithOrphans(callback) {
|
|
4086
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.forEachAssymetricAdjacencyEntryWithOrphans: expecting a callback.");
|
|
4087
|
+
forEachAdjacency(false, true, true, this, callback);
|
|
4088
|
+
}
|
|
4089
|
+
/**
|
|
4090
|
+
* Method returning the list of the graph's nodes.
|
|
4091
|
+
*
|
|
4092
|
+
* @return {array} - The nodes.
|
|
4093
|
+
*/
|
|
4094
|
+
nodes() {
|
|
4095
|
+
return Array.from(this._nodes.keys());
|
|
4096
|
+
}
|
|
4097
|
+
/**
|
|
4098
|
+
* Method iterating over the graph's nodes using the given callback.
|
|
4099
|
+
*
|
|
4100
|
+
* @param {function} callback - Callback (key, attributes, index).
|
|
4101
|
+
*/
|
|
4102
|
+
forEachNode(callback) {
|
|
4103
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.forEachNode: expecting a callback.");
|
|
4104
|
+
const iterator = this._nodes.values();
|
|
4105
|
+
let step, nodeData;
|
|
4106
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4107
|
+
nodeData = step.value;
|
|
4108
|
+
callback(nodeData.key, nodeData.attributes);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
/**
|
|
4112
|
+
* Method iterating attempting to find a node matching the given predicate
|
|
4113
|
+
* function.
|
|
4114
|
+
*
|
|
4115
|
+
* @param {function} callback - Callback (key, attributes).
|
|
4116
|
+
*/
|
|
4117
|
+
findNode(callback) {
|
|
4118
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.findNode: expecting a callback.");
|
|
4119
|
+
const iterator = this._nodes.values();
|
|
4120
|
+
let step, nodeData;
|
|
4121
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4122
|
+
nodeData = step.value;
|
|
4123
|
+
if (callback(nodeData.key, nodeData.attributes)) return nodeData.key;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
/**
|
|
4127
|
+
* Method mapping nodes.
|
|
4128
|
+
*
|
|
4129
|
+
* @param {function} callback - Callback (key, attributes).
|
|
4130
|
+
*/
|
|
4131
|
+
mapNodes(callback) {
|
|
4132
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.mapNode: expecting a callback.");
|
|
4133
|
+
const iterator = this._nodes.values();
|
|
4134
|
+
let step, nodeData;
|
|
4135
|
+
const result = new Array(this.order);
|
|
4136
|
+
let i = 0;
|
|
4137
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4138
|
+
nodeData = step.value;
|
|
4139
|
+
result[i++] = callback(nodeData.key, nodeData.attributes);
|
|
4140
|
+
}
|
|
4141
|
+
return result;
|
|
4142
|
+
}
|
|
4143
|
+
/**
|
|
4144
|
+
* Method returning whether some node verify the given predicate.
|
|
4145
|
+
*
|
|
4146
|
+
* @param {function} callback - Callback (key, attributes).
|
|
4147
|
+
*/
|
|
4148
|
+
someNode(callback) {
|
|
4149
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.someNode: expecting a callback.");
|
|
4150
|
+
const iterator = this._nodes.values();
|
|
4151
|
+
let step, nodeData;
|
|
4152
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4153
|
+
nodeData = step.value;
|
|
4154
|
+
if (callback(nodeData.key, nodeData.attributes)) return true;
|
|
4155
|
+
}
|
|
4156
|
+
return false;
|
|
4157
|
+
}
|
|
4158
|
+
/**
|
|
4159
|
+
* Method returning whether all node verify the given predicate.
|
|
4160
|
+
*
|
|
4161
|
+
* @param {function} callback - Callback (key, attributes).
|
|
4162
|
+
*/
|
|
4163
|
+
everyNode(callback) {
|
|
4164
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.everyNode: expecting a callback.");
|
|
4165
|
+
const iterator = this._nodes.values();
|
|
4166
|
+
let step, nodeData;
|
|
4167
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4168
|
+
nodeData = step.value;
|
|
4169
|
+
if (!callback(nodeData.key, nodeData.attributes)) return false;
|
|
4170
|
+
}
|
|
4171
|
+
return true;
|
|
4172
|
+
}
|
|
4173
|
+
/**
|
|
4174
|
+
* Method filtering nodes.
|
|
4175
|
+
*
|
|
4176
|
+
* @param {function} callback - Callback (key, attributes).
|
|
4177
|
+
*/
|
|
4178
|
+
filterNodes(callback) {
|
|
4179
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.filterNodes: expecting a callback.");
|
|
4180
|
+
const iterator = this._nodes.values();
|
|
4181
|
+
let step, nodeData;
|
|
4182
|
+
const result = [];
|
|
4183
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4184
|
+
nodeData = step.value;
|
|
4185
|
+
if (callback(nodeData.key, nodeData.attributes)) result.push(nodeData.key);
|
|
4186
|
+
}
|
|
4187
|
+
return result;
|
|
4188
|
+
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Method reducing nodes.
|
|
4191
|
+
*
|
|
4192
|
+
* @param {function} callback - Callback (accumulator, key, attributes).
|
|
4193
|
+
*/
|
|
4194
|
+
reduceNodes(callback, initialValue) {
|
|
4195
|
+
if (typeof callback !== "function") throw new InvalidArgumentsGraphError("Graph.reduceNodes: expecting a callback.");
|
|
4196
|
+
if (arguments.length < 2) throw new InvalidArgumentsGraphError("Graph.reduceNodes: missing initial value. You must provide it because the callback takes more than one argument and we cannot infer the initial value from the first iteration, as you could with a simple array.");
|
|
4197
|
+
let accumulator = initialValue;
|
|
4198
|
+
const iterator = this._nodes.values();
|
|
4199
|
+
let step, nodeData;
|
|
4200
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4201
|
+
nodeData = step.value;
|
|
4202
|
+
accumulator = callback(accumulator, nodeData.key, nodeData.attributes);
|
|
4203
|
+
}
|
|
4204
|
+
return accumulator;
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Method returning an iterator over the graph's node entries.
|
|
4208
|
+
*
|
|
4209
|
+
* @return {Iterator}
|
|
4210
|
+
*/
|
|
4211
|
+
nodeEntries() {
|
|
4212
|
+
const iterator = this._nodes.values();
|
|
4213
|
+
return {
|
|
4214
|
+
[Symbol.iterator]() {
|
|
4215
|
+
return this;
|
|
4216
|
+
},
|
|
4217
|
+
next() {
|
|
4218
|
+
const step = iterator.next();
|
|
4219
|
+
if (step.done) return step;
|
|
4220
|
+
const data = step.value;
|
|
4221
|
+
return {
|
|
4222
|
+
value: {
|
|
4223
|
+
node: data.key,
|
|
4224
|
+
attributes: data.attributes
|
|
4225
|
+
},
|
|
4226
|
+
done: false
|
|
4227
|
+
};
|
|
4228
|
+
}
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
4231
|
+
/**---------------------------------------------------------------------------
|
|
4232
|
+
* Serialization
|
|
4233
|
+
**---------------------------------------------------------------------------
|
|
4234
|
+
*/
|
|
4235
|
+
/**
|
|
4236
|
+
* Method used to export the whole graph.
|
|
4237
|
+
*
|
|
4238
|
+
* @return {object} - The serialized graph.
|
|
4239
|
+
*/
|
|
4240
|
+
export() {
|
|
4241
|
+
const nodes = new Array(this._nodes.size);
|
|
4242
|
+
let i = 0;
|
|
4243
|
+
this._nodes.forEach((data, key) => {
|
|
4244
|
+
nodes[i++] = serializeNode(key, data);
|
|
4245
|
+
});
|
|
4246
|
+
const edges = new Array(this._edges.size);
|
|
4247
|
+
i = 0;
|
|
4248
|
+
this._edges.forEach((data, key) => {
|
|
4249
|
+
edges[i++] = serializeEdge(this.type, key, data);
|
|
4250
|
+
});
|
|
4251
|
+
return {
|
|
4252
|
+
options: {
|
|
4253
|
+
type: this.type,
|
|
4254
|
+
multi: this.multi,
|
|
4255
|
+
allowSelfLoops: this.allowSelfLoops
|
|
4256
|
+
},
|
|
4257
|
+
attributes: this.getAttributes(),
|
|
4258
|
+
nodes,
|
|
4259
|
+
edges
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
/**
|
|
4263
|
+
* Method used to import a serialized graph.
|
|
4264
|
+
*
|
|
4265
|
+
* @param {object|Graph} data - The serialized graph.
|
|
4266
|
+
* @param {boolean} merge - Whether to merge data.
|
|
4267
|
+
* @return {Graph} - Returns itself for chaining.
|
|
4268
|
+
*/
|
|
4269
|
+
import(data, merge = false) {
|
|
4270
|
+
if (data instanceof Graph) {
|
|
4271
|
+
data.forEachNode((n, a) => {
|
|
4272
|
+
if (merge) this.mergeNode(n, a);
|
|
4273
|
+
else this.addNode(n, a);
|
|
4274
|
+
});
|
|
4275
|
+
data.forEachEdge((e, a, s, t, _sa, _ta, u) => {
|
|
4276
|
+
if (merge) if (u) this.mergeUndirectedEdgeWithKey(e, s, t, a);
|
|
4277
|
+
else this.mergeDirectedEdgeWithKey(e, s, t, a);
|
|
4278
|
+
else if (u) this.addUndirectedEdgeWithKey(e, s, t, a);
|
|
4279
|
+
else this.addDirectedEdgeWithKey(e, s, t, a);
|
|
4280
|
+
});
|
|
4281
|
+
return this;
|
|
4282
|
+
}
|
|
4283
|
+
if (!isPlainObject(data)) throw new InvalidArgumentsGraphError("Graph.import: invalid argument. Expecting a serialized graph or, alternatively, a Graph instance.");
|
|
4284
|
+
if (data.attributes) {
|
|
4285
|
+
if (!isPlainObject(data.attributes)) throw new InvalidArgumentsGraphError("Graph.import: invalid attributes. Expecting a plain object.");
|
|
4286
|
+
if (merge) this.mergeAttributes(data.attributes);
|
|
4287
|
+
else this.replaceAttributes(data.attributes);
|
|
4288
|
+
}
|
|
4289
|
+
let i, l, list, node, edge;
|
|
4290
|
+
if (data.nodes) {
|
|
4291
|
+
list = data.nodes;
|
|
4292
|
+
if (!Array.isArray(list)) throw new InvalidArgumentsGraphError("Graph.import: invalid nodes. Expecting an array.");
|
|
4293
|
+
for (i = 0, l = list.length; i < l; i++) {
|
|
4294
|
+
node = list[i];
|
|
4295
|
+
validateSerializedNode(node);
|
|
4296
|
+
const { key, attributes } = node;
|
|
4297
|
+
if (merge) this.mergeNode(key, attributes);
|
|
4298
|
+
else this.addNode(key, attributes);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
if (data.edges) {
|
|
4302
|
+
let undirectedByDefault = false;
|
|
4303
|
+
if (this.type === "undirected") undirectedByDefault = true;
|
|
4304
|
+
list = data.edges;
|
|
4305
|
+
if (!Array.isArray(list)) throw new InvalidArgumentsGraphError("Graph.import: invalid edges. Expecting an array.");
|
|
4306
|
+
for (i = 0, l = list.length; i < l; i++) {
|
|
4307
|
+
edge = list[i];
|
|
4308
|
+
validateSerializedEdge(edge);
|
|
4309
|
+
const { source, target, attributes, undirected = undirectedByDefault } = edge;
|
|
4310
|
+
let method;
|
|
4311
|
+
if ("key" in edge) {
|
|
4312
|
+
method = merge ? undirected ? this.mergeUndirectedEdgeWithKey : this.mergeDirectedEdgeWithKey : undirected ? this.addUndirectedEdgeWithKey : this.addDirectedEdgeWithKey;
|
|
4313
|
+
method.call(this, edge.key, source, target, attributes);
|
|
4314
|
+
} else {
|
|
4315
|
+
method = merge ? undirected ? this.mergeUndirectedEdge : this.mergeDirectedEdge : undirected ? this.addUndirectedEdge : this.addDirectedEdge;
|
|
4316
|
+
method.call(this, source, target, attributes);
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return this;
|
|
4321
|
+
}
|
|
4322
|
+
/**---------------------------------------------------------------------------
|
|
4323
|
+
* Utils
|
|
4324
|
+
**---------------------------------------------------------------------------
|
|
4325
|
+
*/
|
|
4326
|
+
/**
|
|
4327
|
+
* Method returning a null copy of the graph, i.e. a graph without nodes
|
|
4328
|
+
* & edges but with the exact same options.
|
|
4329
|
+
*
|
|
4330
|
+
* @param {object} options - Options to merge with the current ones.
|
|
4331
|
+
* @return {Graph} - The null copy.
|
|
4332
|
+
*/
|
|
4333
|
+
nullCopy(options) {
|
|
4334
|
+
const graph = new Graph(assign({}, this._options, options));
|
|
4335
|
+
graph.replaceAttributes(assign({}, this.getAttributes()));
|
|
4336
|
+
return graph;
|
|
4337
|
+
}
|
|
4338
|
+
/**
|
|
4339
|
+
* Method returning an empty copy of the graph, i.e. a graph without edges but
|
|
4340
|
+
* with the exact same options.
|
|
4341
|
+
*
|
|
4342
|
+
* @param {object} options - Options to merge with the current ones.
|
|
4343
|
+
* @return {Graph} - The empty copy.
|
|
4344
|
+
*/
|
|
4345
|
+
emptyCopy(options) {
|
|
4346
|
+
const graph = this.nullCopy(options);
|
|
4347
|
+
this._nodes.forEach((nodeData, key) => {
|
|
4348
|
+
const attributes = assign({}, nodeData.attributes);
|
|
4349
|
+
nodeData = new graph.NodeDataClass(key, attributes);
|
|
4350
|
+
graph._nodes.set(key, nodeData);
|
|
4351
|
+
});
|
|
4352
|
+
return graph;
|
|
4353
|
+
}
|
|
4354
|
+
/**
|
|
4355
|
+
* Method returning an exact copy of the graph.
|
|
4356
|
+
*
|
|
4357
|
+
* @param {object} options - Upgrade options.
|
|
4358
|
+
* @return {Graph} - The copy.
|
|
4359
|
+
*/
|
|
4360
|
+
copy(options) {
|
|
4361
|
+
options = options || {};
|
|
4362
|
+
if (typeof options.type === "string" && options.type !== this.type && options.type !== "mixed") throw new UsageGraphError(`Graph.copy: cannot create an incompatible copy from "${this.type}" type to "${options.type}" because this would mean losing information about the current graph.`);
|
|
4363
|
+
if (typeof options.multi === "boolean" && options.multi !== this.multi && options.multi !== true) throw new UsageGraphError("Graph.copy: cannot create an incompatible copy by downgrading a multi graph to a simple one because this would mean losing information about the current graph.");
|
|
4364
|
+
if (typeof options.allowSelfLoops === "boolean" && options.allowSelfLoops !== this.allowSelfLoops && options.allowSelfLoops !== true) throw new UsageGraphError("Graph.copy: cannot create an incompatible copy from a graph allowing self loops to one that does not because this would mean losing information about the current graph.");
|
|
4365
|
+
const graph = this.emptyCopy(options);
|
|
4366
|
+
const iterator = this._edges.values();
|
|
4367
|
+
let step, edgeData;
|
|
4368
|
+
while (step = iterator.next(), step.done !== true) {
|
|
4369
|
+
edgeData = step.value;
|
|
4370
|
+
addEdge(graph, "copy", false, edgeData.undirected, edgeData.key, edgeData.source.key, edgeData.target.key, assign({}, edgeData.attributes));
|
|
4371
|
+
}
|
|
4372
|
+
return graph;
|
|
4373
|
+
}
|
|
4374
|
+
/**---------------------------------------------------------------------------
|
|
4375
|
+
* Known methods
|
|
4376
|
+
**---------------------------------------------------------------------------
|
|
4377
|
+
*/
|
|
4378
|
+
/**
|
|
4379
|
+
* Method used by JavaScript to perform JSON serialization.
|
|
4380
|
+
*
|
|
4381
|
+
* @return {object} - The serialized graph.
|
|
4382
|
+
*/
|
|
4383
|
+
toJSON() {
|
|
4384
|
+
return this.export();
|
|
4385
|
+
}
|
|
4386
|
+
/**
|
|
4387
|
+
* Method returning [object Graph].
|
|
4388
|
+
*/
|
|
4389
|
+
toString() {
|
|
4390
|
+
return "[object Graph]";
|
|
4391
|
+
}
|
|
4392
|
+
/**
|
|
4393
|
+
* Method used internally by node's console to display a custom object.
|
|
4394
|
+
*
|
|
4395
|
+
* @return {object} - Formatted object representation of the graph.
|
|
4396
|
+
*/
|
|
4397
|
+
inspect() {
|
|
4398
|
+
const nodes = {};
|
|
4399
|
+
this._nodes.forEach((data, key) => {
|
|
4400
|
+
nodes[key] = data.attributes;
|
|
4401
|
+
});
|
|
4402
|
+
const edges = {}, multiIndex = {};
|
|
4403
|
+
this._edges.forEach((data, key) => {
|
|
4404
|
+
const direction = data.undirected ? "--" : "->";
|
|
4405
|
+
let label = "";
|
|
4406
|
+
let source = data.source.key;
|
|
4407
|
+
let target = data.target.key;
|
|
4408
|
+
let tmp;
|
|
4409
|
+
if (data.undirected && source > target) {
|
|
4410
|
+
tmp = source;
|
|
4411
|
+
source = target;
|
|
4412
|
+
target = tmp;
|
|
4413
|
+
}
|
|
4414
|
+
const desc = `(${source})${direction}(${target})`;
|
|
4415
|
+
if (!key.startsWith("geid_")) label += `[${key}]: `;
|
|
4416
|
+
else if (this.multi) {
|
|
4417
|
+
if (typeof multiIndex[desc] === "undefined") multiIndex[desc] = 0;
|
|
4418
|
+
else multiIndex[desc]++;
|
|
4419
|
+
label += `${multiIndex[desc]}. `;
|
|
4420
|
+
}
|
|
4421
|
+
label += desc;
|
|
4422
|
+
edges[label] = data.attributes;
|
|
4423
|
+
});
|
|
4424
|
+
const dummy = {};
|
|
4425
|
+
for (const k in this) if (this.hasOwnProperty(k) && !EMITTER_PROPS.has(k) && typeof this[k] !== "function" && typeof k !== "symbol") dummy[k] = this[k];
|
|
4426
|
+
dummy.attributes = this._attributes;
|
|
4427
|
+
dummy.nodes = nodes;
|
|
4428
|
+
dummy.edges = edges;
|
|
4429
|
+
privateProperty(dummy, "constructor", this.constructor);
|
|
4430
|
+
return dummy;
|
|
4431
|
+
}
|
|
4432
|
+
};
|
|
4433
|
+
/**
|
|
4434
|
+
* Attaching methods to the prototype.
|
|
4435
|
+
*
|
|
4436
|
+
* Here, we are attaching a wide variety of methods to the Graph class'
|
|
4437
|
+
* prototype when those are very numerous and when their creation is
|
|
4438
|
+
* abstracted.
|
|
4439
|
+
*/
|
|
4440
|
+
/**
|
|
4441
|
+
* Attaching custom inspect method for node >= 10.
|
|
4442
|
+
*/
|
|
4443
|
+
if (typeof Symbol !== "undefined") Graph.prototype[Symbol.for("nodejs.util.inspect.custom")] = Graph.prototype.inspect;
|
|
4444
|
+
/**
|
|
4445
|
+
* Related to edge addition.
|
|
4446
|
+
*/
|
|
4447
|
+
EDGE_ADD_METHODS.forEach((method) => {
|
|
4448
|
+
[
|
|
4449
|
+
"add",
|
|
4450
|
+
"merge",
|
|
4451
|
+
"update"
|
|
4452
|
+
].forEach((verb) => {
|
|
4453
|
+
const name = method.name(verb);
|
|
4454
|
+
const fn = verb === "add" ? addEdge : mergeEdge;
|
|
4455
|
+
if (method.generateKey) Graph.prototype[name] = function(source, target, attributes) {
|
|
4456
|
+
return fn(this, name, true, (method.type || this.type) === "undirected", null, source, target, attributes, verb === "update");
|
|
4457
|
+
};
|
|
4458
|
+
else Graph.prototype[name] = function(edge, source, target, attributes) {
|
|
4459
|
+
return fn(this, name, false, (method.type || this.type) === "undirected", edge, source, target, attributes, verb === "update");
|
|
4460
|
+
};
|
|
4461
|
+
});
|
|
4462
|
+
});
|
|
4463
|
+
/**
|
|
4464
|
+
* Attributes-related.
|
|
4465
|
+
*/
|
|
4466
|
+
attachNodeAttributesMethods(Graph);
|
|
4467
|
+
attachEdgeAttributesMethods(Graph);
|
|
4468
|
+
/**
|
|
4469
|
+
* Edge iteration-related.
|
|
4470
|
+
*/
|
|
4471
|
+
attachEdgeIterationMethods(Graph);
|
|
4472
|
+
/**
|
|
4473
|
+
* Neighbor iteration-related.
|
|
4474
|
+
*/
|
|
4475
|
+
attachNeighborIterationMethods(Graph);
|
|
4476
|
+
/**
|
|
4477
|
+
* Graphology Helper Classes
|
|
4478
|
+
* ==========================
|
|
4479
|
+
*
|
|
4480
|
+
* Building some higher-order classes instantiating the graph with
|
|
4481
|
+
* predefinite options.
|
|
4482
|
+
*/
|
|
4483
|
+
/**
|
|
4484
|
+
* Alternative constructors.
|
|
4485
|
+
*/
|
|
4486
|
+
var DirectedGraph = class extends Graph {
|
|
4487
|
+
constructor(options) {
|
|
4488
|
+
const finalOptions = assign({ type: "directed" }, options);
|
|
4489
|
+
if ("multi" in finalOptions && finalOptions.multi !== false) throw new InvalidArgumentsGraphError("DirectedGraph.from: inconsistent indication that the graph should be multi in given options!");
|
|
4490
|
+
if (finalOptions.type !== "directed") throw new InvalidArgumentsGraphError("DirectedGraph.from: inconsistent \"" + finalOptions.type + "\" type in given options!");
|
|
4491
|
+
super(finalOptions);
|
|
4492
|
+
}
|
|
4493
|
+
};
|
|
4494
|
+
var UndirectedGraph = class extends Graph {
|
|
4495
|
+
constructor(options) {
|
|
4496
|
+
const finalOptions = assign({ type: "undirected" }, options);
|
|
4497
|
+
if ("multi" in finalOptions && finalOptions.multi !== false) throw new InvalidArgumentsGraphError("UndirectedGraph.from: inconsistent indication that the graph should be multi in given options!");
|
|
4498
|
+
if (finalOptions.type !== "undirected") throw new InvalidArgumentsGraphError("UndirectedGraph.from: inconsistent \"" + finalOptions.type + "\" type in given options!");
|
|
4499
|
+
super(finalOptions);
|
|
4500
|
+
}
|
|
4501
|
+
};
|
|
4502
|
+
var MultiGraph = class extends Graph {
|
|
4503
|
+
constructor(options) {
|
|
4504
|
+
const finalOptions = assign({ multi: true }, options);
|
|
4505
|
+
if ("multi" in finalOptions && finalOptions.multi !== true) throw new InvalidArgumentsGraphError("MultiGraph.from: inconsistent indication that the graph should be simple in given options!");
|
|
4506
|
+
super(finalOptions);
|
|
4507
|
+
}
|
|
4508
|
+
};
|
|
4509
|
+
var MultiDirectedGraph = class extends Graph {
|
|
4510
|
+
constructor(options) {
|
|
4511
|
+
const finalOptions = assign({
|
|
4512
|
+
type: "directed",
|
|
4513
|
+
multi: true
|
|
4514
|
+
}, options);
|
|
4515
|
+
if ("multi" in finalOptions && finalOptions.multi !== true) throw new InvalidArgumentsGraphError("MultiDirectedGraph.from: inconsistent indication that the graph should be simple in given options!");
|
|
4516
|
+
if (finalOptions.type !== "directed") throw new InvalidArgumentsGraphError("MultiDirectedGraph.from: inconsistent \"" + finalOptions.type + "\" type in given options!");
|
|
4517
|
+
super(finalOptions);
|
|
4518
|
+
}
|
|
4519
|
+
};
|
|
4520
|
+
var MultiUndirectedGraph = class extends Graph {
|
|
4521
|
+
constructor(options) {
|
|
4522
|
+
const finalOptions = assign({
|
|
4523
|
+
type: "undirected",
|
|
4524
|
+
multi: true
|
|
4525
|
+
}, options);
|
|
4526
|
+
if ("multi" in finalOptions && finalOptions.multi !== true) throw new InvalidArgumentsGraphError("MultiUndirectedGraph.from: inconsistent indication that the graph should be simple in given options!");
|
|
4527
|
+
if (finalOptions.type !== "undirected") throw new InvalidArgumentsGraphError("MultiUndirectedGraph.from: inconsistent \"" + finalOptions.type + "\" type in given options!");
|
|
4528
|
+
super(finalOptions);
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
/**
|
|
4532
|
+
* Attaching static #.from method to each of the constructors.
|
|
4533
|
+
*/
|
|
4534
|
+
function attachStaticFromMethod(Class) {
|
|
4535
|
+
/**
|
|
4536
|
+
* Builds a graph from serialized data or another graph's data.
|
|
4537
|
+
*
|
|
4538
|
+
* @param {Graph|SerializedGraph} data - Hydratation data.
|
|
4539
|
+
* @param {object} [options] - Options.
|
|
4540
|
+
* @return {Class}
|
|
4541
|
+
*/
|
|
4542
|
+
Class.from = function(data, options) {
|
|
4543
|
+
const instance = new Class(assign({}, data.options, options));
|
|
4544
|
+
instance.import(data);
|
|
4545
|
+
return instance;
|
|
4546
|
+
};
|
|
4547
|
+
}
|
|
4548
|
+
attachStaticFromMethod(Graph);
|
|
4549
|
+
attachStaticFromMethod(DirectedGraph);
|
|
4550
|
+
attachStaticFromMethod(UndirectedGraph);
|
|
4551
|
+
attachStaticFromMethod(MultiGraph);
|
|
4552
|
+
attachStaticFromMethod(MultiDirectedGraph);
|
|
4553
|
+
attachStaticFromMethod(MultiUndirectedGraph);
|
|
4554
|
+
Graph.Graph = Graph;
|
|
4555
|
+
Graph.DirectedGraph = DirectedGraph;
|
|
4556
|
+
Graph.UndirectedGraph = UndirectedGraph;
|
|
4557
|
+
Graph.MultiGraph = MultiGraph;
|
|
4558
|
+
Graph.MultiDirectedGraph = MultiDirectedGraph;
|
|
4559
|
+
Graph.MultiUndirectedGraph = MultiUndirectedGraph;
|
|
4560
|
+
Graph.InvalidArgumentsGraphError = InvalidArgumentsGraphError;
|
|
4561
|
+
Graph.NotFoundGraphError = NotFoundGraphError;
|
|
4562
|
+
Graph.UsageGraphError = UsageGraphError;
|
|
4563
|
+
|
|
4564
|
+
//#endregion
|
|
4565
|
+
export { Graph as t };
|