4625 lines
146 KiB
JavaScript
4625 lines
146 KiB
JavaScript
|
|
import {
|
||
|
|
__commonJS,
|
||
|
|
__toESM
|
||
|
|
} from "./chunk-4B2QHNJT.js";
|
||
|
|
|
||
|
|
// ../../node_modules/.pnpm/events@3.3.0/node_modules/events/events.js
|
||
|
|
var require_events = __commonJS({
|
||
|
|
"../../node_modules/.pnpm/events@3.3.0/node_modules/events/events.js"(exports, module) {
|
||
|
|
"use strict";
|
||
|
|
var R = typeof Reflect === "object" ? Reflect : null;
|
||
|
|
var ReflectApply = R && typeof R.apply === "function" ? R.apply : function ReflectApply2(target, receiver, args) {
|
||
|
|
return Function.prototype.apply.call(target, receiver, args);
|
||
|
|
};
|
||
|
|
var ReflectOwnKeys;
|
||
|
|
if (R && typeof R.ownKeys === "function") {
|
||
|
|
ReflectOwnKeys = R.ownKeys;
|
||
|
|
} else if (Object.getOwnPropertySymbols) {
|
||
|
|
ReflectOwnKeys = function ReflectOwnKeys2(target) {
|
||
|
|
return Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target));
|
||
|
|
};
|
||
|
|
} else {
|
||
|
|
ReflectOwnKeys = function ReflectOwnKeys2(target) {
|
||
|
|
return Object.getOwnPropertyNames(target);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function ProcessEmitWarning(warning) {
|
||
|
|
if (console && console.warn) console.warn(warning);
|
||
|
|
}
|
||
|
|
var NumberIsNaN = Number.isNaN || function NumberIsNaN2(value) {
|
||
|
|
return value !== value;
|
||
|
|
};
|
||
|
|
function EventEmitter2() {
|
||
|
|
EventEmitter2.init.call(this);
|
||
|
|
}
|
||
|
|
module.exports = EventEmitter2;
|
||
|
|
module.exports.once = once;
|
||
|
|
EventEmitter2.EventEmitter = EventEmitter2;
|
||
|
|
EventEmitter2.prototype._events = void 0;
|
||
|
|
EventEmitter2.prototype._eventsCount = 0;
|
||
|
|
EventEmitter2.prototype._maxListeners = void 0;
|
||
|
|
var defaultMaxListeners = 10;
|
||
|
|
function checkListener(listener) {
|
||
|
|
if (typeof listener !== "function") {
|
||
|
|
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Object.defineProperty(EventEmitter2, "defaultMaxListeners", {
|
||
|
|
enumerable: true,
|
||
|
|
get: function() {
|
||
|
|
return defaultMaxListeners;
|
||
|
|
},
|
||
|
|
set: function(arg) {
|
||
|
|
if (typeof arg !== "number" || arg < 0 || NumberIsNaN(arg)) {
|
||
|
|
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + ".");
|
||
|
|
}
|
||
|
|
defaultMaxListeners = arg;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
EventEmitter2.init = function() {
|
||
|
|
if (this._events === void 0 || this._events === Object.getPrototypeOf(this)._events) {
|
||
|
|
this._events = /* @__PURE__ */ Object.create(null);
|
||
|
|
this._eventsCount = 0;
|
||
|
|
}
|
||
|
|
this._maxListeners = this._maxListeners || void 0;
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.setMaxListeners = function setMaxListeners(n) {
|
||
|
|
if (typeof n !== "number" || n < 0 || NumberIsNaN(n)) {
|
||
|
|
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + ".");
|
||
|
|
}
|
||
|
|
this._maxListeners = n;
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
function _getMaxListeners(that) {
|
||
|
|
if (that._maxListeners === void 0)
|
||
|
|
return EventEmitter2.defaultMaxListeners;
|
||
|
|
return that._maxListeners;
|
||
|
|
}
|
||
|
|
EventEmitter2.prototype.getMaxListeners = function getMaxListeners() {
|
||
|
|
return _getMaxListeners(this);
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.emit = function emit(type) {
|
||
|
|
var args = [];
|
||
|
|
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
|
||
|
|
var doError = type === "error";
|
||
|
|
var events = this._events;
|
||
|
|
if (events !== void 0)
|
||
|
|
doError = doError && events.error === void 0;
|
||
|
|
else if (!doError)
|
||
|
|
return false;
|
||
|
|
if (doError) {
|
||
|
|
var er;
|
||
|
|
if (args.length > 0)
|
||
|
|
er = args[0];
|
||
|
|
if (er instanceof Error) {
|
||
|
|
throw er;
|
||
|
|
}
|
||
|
|
var err = new Error("Unhandled error." + (er ? " (" + er.message + ")" : ""));
|
||
|
|
err.context = er;
|
||
|
|
throw err;
|
||
|
|
}
|
||
|
|
var handler = events[type];
|
||
|
|
if (handler === void 0)
|
||
|
|
return false;
|
||
|
|
if (typeof handler === "function") {
|
||
|
|
ReflectApply(handler, this, args);
|
||
|
|
} else {
|
||
|
|
var len = handler.length;
|
||
|
|
var listeners = arrayClone(handler, len);
|
||
|
|
for (var i = 0; i < len; ++i)
|
||
|
|
ReflectApply(listeners[i], this, args);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
function _addListener(target, type, listener, prepend) {
|
||
|
|
var m;
|
||
|
|
var events;
|
||
|
|
var existing;
|
||
|
|
checkListener(listener);
|
||
|
|
events = target._events;
|
||
|
|
if (events === void 0) {
|
||
|
|
events = target._events = /* @__PURE__ */ Object.create(null);
|
||
|
|
target._eventsCount = 0;
|
||
|
|
} else {
|
||
|
|
if (events.newListener !== void 0) {
|
||
|
|
target.emit(
|
||
|
|
"newListener",
|
||
|
|
type,
|
||
|
|
listener.listener ? listener.listener : listener
|
||
|
|
);
|
||
|
|
events = target._events;
|
||
|
|
}
|
||
|
|
existing = events[type];
|
||
|
|
}
|
||
|
|
if (existing === void 0) {
|
||
|
|
existing = events[type] = listener;
|
||
|
|
++target._eventsCount;
|
||
|
|
} else {
|
||
|
|
if (typeof existing === "function") {
|
||
|
|
existing = events[type] = prepend ? [listener, existing] : [existing, listener];
|
||
|
|
} else if (prepend) {
|
||
|
|
existing.unshift(listener);
|
||
|
|
} else {
|
||
|
|
existing.push(listener);
|
||
|
|
}
|
||
|
|
m = _getMaxListeners(target);
|
||
|
|
if (m > 0 && existing.length > m && !existing.warned) {
|
||
|
|
existing.warned = true;
|
||
|
|
var w = new Error("Possible EventEmitter memory leak detected. " + existing.length + " " + String(type) + " listeners added. Use emitter.setMaxListeners() to increase limit");
|
||
|
|
w.name = "MaxListenersExceededWarning";
|
||
|
|
w.emitter = target;
|
||
|
|
w.type = type;
|
||
|
|
w.count = existing.length;
|
||
|
|
ProcessEmitWarning(w);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return target;
|
||
|
|
}
|
||
|
|
EventEmitter2.prototype.addListener = function addListener(type, listener) {
|
||
|
|
return _addListener(this, type, listener, false);
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.on = EventEmitter2.prototype.addListener;
|
||
|
|
EventEmitter2.prototype.prependListener = function prependListener(type, listener) {
|
||
|
|
return _addListener(this, type, listener, true);
|
||
|
|
};
|
||
|
|
function onceWrapper() {
|
||
|
|
if (!this.fired) {
|
||
|
|
this.target.removeListener(this.type, this.wrapFn);
|
||
|
|
this.fired = true;
|
||
|
|
if (arguments.length === 0)
|
||
|
|
return this.listener.call(this.target);
|
||
|
|
return this.listener.apply(this.target, arguments);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function _onceWrap(target, type, listener) {
|
||
|
|
var state = { fired: false, wrapFn: void 0, target, type, listener };
|
||
|
|
var wrapped = onceWrapper.bind(state);
|
||
|
|
wrapped.listener = listener;
|
||
|
|
state.wrapFn = wrapped;
|
||
|
|
return wrapped;
|
||
|
|
}
|
||
|
|
EventEmitter2.prototype.once = function once2(type, listener) {
|
||
|
|
checkListener(listener);
|
||
|
|
this.on(type, _onceWrap(this, type, listener));
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.prependOnceListener = function prependOnceListener(type, listener) {
|
||
|
|
checkListener(listener);
|
||
|
|
this.prependListener(type, _onceWrap(this, type, listener));
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.removeListener = function removeListener(type, listener) {
|
||
|
|
var list, events, position, i, originalListener;
|
||
|
|
checkListener(listener);
|
||
|
|
events = this._events;
|
||
|
|
if (events === void 0)
|
||
|
|
return this;
|
||
|
|
list = events[type];
|
||
|
|
if (list === void 0)
|
||
|
|
return this;
|
||
|
|
if (list === listener || list.listener === listener) {
|
||
|
|
if (--this._eventsCount === 0)
|
||
|
|
this._events = /* @__PURE__ */ Object.create(null);
|
||
|
|
else {
|
||
|
|
delete events[type];
|
||
|
|
if (events.removeListener)
|
||
|
|
this.emit("removeListener", type, list.listener || listener);
|
||
|
|
}
|
||
|
|
} else if (typeof list !== "function") {
|
||
|
|
position = -1;
|
||
|
|
for (i = list.length - 1; i >= 0; i--) {
|
||
|
|
if (list[i] === listener || list[i].listener === listener) {
|
||
|
|
originalListener = list[i].listener;
|
||
|
|
position = i;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (position < 0)
|
||
|
|
return this;
|
||
|
|
if (position === 0)
|
||
|
|
list.shift();
|
||
|
|
else {
|
||
|
|
spliceOne(list, position);
|
||
|
|
}
|
||
|
|
if (list.length === 1)
|
||
|
|
events[type] = list[0];
|
||
|
|
if (events.removeListener !== void 0)
|
||
|
|
this.emit("removeListener", type, originalListener || listener);
|
||
|
|
}
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.off = EventEmitter2.prototype.removeListener;
|
||
|
|
EventEmitter2.prototype.removeAllListeners = function removeAllListeners(type) {
|
||
|
|
var listeners, events, i;
|
||
|
|
events = this._events;
|
||
|
|
if (events === void 0)
|
||
|
|
return this;
|
||
|
|
if (events.removeListener === void 0) {
|
||
|
|
if (arguments.length === 0) {
|
||
|
|
this._events = /* @__PURE__ */ Object.create(null);
|
||
|
|
this._eventsCount = 0;
|
||
|
|
} else if (events[type] !== void 0) {
|
||
|
|
if (--this._eventsCount === 0)
|
||
|
|
this._events = /* @__PURE__ */ Object.create(null);
|
||
|
|
else
|
||
|
|
delete events[type];
|
||
|
|
}
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
if (arguments.length === 0) {
|
||
|
|
var keys = Object.keys(events);
|
||
|
|
var key;
|
||
|
|
for (i = 0; i < keys.length; ++i) {
|
||
|
|
key = keys[i];
|
||
|
|
if (key === "removeListener") continue;
|
||
|
|
this.removeAllListeners(key);
|
||
|
|
}
|
||
|
|
this.removeAllListeners("removeListener");
|
||
|
|
this._events = /* @__PURE__ */ Object.create(null);
|
||
|
|
this._eventsCount = 0;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
listeners = events[type];
|
||
|
|
if (typeof listeners === "function") {
|
||
|
|
this.removeListener(type, listeners);
|
||
|
|
} else if (listeners !== void 0) {
|
||
|
|
for (i = listeners.length - 1; i >= 0; i--) {
|
||
|
|
this.removeListener(type, listeners[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
function _listeners(target, type, unwrap) {
|
||
|
|
var events = target._events;
|
||
|
|
if (events === void 0)
|
||
|
|
return [];
|
||
|
|
var evlistener = events[type];
|
||
|
|
if (evlistener === void 0)
|
||
|
|
return [];
|
||
|
|
if (typeof evlistener === "function")
|
||
|
|
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
|
||
|
|
return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
|
||
|
|
}
|
||
|
|
EventEmitter2.prototype.listeners = function listeners(type) {
|
||
|
|
return _listeners(this, type, true);
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.rawListeners = function rawListeners(type) {
|
||
|
|
return _listeners(this, type, false);
|
||
|
|
};
|
||
|
|
EventEmitter2.listenerCount = function(emitter, type) {
|
||
|
|
if (typeof emitter.listenerCount === "function") {
|
||
|
|
return emitter.listenerCount(type);
|
||
|
|
} else {
|
||
|
|
return listenerCount.call(emitter, type);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
EventEmitter2.prototype.listenerCount = listenerCount;
|
||
|
|
function listenerCount(type) {
|
||
|
|
var events = this._events;
|
||
|
|
if (events !== void 0) {
|
||
|
|
var evlistener = events[type];
|
||
|
|
if (typeof evlistener === "function") {
|
||
|
|
return 1;
|
||
|
|
} else if (evlistener !== void 0) {
|
||
|
|
return evlistener.length;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
EventEmitter2.prototype.eventNames = function eventNames() {
|
||
|
|
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
|
||
|
|
};
|
||
|
|
function arrayClone(arr, n) {
|
||
|
|
var copy = new Array(n);
|
||
|
|
for (var i = 0; i < n; ++i)
|
||
|
|
copy[i] = arr[i];
|
||
|
|
return copy;
|
||
|
|
}
|
||
|
|
function spliceOne(list, index) {
|
||
|
|
for (; index + 1 < list.length; index++)
|
||
|
|
list[index] = list[index + 1];
|
||
|
|
list.pop();
|
||
|
|
}
|
||
|
|
function unwrapListeners(arr) {
|
||
|
|
var ret = new Array(arr.length);
|
||
|
|
for (var i = 0; i < ret.length; ++i) {
|
||
|
|
ret[i] = arr[i].listener || arr[i];
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
function once(emitter, name) {
|
||
|
|
return new Promise(function(resolve, reject) {
|
||
|
|
function errorListener(err) {
|
||
|
|
emitter.removeListener(name, resolver);
|
||
|
|
reject(err);
|
||
|
|
}
|
||
|
|
function resolver() {
|
||
|
|
if (typeof emitter.removeListener === "function") {
|
||
|
|
emitter.removeListener("error", errorListener);
|
||
|
|
}
|
||
|
|
resolve([].slice.call(arguments));
|
||
|
|
}
|
||
|
|
;
|
||
|
|
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
|
||
|
|
if (name !== "error") {
|
||
|
|
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
|
||
|
|
if (typeof emitter.on === "function") {
|
||
|
|
eventTargetAgnosticAddListener(emitter, "error", handler, flags);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
|
||
|
|
if (typeof emitter.on === "function") {
|
||
|
|
if (flags.once) {
|
||
|
|
emitter.once(name, listener);
|
||
|
|
} else {
|
||
|
|
emitter.on(name, listener);
|
||
|
|
}
|
||
|
|
} else if (typeof emitter.addEventListener === "function") {
|
||
|
|
emitter.addEventListener(name, function wrapListener(arg) {
|
||
|
|
if (flags.once) {
|
||
|
|
emitter.removeEventListener(name, wrapListener);
|
||
|
|
}
|
||
|
|
listener(arg);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// ../../node_modules/.pnpm/graphology@0.26.0_graphology-types@0.24.8/node_modules/graphology/dist/graphology.mjs
|
||
|
|
var import_events = __toESM(require_events(), 1);
|
||
|
|
function assignPolyfill() {
|
||
|
|
const target = arguments[0];
|
||
|
|
for (let i = 1, l = arguments.length; i < l; i++) {
|
||
|
|
if (!arguments[i]) continue;
|
||
|
|
for (const k in arguments[i]) target[k] = arguments[i][k];
|
||
|
|
}
|
||
|
|
return target;
|
||
|
|
}
|
||
|
|
var assign = assignPolyfill;
|
||
|
|
if (typeof Object.assign === "function") assign = Object.assign;
|
||
|
|
function getMatchingEdge(graph, source, target, type) {
|
||
|
|
const sourceData = graph._nodes.get(source);
|
||
|
|
let edge = null;
|
||
|
|
if (!sourceData) return edge;
|
||
|
|
if (type === "mixed") {
|
||
|
|
edge = sourceData.out && sourceData.out[target] || sourceData.undirected && sourceData.undirected[target];
|
||
|
|
} else if (type === "directed") {
|
||
|
|
edge = sourceData.out && sourceData.out[target];
|
||
|
|
} else {
|
||
|
|
edge = sourceData.undirected && sourceData.undirected[target];
|
||
|
|
}
|
||
|
|
return edge;
|
||
|
|
}
|
||
|
|
function isPlainObject(value) {
|
||
|
|
return typeof value === "object" && value !== null;
|
||
|
|
}
|
||
|
|
function isEmpty(o) {
|
||
|
|
let k;
|
||
|
|
for (k in o) return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
function privateProperty(target, name, value) {
|
||
|
|
Object.defineProperty(target, name, {
|
||
|
|
enumerable: false,
|
||
|
|
configurable: false,
|
||
|
|
writable: true,
|
||
|
|
value
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function readOnlyProperty(target, name, value) {
|
||
|
|
const descriptor = {
|
||
|
|
enumerable: true,
|
||
|
|
configurable: true
|
||
|
|
};
|
||
|
|
if (typeof value === "function") {
|
||
|
|
descriptor.get = value;
|
||
|
|
} else {
|
||
|
|
descriptor.value = value;
|
||
|
|
descriptor.writable = false;
|
||
|
|
}
|
||
|
|
Object.defineProperty(target, name, descriptor);
|
||
|
|
}
|
||
|
|
function validateHints(hints) {
|
||
|
|
if (!isPlainObject(hints)) return false;
|
||
|
|
if (hints.attributes && !Array.isArray(hints.attributes)) return false;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
function incrementalIdStartingFromRandomByte() {
|
||
|
|
let i = Math.floor(Math.random() * 256) & 255;
|
||
|
|
return () => {
|
||
|
|
return i++;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function chain() {
|
||
|
|
const iterables = arguments;
|
||
|
|
let current = null;
|
||
|
|
let i = -1;
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
let step = null;
|
||
|
|
do {
|
||
|
|
if (current === null) {
|
||
|
|
i++;
|
||
|
|
if (i >= iterables.length) return { done: true };
|
||
|
|
current = iterables[i][Symbol.iterator]();
|
||
|
|
}
|
||
|
|
step = current.next();
|
||
|
|
if (step.done) {
|
||
|
|
current = null;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
} while (true);
|
||
|
|
return step;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function emptyIterator() {
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
return { done: true };
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
var GraphError = class extends Error {
|
||
|
|
constructor(message) {
|
||
|
|
super();
|
||
|
|
this.name = "GraphError";
|
||
|
|
this.message = message;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var InvalidArgumentsGraphError = class _InvalidArgumentsGraphError extends GraphError {
|
||
|
|
constructor(message) {
|
||
|
|
super(message);
|
||
|
|
this.name = "InvalidArgumentsGraphError";
|
||
|
|
if (typeof Error.captureStackTrace === "function")
|
||
|
|
Error.captureStackTrace(
|
||
|
|
this,
|
||
|
|
_InvalidArgumentsGraphError.prototype.constructor
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var NotFoundGraphError = class _NotFoundGraphError extends GraphError {
|
||
|
|
constructor(message) {
|
||
|
|
super(message);
|
||
|
|
this.name = "NotFoundGraphError";
|
||
|
|
if (typeof Error.captureStackTrace === "function")
|
||
|
|
Error.captureStackTrace(this, _NotFoundGraphError.prototype.constructor);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var UsageGraphError = class _UsageGraphError extends GraphError {
|
||
|
|
constructor(message) {
|
||
|
|
super(message);
|
||
|
|
this.name = "UsageGraphError";
|
||
|
|
if (typeof Error.captureStackTrace === "function")
|
||
|
|
Error.captureStackTrace(this, _UsageGraphError.prototype.constructor);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
function MixedNodeData(key, attributes) {
|
||
|
|
this.key = key;
|
||
|
|
this.attributes = attributes;
|
||
|
|
this.clear();
|
||
|
|
}
|
||
|
|
MixedNodeData.prototype.clear = function() {
|
||
|
|
this.inDegree = 0;
|
||
|
|
this.outDegree = 0;
|
||
|
|
this.undirectedDegree = 0;
|
||
|
|
this.undirectedLoops = 0;
|
||
|
|
this.directedLoops = 0;
|
||
|
|
this.in = {};
|
||
|
|
this.out = {};
|
||
|
|
this.undirected = {};
|
||
|
|
};
|
||
|
|
function DirectedNodeData(key, attributes) {
|
||
|
|
this.key = key;
|
||
|
|
this.attributes = attributes;
|
||
|
|
this.clear();
|
||
|
|
}
|
||
|
|
DirectedNodeData.prototype.clear = function() {
|
||
|
|
this.inDegree = 0;
|
||
|
|
this.outDegree = 0;
|
||
|
|
this.directedLoops = 0;
|
||
|
|
this.in = {};
|
||
|
|
this.out = {};
|
||
|
|
};
|
||
|
|
function UndirectedNodeData(key, attributes) {
|
||
|
|
this.key = key;
|
||
|
|
this.attributes = attributes;
|
||
|
|
this.clear();
|
||
|
|
}
|
||
|
|
UndirectedNodeData.prototype.clear = function() {
|
||
|
|
this.undirectedDegree = 0;
|
||
|
|
this.undirectedLoops = 0;
|
||
|
|
this.undirected = {};
|
||
|
|
};
|
||
|
|
function EdgeData(undirected, key, source, target, attributes) {
|
||
|
|
this.key = key;
|
||
|
|
this.attributes = attributes;
|
||
|
|
this.undirected = undirected;
|
||
|
|
this.source = source;
|
||
|
|
this.target = target;
|
||
|
|
}
|
||
|
|
EdgeData.prototype.attach = function() {
|
||
|
|
let outKey = "out";
|
||
|
|
let inKey = "in";
|
||
|
|
if (this.undirected) outKey = inKey = "undirected";
|
||
|
|
const source = this.source.key;
|
||
|
|
const target = this.target.key;
|
||
|
|
this.source[outKey][target] = this;
|
||
|
|
if (this.undirected && source === target) return;
|
||
|
|
this.target[inKey][source] = this;
|
||
|
|
};
|
||
|
|
EdgeData.prototype.attachMulti = function() {
|
||
|
|
let outKey = "out";
|
||
|
|
let inKey = "in";
|
||
|
|
const source = this.source.key;
|
||
|
|
const target = this.target.key;
|
||
|
|
if (this.undirected) outKey = inKey = "undirected";
|
||
|
|
const adj = this.source[outKey];
|
||
|
|
const head = adj[target];
|
||
|
|
if (typeof head === "undefined") {
|
||
|
|
adj[target] = this;
|
||
|
|
if (!(this.undirected && source === target)) {
|
||
|
|
this.target[inKey][source] = this;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
head.previous = this;
|
||
|
|
this.next = head;
|
||
|
|
adj[target] = this;
|
||
|
|
this.target[inKey][source] = this;
|
||
|
|
};
|
||
|
|
EdgeData.prototype.detach = function() {
|
||
|
|
const source = this.source.key;
|
||
|
|
const target = this.target.key;
|
||
|
|
let outKey = "out";
|
||
|
|
let inKey = "in";
|
||
|
|
if (this.undirected) outKey = inKey = "undirected";
|
||
|
|
delete this.source[outKey][target];
|
||
|
|
delete this.target[inKey][source];
|
||
|
|
};
|
||
|
|
EdgeData.prototype.detachMulti = function() {
|
||
|
|
const source = this.source.key;
|
||
|
|
const target = this.target.key;
|
||
|
|
let outKey = "out";
|
||
|
|
let inKey = "in";
|
||
|
|
if (this.undirected) outKey = inKey = "undirected";
|
||
|
|
if (this.previous === void 0) {
|
||
|
|
if (this.next === void 0) {
|
||
|
|
delete this.source[outKey][target];
|
||
|
|
delete this.target[inKey][source];
|
||
|
|
} else {
|
||
|
|
this.next.previous = void 0;
|
||
|
|
this.source[outKey][target] = this.next;
|
||
|
|
this.target[inKey][source] = this.next;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this.previous.next = this.next;
|
||
|
|
if (this.next !== void 0) {
|
||
|
|
this.next.previous = this.previous;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var NODE = 0;
|
||
|
|
var SOURCE = 1;
|
||
|
|
var TARGET = 2;
|
||
|
|
var OPPOSITE = 3;
|
||
|
|
function findRelevantNodeData(graph, method, mode, nodeOrEdge, nameOrEdge, add1, add2) {
|
||
|
|
let nodeData, edgeData, arg1, arg2;
|
||
|
|
nodeOrEdge = "" + nodeOrEdge;
|
||
|
|
if (mode === NODE) {
|
||
|
|
nodeData = graph._nodes.get(nodeOrEdge);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${nodeOrEdge}" node in the graph.`
|
||
|
|
);
|
||
|
|
arg1 = nameOrEdge;
|
||
|
|
arg2 = add1;
|
||
|
|
} else if (mode === OPPOSITE) {
|
||
|
|
nameOrEdge = "" + nameOrEdge;
|
||
|
|
edgeData = graph._edges.get(nameOrEdge);
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${nameOrEdge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
const source = edgeData.source.key;
|
||
|
|
const target = edgeData.target.key;
|
||
|
|
if (nodeOrEdge === source) {
|
||
|
|
nodeData = edgeData.target;
|
||
|
|
} else if (nodeOrEdge === target) {
|
||
|
|
nodeData = edgeData.source;
|
||
|
|
} else {
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: the "${nodeOrEdge}" node is not attached to the "${nameOrEdge}" edge (${source}, ${target}).`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
arg1 = add1;
|
||
|
|
arg2 = add2;
|
||
|
|
} else {
|
||
|
|
edgeData = graph._edges.get(nodeOrEdge);
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${nodeOrEdge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
if (mode === SOURCE) {
|
||
|
|
nodeData = edgeData.source;
|
||
|
|
} else {
|
||
|
|
nodeData = edgeData.target;
|
||
|
|
}
|
||
|
|
arg1 = nameOrEdge;
|
||
|
|
arg2 = add1;
|
||
|
|
}
|
||
|
|
return [nodeData, arg1, arg2];
|
||
|
|
}
|
||
|
|
function attachNodeAttributeGetter(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
||
|
|
const [data, name] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1
|
||
|
|
);
|
||
|
|
return data.attributes[name];
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributesGetter(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge) {
|
||
|
|
const [data] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge
|
||
|
|
);
|
||
|
|
return data.attributes;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributeChecker(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
||
|
|
const [data, name] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1
|
||
|
|
);
|
||
|
|
return data.attributes.hasOwnProperty(name);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributeSetter(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1, add2) {
|
||
|
|
const [data, name, value] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1,
|
||
|
|
add2
|
||
|
|
);
|
||
|
|
data.attributes[name] = value;
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "set",
|
||
|
|
attributes: data.attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributeUpdater(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1, add2) {
|
||
|
|
const [data, name, updater] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1,
|
||
|
|
add2
|
||
|
|
);
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: updater should be a function.`
|
||
|
|
);
|
||
|
|
const attributes = data.attributes;
|
||
|
|
const value = updater(attributes[name]);
|
||
|
|
attributes[name] = value;
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "set",
|
||
|
|
attributes: data.attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributeRemover(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
||
|
|
const [data, name] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1
|
||
|
|
);
|
||
|
|
delete data.attributes[name];
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "remove",
|
||
|
|
attributes: data.attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributesReplacer(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
||
|
|
const [data, attributes] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1
|
||
|
|
);
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: provided attributes are not a plain object.`
|
||
|
|
);
|
||
|
|
data.attributes = attributes;
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "replace",
|
||
|
|
attributes: data.attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributesMerger(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
||
|
|
const [data, attributes] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1
|
||
|
|
);
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: provided attributes are not a plain object.`
|
||
|
|
);
|
||
|
|
assign(data.attributes, attributes);
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "merge",
|
||
|
|
attributes: data.attributes,
|
||
|
|
data: attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNodeAttributesUpdater(Class, method, mode) {
|
||
|
|
Class.prototype[method] = function(nodeOrEdge, nameOrEdge, add1) {
|
||
|
|
const [data, updater] = findRelevantNodeData(
|
||
|
|
this,
|
||
|
|
method,
|
||
|
|
mode,
|
||
|
|
nodeOrEdge,
|
||
|
|
nameOrEdge,
|
||
|
|
add1
|
||
|
|
);
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: provided updater is not a function.`
|
||
|
|
);
|
||
|
|
data.attributes = updater(data.attributes);
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "update",
|
||
|
|
attributes: data.attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
var NODE_ATTRIBUTES_METHODS = [
|
||
|
|
{
|
||
|
|
name: (element) => `get${element}Attribute`,
|
||
|
|
attacher: attachNodeAttributeGetter
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `get${element}Attributes`,
|
||
|
|
attacher: attachNodeAttributesGetter
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `has${element}Attribute`,
|
||
|
|
attacher: attachNodeAttributeChecker
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `set${element}Attribute`,
|
||
|
|
attacher: attachNodeAttributeSetter
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `update${element}Attribute`,
|
||
|
|
attacher: attachNodeAttributeUpdater
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `remove${element}Attribute`,
|
||
|
|
attacher: attachNodeAttributeRemover
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `replace${element}Attributes`,
|
||
|
|
attacher: attachNodeAttributesReplacer
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `merge${element}Attributes`,
|
||
|
|
attacher: attachNodeAttributesMerger
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `update${element}Attributes`,
|
||
|
|
attacher: attachNodeAttributesUpdater
|
||
|
|
}
|
||
|
|
];
|
||
|
|
function attachNodeAttributesMethods(Graph2) {
|
||
|
|
NODE_ATTRIBUTES_METHODS.forEach(function({ name, attacher }) {
|
||
|
|
attacher(Graph2, name("Node"), NODE);
|
||
|
|
attacher(Graph2, name("Source"), SOURCE);
|
||
|
|
attacher(Graph2, name("Target"), TARGET);
|
||
|
|
attacher(Graph2, name("Opposite"), OPPOSITE);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function attachEdgeAttributeGetter(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, name) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 2) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element;
|
||
|
|
const target = "" + name;
|
||
|
|
name = arguments[2];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return data.attributes[name];
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributesGetter(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 1) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element, target = "" + arguments[1];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return data.attributes;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributeChecker(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, name) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 2) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element;
|
||
|
|
const target = "" + name;
|
||
|
|
name = arguments[2];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return data.attributes.hasOwnProperty(name);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributeSetter(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, name, value) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 3) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element;
|
||
|
|
const target = "" + name;
|
||
|
|
name = arguments[2];
|
||
|
|
value = arguments[3];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
data.attributes[name] = value;
|
||
|
|
this.emit("edgeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "set",
|
||
|
|
attributes: data.attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributeUpdater(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, name, updater) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 3) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element;
|
||
|
|
const target = "" + name;
|
||
|
|
name = arguments[2];
|
||
|
|
updater = arguments[3];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: updater should be a function.`
|
||
|
|
);
|
||
|
|
data.attributes[name] = updater(data.attributes[name]);
|
||
|
|
this.emit("edgeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "set",
|
||
|
|
attributes: data.attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributeRemover(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, name) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 2) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element;
|
||
|
|
const target = "" + name;
|
||
|
|
name = arguments[2];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
delete data.attributes[name];
|
||
|
|
this.emit("edgeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "remove",
|
||
|
|
attributes: data.attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributesReplacer(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, attributes) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 2) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element, target = "" + attributes;
|
||
|
|
attributes = arguments[2];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: provided attributes are not a plain object.`
|
||
|
|
);
|
||
|
|
data.attributes = attributes;
|
||
|
|
this.emit("edgeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "replace",
|
||
|
|
attributes: data.attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributesMerger(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, attributes) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 2) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element, target = "" + attributes;
|
||
|
|
attributes = arguments[2];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: provided attributes are not a plain object.`
|
||
|
|
);
|
||
|
|
assign(data.attributes, attributes);
|
||
|
|
this.emit("edgeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "merge",
|
||
|
|
attributes: data.attributes,
|
||
|
|
data: attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeAttributesUpdater(Class, method, type) {
|
||
|
|
Class.prototype[method] = function(element, updater) {
|
||
|
|
let data;
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (arguments.length > 2) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const source = "" + element, target = "" + updater;
|
||
|
|
updater = arguments[2];
|
||
|
|
data = getMatchingEdge(this, source, target, type);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find an edge for the given path ("${source}" - "${target}").`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
element = "" + element;
|
||
|
|
data = this._edges.get(element);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${method}: could not find the "${element}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${method}: provided updater is not a function.`
|
||
|
|
);
|
||
|
|
data.attributes = updater(data.attributes);
|
||
|
|
this.emit("edgeAttributesUpdated", {
|
||
|
|
key: data.key,
|
||
|
|
type: "update",
|
||
|
|
attributes: data.attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
var EDGE_ATTRIBUTES_METHODS = [
|
||
|
|
{
|
||
|
|
name: (element) => `get${element}Attribute`,
|
||
|
|
attacher: attachEdgeAttributeGetter
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `get${element}Attributes`,
|
||
|
|
attacher: attachEdgeAttributesGetter
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `has${element}Attribute`,
|
||
|
|
attacher: attachEdgeAttributeChecker
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `set${element}Attribute`,
|
||
|
|
attacher: attachEdgeAttributeSetter
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `update${element}Attribute`,
|
||
|
|
attacher: attachEdgeAttributeUpdater
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `remove${element}Attribute`,
|
||
|
|
attacher: attachEdgeAttributeRemover
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `replace${element}Attributes`,
|
||
|
|
attacher: attachEdgeAttributesReplacer
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `merge${element}Attributes`,
|
||
|
|
attacher: attachEdgeAttributesMerger
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (element) => `update${element}Attributes`,
|
||
|
|
attacher: attachEdgeAttributesUpdater
|
||
|
|
}
|
||
|
|
];
|
||
|
|
function attachEdgeAttributesMethods(Graph2) {
|
||
|
|
EDGE_ATTRIBUTES_METHODS.forEach(function({ name, attacher }) {
|
||
|
|
attacher(Graph2, name("Edge"), "mixed");
|
||
|
|
attacher(Graph2, name("DirectedEdge"), "directed");
|
||
|
|
attacher(Graph2, name("UndirectedEdge"), "undirected");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
var EDGES_ITERATION = [
|
||
|
|
{
|
||
|
|
name: "edges",
|
||
|
|
type: "mixed"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "inEdges",
|
||
|
|
type: "directed",
|
||
|
|
direction: "in"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "outEdges",
|
||
|
|
type: "directed",
|
||
|
|
direction: "out"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "inboundEdges",
|
||
|
|
type: "mixed",
|
||
|
|
direction: "in"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "outboundEdges",
|
||
|
|
type: "mixed",
|
||
|
|
direction: "out"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "directedEdges",
|
||
|
|
type: "directed"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "undirectedEdges",
|
||
|
|
type: "undirected"
|
||
|
|
}
|
||
|
|
];
|
||
|
|
function forEachSimple(breakable, object, callback, avoid) {
|
||
|
|
let shouldBreak = false;
|
||
|
|
for (const k in object) {
|
||
|
|
if (k === avoid) continue;
|
||
|
|
const edgeData = object[k];
|
||
|
|
shouldBreak = callback(
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
edgeData.source.key,
|
||
|
|
edgeData.target.key,
|
||
|
|
edgeData.source.attributes,
|
||
|
|
edgeData.target.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return edgeData.key;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function forEachMulti(breakable, object, callback, avoid) {
|
||
|
|
let edgeData, source, target;
|
||
|
|
let shouldBreak = false;
|
||
|
|
for (const k in object) {
|
||
|
|
if (k === avoid) continue;
|
||
|
|
edgeData = object[k];
|
||
|
|
do {
|
||
|
|
source = edgeData.source;
|
||
|
|
target = edgeData.target;
|
||
|
|
shouldBreak = callback(
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
source.key,
|
||
|
|
target.key,
|
||
|
|
source.attributes,
|
||
|
|
target.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return edgeData.key;
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData !== void 0);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function createIterator(object, avoid) {
|
||
|
|
const keys = Object.keys(object);
|
||
|
|
const l = keys.length;
|
||
|
|
let edgeData;
|
||
|
|
let i = 0;
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
do {
|
||
|
|
if (!edgeData) {
|
||
|
|
if (i >= l) return { done: true };
|
||
|
|
const k = keys[i++];
|
||
|
|
if (k === avoid) {
|
||
|
|
edgeData = void 0;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
edgeData = object[k];
|
||
|
|
} else {
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
}
|
||
|
|
} while (!edgeData);
|
||
|
|
return {
|
||
|
|
done: false,
|
||
|
|
value: {
|
||
|
|
edge: edgeData.key,
|
||
|
|
attributes: edgeData.attributes,
|
||
|
|
source: edgeData.source.key,
|
||
|
|
target: edgeData.target.key,
|
||
|
|
sourceAttributes: edgeData.source.attributes,
|
||
|
|
targetAttributes: edgeData.target.attributes,
|
||
|
|
undirected: edgeData.undirected
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function forEachForKeySimple(breakable, object, k, callback) {
|
||
|
|
const edgeData = object[k];
|
||
|
|
if (!edgeData) return;
|
||
|
|
const sourceData = edgeData.source;
|
||
|
|
const targetData = edgeData.target;
|
||
|
|
if (callback(
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
sourceData.key,
|
||
|
|
targetData.key,
|
||
|
|
sourceData.attributes,
|
||
|
|
targetData.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
) && breakable)
|
||
|
|
return edgeData.key;
|
||
|
|
}
|
||
|
|
function forEachForKeyMulti(breakable, object, k, callback) {
|
||
|
|
let edgeData = object[k];
|
||
|
|
if (!edgeData) return;
|
||
|
|
let shouldBreak = false;
|
||
|
|
do {
|
||
|
|
shouldBreak = callback(
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
edgeData.source.key,
|
||
|
|
edgeData.target.key,
|
||
|
|
edgeData.source.attributes,
|
||
|
|
edgeData.target.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return edgeData.key;
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData !== void 0);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function createIteratorForKey(object, k) {
|
||
|
|
let edgeData = object[k];
|
||
|
|
if (edgeData.next !== void 0) {
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
if (!edgeData) return { done: true };
|
||
|
|
const value = {
|
||
|
|
edge: edgeData.key,
|
||
|
|
attributes: edgeData.attributes,
|
||
|
|
source: edgeData.source.key,
|
||
|
|
target: edgeData.target.key,
|
||
|
|
sourceAttributes: edgeData.source.attributes,
|
||
|
|
targetAttributes: edgeData.target.attributes,
|
||
|
|
undirected: edgeData.undirected
|
||
|
|
};
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
return {
|
||
|
|
done: false,
|
||
|
|
value
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
let done = false;
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
if (done === true) return { done: true };
|
||
|
|
done = true;
|
||
|
|
return {
|
||
|
|
done: false,
|
||
|
|
value: {
|
||
|
|
edge: edgeData.key,
|
||
|
|
attributes: edgeData.attributes,
|
||
|
|
source: edgeData.source.key,
|
||
|
|
target: edgeData.target.key,
|
||
|
|
sourceAttributes: edgeData.source.attributes,
|
||
|
|
targetAttributes: edgeData.target.attributes,
|
||
|
|
undirected: edgeData.undirected
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function createEdgeArray(graph, type) {
|
||
|
|
if (graph.size === 0) return [];
|
||
|
|
if (type === "mixed" || type === graph.type) {
|
||
|
|
return Array.from(graph._edges.keys());
|
||
|
|
}
|
||
|
|
const size = type === "undirected" ? graph.undirectedSize : graph.directedSize;
|
||
|
|
const list = new Array(size), mask = type === "undirected";
|
||
|
|
const iterator = graph._edges.values();
|
||
|
|
let i = 0;
|
||
|
|
let step, data;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
data = step.value;
|
||
|
|
if (data.undirected === mask) list[i++] = data.key;
|
||
|
|
}
|
||
|
|
return list;
|
||
|
|
}
|
||
|
|
function forEachEdge(breakable, graph, type, callback) {
|
||
|
|
if (graph.size === 0) return;
|
||
|
|
const shouldFilter = type !== "mixed" && type !== graph.type;
|
||
|
|
const mask = type === "undirected";
|
||
|
|
let step, data;
|
||
|
|
let shouldBreak = false;
|
||
|
|
const iterator = graph._edges.values();
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
data = step.value;
|
||
|
|
if (shouldFilter && data.undirected !== mask) continue;
|
||
|
|
const { key, attributes, source, target } = data;
|
||
|
|
shouldBreak = callback(
|
||
|
|
key,
|
||
|
|
attributes,
|
||
|
|
source.key,
|
||
|
|
target.key,
|
||
|
|
source.attributes,
|
||
|
|
target.attributes,
|
||
|
|
data.undirected
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return key;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function createEdgeIterator(graph, type) {
|
||
|
|
if (graph.size === 0) return emptyIterator();
|
||
|
|
const shouldFilter = type !== "mixed" && type !== graph.type;
|
||
|
|
const mask = type === "undirected";
|
||
|
|
const iterator = graph._edges.values();
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
let step, data;
|
||
|
|
while (true) {
|
||
|
|
step = iterator.next();
|
||
|
|
if (step.done) return step;
|
||
|
|
data = step.value;
|
||
|
|
if (shouldFilter && data.undirected !== mask) continue;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const value = {
|
||
|
|
edge: data.key,
|
||
|
|
attributes: data.attributes,
|
||
|
|
source: data.source.key,
|
||
|
|
target: data.target.key,
|
||
|
|
sourceAttributes: data.source.attributes,
|
||
|
|
targetAttributes: data.target.attributes,
|
||
|
|
undirected: data.undirected
|
||
|
|
};
|
||
|
|
return { value, done: false };
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function forEachEdgeForNode(breakable, multi, type, direction, nodeData, callback) {
|
||
|
|
const fn = multi ? forEachMulti : forEachSimple;
|
||
|
|
let found;
|
||
|
|
if (type !== "undirected") {
|
||
|
|
if (direction !== "out") {
|
||
|
|
found = fn(breakable, nodeData.in, callback);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
if (direction !== "in") {
|
||
|
|
found = fn(
|
||
|
|
breakable,
|
||
|
|
nodeData.out,
|
||
|
|
callback,
|
||
|
|
!direction ? nodeData.key : void 0
|
||
|
|
);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (type !== "directed") {
|
||
|
|
found = fn(breakable, nodeData.undirected, callback);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function createEdgeArrayForNode(multi, type, direction, nodeData) {
|
||
|
|
const edges = [];
|
||
|
|
forEachEdgeForNode(false, multi, type, direction, nodeData, function(key) {
|
||
|
|
edges.push(key);
|
||
|
|
});
|
||
|
|
return edges;
|
||
|
|
}
|
||
|
|
function createEdgeIteratorForNode(type, direction, nodeData) {
|
||
|
|
let iterator = emptyIterator();
|
||
|
|
if (type !== "undirected") {
|
||
|
|
if (direction !== "out" && typeof nodeData.in !== "undefined")
|
||
|
|
iterator = chain(iterator, createIterator(nodeData.in));
|
||
|
|
if (direction !== "in" && typeof nodeData.out !== "undefined")
|
||
|
|
iterator = chain(
|
||
|
|
iterator,
|
||
|
|
createIterator(nodeData.out, !direction ? nodeData.key : void 0)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (type !== "directed" && typeof nodeData.undirected !== "undefined") {
|
||
|
|
iterator = chain(iterator, createIterator(nodeData.undirected));
|
||
|
|
}
|
||
|
|
return iterator;
|
||
|
|
}
|
||
|
|
function forEachEdgeForPath(breakable, type, multi, direction, sourceData, target, callback) {
|
||
|
|
const fn = multi ? forEachForKeyMulti : forEachForKeySimple;
|
||
|
|
let found;
|
||
|
|
if (type !== "undirected") {
|
||
|
|
if (typeof sourceData.in !== "undefined" && direction !== "out") {
|
||
|
|
found = fn(breakable, sourceData.in, target, callback);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
if (typeof sourceData.out !== "undefined" && direction !== "in" && (direction || sourceData.key !== target)) {
|
||
|
|
found = fn(breakable, sourceData.out, target, callback);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (type !== "directed") {
|
||
|
|
if (typeof sourceData.undirected !== "undefined") {
|
||
|
|
found = fn(breakable, sourceData.undirected, target, callback);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function createEdgeArrayForPath(type, multi, direction, sourceData, target) {
|
||
|
|
const edges = [];
|
||
|
|
forEachEdgeForPath(
|
||
|
|
false,
|
||
|
|
type,
|
||
|
|
multi,
|
||
|
|
direction,
|
||
|
|
sourceData,
|
||
|
|
target,
|
||
|
|
function(key) {
|
||
|
|
edges.push(key);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
return edges;
|
||
|
|
}
|
||
|
|
function createEdgeIteratorForPath(type, direction, sourceData, target) {
|
||
|
|
let iterator = emptyIterator();
|
||
|
|
if (type !== "undirected") {
|
||
|
|
if (typeof sourceData.in !== "undefined" && direction !== "out" && target in sourceData.in)
|
||
|
|
iterator = chain(iterator, createIteratorForKey(sourceData.in, target));
|
||
|
|
if (typeof sourceData.out !== "undefined" && direction !== "in" && target in sourceData.out && (direction || sourceData.key !== target))
|
||
|
|
iterator = chain(iterator, createIteratorForKey(sourceData.out, target));
|
||
|
|
}
|
||
|
|
if (type !== "directed") {
|
||
|
|
if (typeof sourceData.undirected !== "undefined" && target in sourceData.undirected)
|
||
|
|
iterator = chain(
|
||
|
|
iterator,
|
||
|
|
createIteratorForKey(sourceData.undirected, target)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return iterator;
|
||
|
|
}
|
||
|
|
function attachEdgeArrayCreator(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
Class.prototype[name] = function(source, target) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type)
|
||
|
|
return [];
|
||
|
|
if (!arguments.length) return createEdgeArray(this, type);
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
source = "" + source;
|
||
|
|
const nodeData = this._nodes.get(source);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${source}" node in the graph.`
|
||
|
|
);
|
||
|
|
return createEdgeArrayForNode(
|
||
|
|
this.multi,
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
return createEdgeArrayForPath(
|
||
|
|
type,
|
||
|
|
this.multi,
|
||
|
|
direction,
|
||
|
|
sourceData,
|
||
|
|
target
|
||
|
|
);
|
||
|
|
}
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${name}: too many arguments (expecting 0, 1 or 2 and got ${arguments.length}).`
|
||
|
|
);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachForEachEdge(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
const forEachName = "forEach" + name[0].toUpperCase() + name.slice(1, -1);
|
||
|
|
Class.prototype[forEachName] = function(source, target, callback) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return;
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
callback = source;
|
||
|
|
return forEachEdge(false, this, type, callback);
|
||
|
|
}
|
||
|
|
if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
callback = target;
|
||
|
|
const nodeData = this._nodes.get(source);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${forEachName}: could not find the "${source}" node in the graph.`
|
||
|
|
);
|
||
|
|
return forEachEdgeForNode(
|
||
|
|
false,
|
||
|
|
this.multi,
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (arguments.length === 3) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${forEachName}: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${forEachName}: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
return forEachEdgeForPath(
|
||
|
|
false,
|
||
|
|
type,
|
||
|
|
this.multi,
|
||
|
|
direction,
|
||
|
|
sourceData,
|
||
|
|
target,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
}
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${forEachName}: too many arguments (expecting 1, 2 or 3 and got ${arguments.length}).`
|
||
|
|
);
|
||
|
|
};
|
||
|
|
const mapName = "map" + name[0].toUpperCase() + name.slice(1);
|
||
|
|
Class.prototype[mapName] = function() {
|
||
|
|
const args = Array.prototype.slice.call(arguments);
|
||
|
|
const callback = args.pop();
|
||
|
|
let result;
|
||
|
|
if (args.length === 0) {
|
||
|
|
let length = 0;
|
||
|
|
if (type !== "directed") length += this.undirectedSize;
|
||
|
|
if (type !== "undirected") length += this.directedSize;
|
||
|
|
result = new Array(length);
|
||
|
|
let i = 0;
|
||
|
|
args.push((e, ea, s, t, sa, ta, u) => {
|
||
|
|
result[i++] = callback(e, ea, s, t, sa, ta, u);
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
result = [];
|
||
|
|
args.push((e, ea, s, t, sa, ta, u) => {
|
||
|
|
result.push(callback(e, ea, s, t, sa, ta, u));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
this[forEachName].apply(this, args);
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
const filterName = "filter" + name[0].toUpperCase() + name.slice(1);
|
||
|
|
Class.prototype[filterName] = function() {
|
||
|
|
const args = Array.prototype.slice.call(arguments);
|
||
|
|
const callback = args.pop();
|
||
|
|
const result = [];
|
||
|
|
args.push((e, ea, s, t, sa, ta, u) => {
|
||
|
|
if (callback(e, ea, s, t, sa, ta, u)) result.push(e);
|
||
|
|
});
|
||
|
|
this[forEachName].apply(this, args);
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
const reduceName = "reduce" + name[0].toUpperCase() + name.slice(1);
|
||
|
|
Class.prototype[reduceName] = function() {
|
||
|
|
let args = Array.prototype.slice.call(arguments);
|
||
|
|
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}).`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
let callback;
|
||
|
|
let initialValue;
|
||
|
|
if (args.length === 2) {
|
||
|
|
callback = args[0];
|
||
|
|
initialValue = args[1];
|
||
|
|
args = [];
|
||
|
|
} else if (args.length === 3) {
|
||
|
|
callback = args[1];
|
||
|
|
initialValue = args[2];
|
||
|
|
args = [args[0]];
|
||
|
|
} else if (args.length === 4) {
|
||
|
|
callback = args[2];
|
||
|
|
initialValue = args[3];
|
||
|
|
args = [args[0], args[1]];
|
||
|
|
}
|
||
|
|
let accumulator = initialValue;
|
||
|
|
args.push((e, ea, s, t, sa, ta, u) => {
|
||
|
|
accumulator = callback(accumulator, e, ea, s, t, sa, ta, u);
|
||
|
|
});
|
||
|
|
this[forEachName].apply(this, args);
|
||
|
|
return accumulator;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachFindEdge(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
const findEdgeName = "find" + name[0].toUpperCase() + name.slice(1, -1);
|
||
|
|
Class.prototype[findEdgeName] = function(source, target, callback) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type)
|
||
|
|
return false;
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
callback = source;
|
||
|
|
return forEachEdge(true, this, type, callback);
|
||
|
|
}
|
||
|
|
if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
callback = target;
|
||
|
|
const nodeData = this._nodes.get(source);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${findEdgeName}: could not find the "${source}" node in the graph.`
|
||
|
|
);
|
||
|
|
return forEachEdgeForNode(
|
||
|
|
true,
|
||
|
|
this.multi,
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (arguments.length === 3) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${findEdgeName}: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${findEdgeName}: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
return forEachEdgeForPath(
|
||
|
|
true,
|
||
|
|
type,
|
||
|
|
this.multi,
|
||
|
|
direction,
|
||
|
|
sourceData,
|
||
|
|
target,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
}
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${findEdgeName}: too many arguments (expecting 1, 2 or 3 and got ${arguments.length}).`
|
||
|
|
);
|
||
|
|
};
|
||
|
|
const someName = "some" + name[0].toUpperCase() + name.slice(1, -1);
|
||
|
|
Class.prototype[someName] = function() {
|
||
|
|
const args = Array.prototype.slice.call(arguments);
|
||
|
|
const callback = args.pop();
|
||
|
|
args.push((e, ea, s, t, sa, ta, u) => {
|
||
|
|
return callback(e, ea, s, t, sa, ta, u);
|
||
|
|
});
|
||
|
|
const found = this[findEdgeName].apply(this, args);
|
||
|
|
if (found) return true;
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
const everyName = "every" + name[0].toUpperCase() + name.slice(1, -1);
|
||
|
|
Class.prototype[everyName] = function() {
|
||
|
|
const args = Array.prototype.slice.call(arguments);
|
||
|
|
const callback = args.pop();
|
||
|
|
args.push((e, ea, s, t, sa, ta, u) => {
|
||
|
|
return !callback(e, ea, s, t, sa, ta, u);
|
||
|
|
});
|
||
|
|
const found = this[findEdgeName].apply(this, args);
|
||
|
|
if (found) return false;
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeIteratorCreator(Class, description) {
|
||
|
|
const { name: originalName, type, direction } = description;
|
||
|
|
const name = originalName.slice(0, -1) + "Entries";
|
||
|
|
Class.prototype[name] = function(source, target) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type)
|
||
|
|
return emptyIterator();
|
||
|
|
if (!arguments.length) return createEdgeIterator(this, type);
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
source = "" + source;
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${source}" node in the graph.`
|
||
|
|
);
|
||
|
|
return createEdgeIteratorForNode(type, direction, sourceData);
|
||
|
|
}
|
||
|
|
if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
return createEdgeIteratorForPath(type, direction, sourceData, target);
|
||
|
|
}
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${name}: too many arguments (expecting 0, 1 or 2 and got ${arguments.length}).`
|
||
|
|
);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachEdgeIterationMethods(Graph2) {
|
||
|
|
EDGES_ITERATION.forEach((description) => {
|
||
|
|
attachEdgeArrayCreator(Graph2, description);
|
||
|
|
attachForEachEdge(Graph2, description);
|
||
|
|
attachFindEdge(Graph2, description);
|
||
|
|
attachEdgeIteratorCreator(Graph2, description);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
var NEIGHBORS_ITERATION = [
|
||
|
|
{
|
||
|
|
name: "neighbors",
|
||
|
|
type: "mixed"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "inNeighbors",
|
||
|
|
type: "directed",
|
||
|
|
direction: "in"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "outNeighbors",
|
||
|
|
type: "directed",
|
||
|
|
direction: "out"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "inboundNeighbors",
|
||
|
|
type: "mixed",
|
||
|
|
direction: "in"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "outboundNeighbors",
|
||
|
|
type: "mixed",
|
||
|
|
direction: "out"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "directedNeighbors",
|
||
|
|
type: "directed"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "undirectedNeighbors",
|
||
|
|
type: "undirected"
|
||
|
|
}
|
||
|
|
];
|
||
|
|
function CompositeSetWrapper() {
|
||
|
|
this.A = null;
|
||
|
|
this.B = null;
|
||
|
|
}
|
||
|
|
CompositeSetWrapper.prototype.wrap = function(set) {
|
||
|
|
if (this.A === null) this.A = set;
|
||
|
|
else if (this.B === null) this.B = set;
|
||
|
|
};
|
||
|
|
CompositeSetWrapper.prototype.has = function(key) {
|
||
|
|
if (this.A !== null && key in this.A) return true;
|
||
|
|
if (this.B !== null && key in this.B) return true;
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
function forEachInObjectOnce(breakable, visited, nodeData, object, callback) {
|
||
|
|
for (const k in object) {
|
||
|
|
const edgeData = object[k];
|
||
|
|
const sourceData = edgeData.source;
|
||
|
|
const targetData = edgeData.target;
|
||
|
|
const neighborData = sourceData === nodeData ? targetData : sourceData;
|
||
|
|
if (visited && visited.has(neighborData.key)) continue;
|
||
|
|
const shouldBreak = callback(neighborData.key, neighborData.attributes);
|
||
|
|
if (breakable && shouldBreak) return neighborData.key;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function forEachNeighbor(breakable, type, direction, nodeData, callback) {
|
||
|
|
if (type !== "mixed") {
|
||
|
|
if (type === "undirected")
|
||
|
|
return forEachInObjectOnce(
|
||
|
|
breakable,
|
||
|
|
null,
|
||
|
|
nodeData,
|
||
|
|
nodeData.undirected,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
if (typeof direction === "string")
|
||
|
|
return forEachInObjectOnce(
|
||
|
|
breakable,
|
||
|
|
null,
|
||
|
|
nodeData,
|
||
|
|
nodeData[direction],
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const visited = new CompositeSetWrapper();
|
||
|
|
let found;
|
||
|
|
if (type !== "undirected") {
|
||
|
|
if (direction !== "out") {
|
||
|
|
found = forEachInObjectOnce(
|
||
|
|
breakable,
|
||
|
|
null,
|
||
|
|
nodeData,
|
||
|
|
nodeData.in,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
visited.wrap(nodeData.in);
|
||
|
|
}
|
||
|
|
if (direction !== "in") {
|
||
|
|
found = forEachInObjectOnce(
|
||
|
|
breakable,
|
||
|
|
visited,
|
||
|
|
nodeData,
|
||
|
|
nodeData.out,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
visited.wrap(nodeData.out);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (type !== "directed") {
|
||
|
|
found = forEachInObjectOnce(
|
||
|
|
breakable,
|
||
|
|
visited,
|
||
|
|
nodeData,
|
||
|
|
nodeData.undirected,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
if (breakable && found) return found;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function createNeighborArrayForNode(type, direction, nodeData) {
|
||
|
|
if (type !== "mixed") {
|
||
|
|
if (type === "undirected") return Object.keys(nodeData.undirected);
|
||
|
|
if (typeof direction === "string") return Object.keys(nodeData[direction]);
|
||
|
|
}
|
||
|
|
const neighbors = [];
|
||
|
|
forEachNeighbor(false, type, direction, nodeData, function(key) {
|
||
|
|
neighbors.push(key);
|
||
|
|
});
|
||
|
|
return neighbors;
|
||
|
|
}
|
||
|
|
function createDedupedObjectIterator(visited, nodeData, object) {
|
||
|
|
const keys = Object.keys(object);
|
||
|
|
const l = keys.length;
|
||
|
|
let i = 0;
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
let neighborData = null;
|
||
|
|
do {
|
||
|
|
if (i >= l) {
|
||
|
|
if (visited) visited.wrap(object);
|
||
|
|
return { done: true };
|
||
|
|
}
|
||
|
|
const edgeData = object[keys[i++]];
|
||
|
|
const sourceData = edgeData.source;
|
||
|
|
const targetData = edgeData.target;
|
||
|
|
neighborData = sourceData === nodeData ? targetData : sourceData;
|
||
|
|
if (visited && visited.has(neighborData.key)) {
|
||
|
|
neighborData = null;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
} while (neighborData === null);
|
||
|
|
return {
|
||
|
|
done: false,
|
||
|
|
value: { neighbor: neighborData.key, attributes: neighborData.attributes }
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function createNeighborIterator(type, direction, nodeData) {
|
||
|
|
if (type !== "mixed") {
|
||
|
|
if (type === "undirected")
|
||
|
|
return createDedupedObjectIterator(null, nodeData, nodeData.undirected);
|
||
|
|
if (typeof direction === "string")
|
||
|
|
return createDedupedObjectIterator(null, nodeData, nodeData[direction]);
|
||
|
|
}
|
||
|
|
let iterator = emptyIterator();
|
||
|
|
const visited = new CompositeSetWrapper();
|
||
|
|
if (type !== "undirected") {
|
||
|
|
if (direction !== "out") {
|
||
|
|
iterator = chain(
|
||
|
|
iterator,
|
||
|
|
createDedupedObjectIterator(visited, nodeData, nodeData.in)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if (direction !== "in") {
|
||
|
|
iterator = chain(
|
||
|
|
iterator,
|
||
|
|
createDedupedObjectIterator(visited, nodeData, nodeData.out)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (type !== "directed") {
|
||
|
|
iterator = chain(
|
||
|
|
iterator,
|
||
|
|
createDedupedObjectIterator(visited, nodeData, nodeData.undirected)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return iterator;
|
||
|
|
}
|
||
|
|
function attachNeighborArrayCreator(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
Class.prototype[name] = function(node) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type)
|
||
|
|
return [];
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
return createNeighborArrayForNode(
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData
|
||
|
|
);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachForEachNeighbor(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
const forEachName = "forEach" + name[0].toUpperCase() + name.slice(1, -1);
|
||
|
|
Class.prototype[forEachName] = function(node, callback) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return;
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${forEachName}: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
forEachNeighbor(
|
||
|
|
false,
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
};
|
||
|
|
const mapName = "map" + name[0].toUpperCase() + name.slice(1);
|
||
|
|
Class.prototype[mapName] = function(node, callback) {
|
||
|
|
const result = [];
|
||
|
|
this[forEachName](node, (n, a) => {
|
||
|
|
result.push(callback(n, a));
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
const filterName = "filter" + name[0].toUpperCase() + name.slice(1);
|
||
|
|
Class.prototype[filterName] = function(node, callback) {
|
||
|
|
const result = [];
|
||
|
|
this[forEachName](node, (n, a) => {
|
||
|
|
if (callback(n, a)) result.push(n);
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
const reduceName = "reduce" + name[0].toUpperCase() + name.slice(1);
|
||
|
|
Class.prototype[reduceName] = function(node, callback, initialValue) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
let accumulator = initialValue;
|
||
|
|
this[forEachName](node, (n, a) => {
|
||
|
|
accumulator = callback(accumulator, n, a);
|
||
|
|
});
|
||
|
|
return accumulator;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachFindNeighbor(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
const capitalizedSingular = name[0].toUpperCase() + name.slice(1, -1);
|
||
|
|
const findName = "find" + capitalizedSingular;
|
||
|
|
Class.prototype[findName] = function(node, callback) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type) return;
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${findName}: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
return forEachNeighbor(
|
||
|
|
true,
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData,
|
||
|
|
callback
|
||
|
|
);
|
||
|
|
};
|
||
|
|
const someName = "some" + capitalizedSingular;
|
||
|
|
Class.prototype[someName] = function(node, callback) {
|
||
|
|
const found = this[findName](node, callback);
|
||
|
|
if (found) return true;
|
||
|
|
return false;
|
||
|
|
};
|
||
|
|
const everyName = "every" + capitalizedSingular;
|
||
|
|
Class.prototype[everyName] = function(node, callback) {
|
||
|
|
const found = this[findName](node, (n, a) => {
|
||
|
|
return !callback(n, a);
|
||
|
|
});
|
||
|
|
if (found) return false;
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNeighborIteratorCreator(Class, description) {
|
||
|
|
const { name, type, direction } = description;
|
||
|
|
const iteratorName = name.slice(0, -1) + "Entries";
|
||
|
|
Class.prototype[iteratorName] = function(node) {
|
||
|
|
if (type !== "mixed" && this.type !== "mixed" && type !== this.type)
|
||
|
|
return emptyIterator();
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (typeof nodeData === "undefined")
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${iteratorName}: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
return createNeighborIterator(
|
||
|
|
type === "mixed" ? this.type : type,
|
||
|
|
direction,
|
||
|
|
nodeData
|
||
|
|
);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function attachNeighborIterationMethods(Graph2) {
|
||
|
|
NEIGHBORS_ITERATION.forEach((description) => {
|
||
|
|
attachNeighborArrayCreator(Graph2, description);
|
||
|
|
attachForEachNeighbor(Graph2, description);
|
||
|
|
attachFindNeighbor(Graph2, description);
|
||
|
|
attachNeighborIteratorCreator(Graph2, description);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function forEachAdjacency(breakable, assymetric, disconnectedNodes, graph, callback) {
|
||
|
|
const iterator = graph._nodes.values();
|
||
|
|
const type = graph.type;
|
||
|
|
let step, sourceData, neighbor, adj, edgeData, targetData, shouldBreak;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
let hasEdges = false;
|
||
|
|
sourceData = step.value;
|
||
|
|
if (type !== "undirected") {
|
||
|
|
adj = sourceData.out;
|
||
|
|
for (neighbor in adj) {
|
||
|
|
edgeData = adj[neighbor];
|
||
|
|
do {
|
||
|
|
targetData = edgeData.target;
|
||
|
|
hasEdges = true;
|
||
|
|
shouldBreak = callback(
|
||
|
|
sourceData.key,
|
||
|
|
targetData.key,
|
||
|
|
sourceData.attributes,
|
||
|
|
targetData.attributes,
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return edgeData;
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (type !== "directed") {
|
||
|
|
adj = sourceData.undirected;
|
||
|
|
for (neighbor in adj) {
|
||
|
|
if (assymetric && sourceData.key > neighbor) continue;
|
||
|
|
edgeData = adj[neighbor];
|
||
|
|
do {
|
||
|
|
targetData = edgeData.target;
|
||
|
|
if (targetData.key !== neighbor) targetData = edgeData.source;
|
||
|
|
hasEdges = true;
|
||
|
|
shouldBreak = callback(
|
||
|
|
sourceData.key,
|
||
|
|
targetData.key,
|
||
|
|
sourceData.attributes,
|
||
|
|
targetData.attributes,
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return edgeData;
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (disconnectedNodes && !hasEdges) {
|
||
|
|
shouldBreak = callback(
|
||
|
|
sourceData.key,
|
||
|
|
null,
|
||
|
|
sourceData.attributes,
|
||
|
|
null,
|
||
|
|
null,
|
||
|
|
null,
|
||
|
|
null
|
||
|
|
);
|
||
|
|
if (breakable && shouldBreak) return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
function serializeNode(key, data) {
|
||
|
|
const serialized = { key };
|
||
|
|
if (!isEmpty(data.attributes))
|
||
|
|
serialized.attributes = assign({}, data.attributes);
|
||
|
|
return serialized;
|
||
|
|
}
|
||
|
|
function serializeEdge(type, key, data) {
|
||
|
|
const serialized = {
|
||
|
|
key,
|
||
|
|
source: data.source.key,
|
||
|
|
target: data.target.key
|
||
|
|
};
|
||
|
|
if (!isEmpty(data.attributes))
|
||
|
|
serialized.attributes = assign({}, data.attributes);
|
||
|
|
if (type === "mixed" && data.undirected) serialized.undirected = true;
|
||
|
|
return serialized;
|
||
|
|
}
|
||
|
|
function validateSerializedNode(value) {
|
||
|
|
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.'
|
||
|
|
);
|
||
|
|
if (!("key" in value))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: serialized node is missing its key."
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
}
|
||
|
|
function validateSerializedEdge(value) {
|
||
|
|
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.'
|
||
|
|
);
|
||
|
|
if (!("source" in value))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: serialized edge is missing its source."
|
||
|
|
);
|
||
|
|
if (!("target" in value))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: serialized edge is missing its target."
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
if ("undirected" in value && typeof value.undirected !== "boolean")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: invalid undirectedness information. Undirected should be boolean or omitted."
|
||
|
|
);
|
||
|
|
}
|
||
|
|
var INSTANCE_ID = incrementalIdStartingFromRandomByte();
|
||
|
|
var TYPES = /* @__PURE__ */ new Set(["directed", "undirected", "mixed"]);
|
||
|
|
var EMITTER_PROPS = /* @__PURE__ */ new Set([
|
||
|
|
"domain",
|
||
|
|
"_events",
|
||
|
|
"_eventsCount",
|
||
|
|
"_maxListeners"
|
||
|
|
]);
|
||
|
|
var EDGE_ADD_METHODS = [
|
||
|
|
{
|
||
|
|
name: (verb) => `${verb}Edge`,
|
||
|
|
generateKey: true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (verb) => `${verb}DirectedEdge`,
|
||
|
|
generateKey: true,
|
||
|
|
type: "directed"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (verb) => `${verb}UndirectedEdge`,
|
||
|
|
generateKey: true,
|
||
|
|
type: "undirected"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (verb) => `${verb}EdgeWithKey`
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (verb) => `${verb}DirectedEdgeWithKey`,
|
||
|
|
type: "directed"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: (verb) => `${verb}UndirectedEdgeWithKey`,
|
||
|
|
type: "undirected"
|
||
|
|
}
|
||
|
|
];
|
||
|
|
var DEFAULTS = {
|
||
|
|
allowSelfLoops: true,
|
||
|
|
multi: false,
|
||
|
|
type: "mixed"
|
||
|
|
};
|
||
|
|
function addNode(graph, node, attributes) {
|
||
|
|
if (attributes && !isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.addNode: invalid attributes. Expecting an object but got "${attributes}"`
|
||
|
|
);
|
||
|
|
node = "" + node;
|
||
|
|
attributes = attributes || {};
|
||
|
|
if (graph._nodes.has(node))
|
||
|
|
throw new UsageGraphError(
|
||
|
|
`Graph.addNode: the "${node}" node already exist in the graph.`
|
||
|
|
);
|
||
|
|
const data = new graph.NodeDataClass(node, attributes);
|
||
|
|
graph._nodes.set(node, data);
|
||
|
|
graph.emit("nodeAdded", {
|
||
|
|
key: node,
|
||
|
|
attributes
|
||
|
|
});
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
function unsafeAddNode(graph, node, attributes) {
|
||
|
|
const data = new graph.NodeDataClass(node, attributes);
|
||
|
|
graph._nodes.set(node, data);
|
||
|
|
graph.emit("nodeAdded", {
|
||
|
|
key: node,
|
||
|
|
attributes
|
||
|
|
});
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
function addEdge(graph, name, mustGenerateKey, undirected, edge, source, target, attributes) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (attributes && !isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${name}: invalid attributes. Expecting an object but got "${attributes}"`
|
||
|
|
);
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
attributes = attributes || {};
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
const sourceData = graph._nodes.get(source), targetData = graph._nodes.get(target);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: source node "${source}" not found.`
|
||
|
|
);
|
||
|
|
if (!targetData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.${name}: target node "${target}" not found.`
|
||
|
|
);
|
||
|
|
const eventData = {
|
||
|
|
key: null,
|
||
|
|
undirected,
|
||
|
|
source,
|
||
|
|
target,
|
||
|
|
attributes
|
||
|
|
};
|
||
|
|
if (mustGenerateKey) {
|
||
|
|
edge = graph._edgeKeyGenerator();
|
||
|
|
} else {
|
||
|
|
edge = "" + edge;
|
||
|
|
if (graph._edges.has(edge))
|
||
|
|
throw new UsageGraphError(
|
||
|
|
`Graph.${name}: the "${edge}" edge already exists in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
const edgeData = new EdgeData(
|
||
|
|
undirected,
|
||
|
|
edge,
|
||
|
|
sourceData,
|
||
|
|
targetData,
|
||
|
|
attributes
|
||
|
|
);
|
||
|
|
graph._edges.set(edge, edgeData);
|
||
|
|
const isSelfLoop = source === target;
|
||
|
|
if (undirected) {
|
||
|
|
sourceData.undirectedDegree++;
|
||
|
|
targetData.undirectedDegree++;
|
||
|
|
if (isSelfLoop) {
|
||
|
|
sourceData.undirectedLoops++;
|
||
|
|
graph._undirectedSelfLoopCount++;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
sourceData.outDegree++;
|
||
|
|
targetData.inDegree++;
|
||
|
|
if (isSelfLoop) {
|
||
|
|
sourceData.directedLoops++;
|
||
|
|
graph._directedSelfLoopCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (graph.multi) edgeData.attachMulti();
|
||
|
|
else edgeData.attach();
|
||
|
|
if (undirected) graph._undirectedSize++;
|
||
|
|
else graph._directedSize++;
|
||
|
|
eventData.key = edge;
|
||
|
|
graph.emit("edgeAdded", eventData);
|
||
|
|
return edge;
|
||
|
|
}
|
||
|
|
function mergeEdge(graph, name, mustGenerateKey, undirected, edge, source, target, attributes, asUpdater) {
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
if (attributes) {
|
||
|
|
if (asUpdater) {
|
||
|
|
if (typeof attributes !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${name}: invalid updater function. Expecting a function but got "${attributes}"`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.${name}: invalid attributes. Expecting an object but got "${attributes}"`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
let updater;
|
||
|
|
if (asUpdater) {
|
||
|
|
updater = attributes;
|
||
|
|
attributes = void 0;
|
||
|
|
}
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
let sourceData = graph._nodes.get(source);
|
||
|
|
let targetData = graph._nodes.get(target);
|
||
|
|
let edgeData;
|
||
|
|
let alreadyExistingEdgeData;
|
||
|
|
if (!mustGenerateKey) {
|
||
|
|
edgeData = graph._edges.get(edge);
|
||
|
|
if (edgeData) {
|
||
|
|
if (edgeData.source.key !== source || edgeData.target.key !== target) {
|
||
|
|
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}").`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
alreadyExistingEdgeData = edgeData;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!alreadyExistingEdgeData && !graph.multi && sourceData) {
|
||
|
|
alreadyExistingEdgeData = undirected ? sourceData.undirected[target] : sourceData.out[target];
|
||
|
|
}
|
||
|
|
if (alreadyExistingEdgeData) {
|
||
|
|
const info = [alreadyExistingEdgeData.key, false, false, false];
|
||
|
|
if (asUpdater ? !updater : !attributes) return info;
|
||
|
|
if (asUpdater) {
|
||
|
|
const oldAttributes = alreadyExistingEdgeData.attributes;
|
||
|
|
alreadyExistingEdgeData.attributes = updater(oldAttributes);
|
||
|
|
graph.emit("edgeAttributesUpdated", {
|
||
|
|
type: "replace",
|
||
|
|
key: alreadyExistingEdgeData.key,
|
||
|
|
attributes: alreadyExistingEdgeData.attributes
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
assign(alreadyExistingEdgeData.attributes, attributes);
|
||
|
|
graph.emit("edgeAttributesUpdated", {
|
||
|
|
type: "merge",
|
||
|
|
key: alreadyExistingEdgeData.key,
|
||
|
|
attributes: alreadyExistingEdgeData.attributes,
|
||
|
|
data: attributes
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return info;
|
||
|
|
}
|
||
|
|
attributes = attributes || {};
|
||
|
|
if (asUpdater && updater) attributes = updater(attributes);
|
||
|
|
const eventData = {
|
||
|
|
key: null,
|
||
|
|
undirected,
|
||
|
|
source,
|
||
|
|
target,
|
||
|
|
attributes
|
||
|
|
};
|
||
|
|
if (mustGenerateKey) {
|
||
|
|
edge = graph._edgeKeyGenerator();
|
||
|
|
} else {
|
||
|
|
edge = "" + edge;
|
||
|
|
if (graph._edges.has(edge))
|
||
|
|
throw new UsageGraphError(
|
||
|
|
`Graph.${name}: the "${edge}" edge already exists in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
let sourceWasAdded = false;
|
||
|
|
let targetWasAdded = false;
|
||
|
|
if (!sourceData) {
|
||
|
|
sourceData = unsafeAddNode(graph, source, {});
|
||
|
|
sourceWasAdded = true;
|
||
|
|
if (source === target) {
|
||
|
|
targetData = sourceData;
|
||
|
|
targetWasAdded = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!targetData) {
|
||
|
|
targetData = unsafeAddNode(graph, target, {});
|
||
|
|
targetWasAdded = true;
|
||
|
|
}
|
||
|
|
edgeData = new EdgeData(undirected, edge, sourceData, targetData, attributes);
|
||
|
|
graph._edges.set(edge, edgeData);
|
||
|
|
const isSelfLoop = source === target;
|
||
|
|
if (undirected) {
|
||
|
|
sourceData.undirectedDegree++;
|
||
|
|
targetData.undirectedDegree++;
|
||
|
|
if (isSelfLoop) {
|
||
|
|
sourceData.undirectedLoops++;
|
||
|
|
graph._undirectedSelfLoopCount++;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
sourceData.outDegree++;
|
||
|
|
targetData.inDegree++;
|
||
|
|
if (isSelfLoop) {
|
||
|
|
sourceData.directedLoops++;
|
||
|
|
graph._directedSelfLoopCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (graph.multi) edgeData.attachMulti();
|
||
|
|
else edgeData.attach();
|
||
|
|
if (undirected) graph._undirectedSize++;
|
||
|
|
else graph._directedSize++;
|
||
|
|
eventData.key = edge;
|
||
|
|
graph.emit("edgeAdded", eventData);
|
||
|
|
return [edge, true, sourceWasAdded, targetWasAdded];
|
||
|
|
}
|
||
|
|
function dropEdgeFromData(graph, edgeData) {
|
||
|
|
graph._edges.delete(edgeData.key);
|
||
|
|
const { source: sourceData, target: targetData, attributes } = edgeData;
|
||
|
|
const undirected = edgeData.undirected;
|
||
|
|
const isSelfLoop = sourceData === targetData;
|
||
|
|
if (undirected) {
|
||
|
|
sourceData.undirectedDegree--;
|
||
|
|
targetData.undirectedDegree--;
|
||
|
|
if (isSelfLoop) {
|
||
|
|
sourceData.undirectedLoops--;
|
||
|
|
graph._undirectedSelfLoopCount--;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
sourceData.outDegree--;
|
||
|
|
targetData.inDegree--;
|
||
|
|
if (isSelfLoop) {
|
||
|
|
sourceData.directedLoops--;
|
||
|
|
graph._directedSelfLoopCount--;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (graph.multi) edgeData.detachMulti();
|
||
|
|
else edgeData.detach();
|
||
|
|
if (undirected) graph._undirectedSize--;
|
||
|
|
else graph._directedSize--;
|
||
|
|
graph.emit("edgeDropped", {
|
||
|
|
key: edgeData.key,
|
||
|
|
attributes,
|
||
|
|
source: sourceData.key,
|
||
|
|
target: targetData.key,
|
||
|
|
undirected
|
||
|
|
});
|
||
|
|
}
|
||
|
|
var Graph = class _Graph extends import_events.EventEmitter {
|
||
|
|
constructor(options) {
|
||
|
|
super();
|
||
|
|
options = assign({}, DEFAULTS, options);
|
||
|
|
if (typeof options.multi !== "boolean")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.constructor: invalid 'multi' option. Expecting a boolean but got "${options.multi}".`
|
||
|
|
);
|
||
|
|
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}".`
|
||
|
|
);
|
||
|
|
if (typeof options.allowSelfLoops !== "boolean")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.constructor: invalid 'allowSelfLoops' option. Expecting a boolean but got "${options.allowSelfLoops}".`
|
||
|
|
);
|
||
|
|
const NodeDataClass = options.type === "mixed" ? MixedNodeData : options.type === "directed" ? DirectedNodeData : UndirectedNodeData;
|
||
|
|
privateProperty(this, "NodeDataClass", NodeDataClass);
|
||
|
|
const instancePrefix = "geid_" + INSTANCE_ID() + "_";
|
||
|
|
let edgeId = 0;
|
||
|
|
const edgeKeyGenerator = () => {
|
||
|
|
let availableEdgeKey;
|
||
|
|
do {
|
||
|
|
availableEdgeKey = instancePrefix + edgeId++;
|
||
|
|
} while (this._edges.has(availableEdgeKey));
|
||
|
|
return availableEdgeKey;
|
||
|
|
};
|
||
|
|
privateProperty(this, "_attributes", {});
|
||
|
|
privateProperty(this, "_nodes", /* @__PURE__ */ new Map());
|
||
|
|
privateProperty(this, "_edges", /* @__PURE__ */ new Map());
|
||
|
|
privateProperty(this, "_directedSize", 0);
|
||
|
|
privateProperty(this, "_undirectedSize", 0);
|
||
|
|
privateProperty(this, "_directedSelfLoopCount", 0);
|
||
|
|
privateProperty(this, "_undirectedSelfLoopCount", 0);
|
||
|
|
privateProperty(this, "_edgeKeyGenerator", edgeKeyGenerator);
|
||
|
|
privateProperty(this, "_options", options);
|
||
|
|
EMITTER_PROPS.forEach((prop) => privateProperty(this, prop, this[prop]));
|
||
|
|
readOnlyProperty(this, "order", () => this._nodes.size);
|
||
|
|
readOnlyProperty(this, "size", () => this._edges.size);
|
||
|
|
readOnlyProperty(this, "directedSize", () => this._directedSize);
|
||
|
|
readOnlyProperty(this, "undirectedSize", () => this._undirectedSize);
|
||
|
|
readOnlyProperty(
|
||
|
|
this,
|
||
|
|
"selfLoopCount",
|
||
|
|
() => this._directedSelfLoopCount + this._undirectedSelfLoopCount
|
||
|
|
);
|
||
|
|
readOnlyProperty(
|
||
|
|
this,
|
||
|
|
"directedSelfLoopCount",
|
||
|
|
() => this._directedSelfLoopCount
|
||
|
|
);
|
||
|
|
readOnlyProperty(
|
||
|
|
this,
|
||
|
|
"undirectedSelfLoopCount",
|
||
|
|
() => this._undirectedSelfLoopCount
|
||
|
|
);
|
||
|
|
readOnlyProperty(this, "multi", this._options.multi);
|
||
|
|
readOnlyProperty(this, "type", this._options.type);
|
||
|
|
readOnlyProperty(this, "allowSelfLoops", this._options.allowSelfLoops);
|
||
|
|
readOnlyProperty(this, "implementation", () => "graphology");
|
||
|
|
}
|
||
|
|
_resetInstanceCounters() {
|
||
|
|
this._directedSize = 0;
|
||
|
|
this._undirectedSize = 0;
|
||
|
|
this._directedSelfLoopCount = 0;
|
||
|
|
this._undirectedSelfLoopCount = 0;
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Read
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method returning whether the given node is found in the graph.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node.
|
||
|
|
* @return {boolean}
|
||
|
|
*/
|
||
|
|
hasNode(node) {
|
||
|
|
return this._nodes.has("" + node);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the given directed edge is found in the graph.
|
||
|
|
*
|
||
|
|
* Arity 1:
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
*
|
||
|
|
* Arity 2:
|
||
|
|
* @param {any} source - The edge's source.
|
||
|
|
* @param {any} target - The edge's target.
|
||
|
|
*
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the arguments are invalid.
|
||
|
|
*/
|
||
|
|
hasDirectedEdge(source, target) {
|
||
|
|
if (this.type === "undirected") return false;
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
const edge = "" + source;
|
||
|
|
const edgeData = this._edges.get(edge);
|
||
|
|
return !!edgeData && !edgeData.undirected;
|
||
|
|
} else if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const nodeData = this._nodes.get(source);
|
||
|
|
if (!nodeData) return false;
|
||
|
|
return nodeData.out.hasOwnProperty(target);
|
||
|
|
}
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the given undirected edge is found in the graph.
|
||
|
|
*
|
||
|
|
* Arity 1:
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
*
|
||
|
|
* Arity 2:
|
||
|
|
* @param {any} source - The edge's source.
|
||
|
|
* @param {any} target - The edge's target.
|
||
|
|
*
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the arguments are invalid.
|
||
|
|
*/
|
||
|
|
hasUndirectedEdge(source, target) {
|
||
|
|
if (this.type === "directed") return false;
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
const edge = "" + source;
|
||
|
|
const edgeData = this._edges.get(edge);
|
||
|
|
return !!edgeData && edgeData.undirected;
|
||
|
|
} else if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const nodeData = this._nodes.get(source);
|
||
|
|
if (!nodeData) return false;
|
||
|
|
return nodeData.undirected.hasOwnProperty(target);
|
||
|
|
}
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the given edge is found in the graph.
|
||
|
|
*
|
||
|
|
* Arity 1:
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
*
|
||
|
|
* Arity 2:
|
||
|
|
* @param {any} source - The edge's source.
|
||
|
|
* @param {any} target - The edge's target.
|
||
|
|
*
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the arguments are invalid.
|
||
|
|
*/
|
||
|
|
hasEdge(source, target) {
|
||
|
|
if (arguments.length === 1) {
|
||
|
|
const edge = "" + source;
|
||
|
|
return this._edges.has(edge);
|
||
|
|
} else if (arguments.length === 2) {
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const nodeData = this._nodes.get(source);
|
||
|
|
if (!nodeData) return false;
|
||
|
|
return typeof nodeData.out !== "undefined" && nodeData.out.hasOwnProperty(target) || typeof nodeData.undirected !== "undefined" && nodeData.undirected.hasOwnProperty(target);
|
||
|
|
}
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the edge matching source & target in a directed fashion.
|
||
|
|
*
|
||
|
|
* @param {any} source - The edge's source.
|
||
|
|
* @param {any} target - The edge's target.
|
||
|
|
*
|
||
|
|
* @return {any|undefined}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the graph is multi.
|
||
|
|
* @throws {Error} - Will throw if source or target doesn't exist.
|
||
|
|
*/
|
||
|
|
directedEdge(source, target) {
|
||
|
|
if (this.type === "undirected") return;
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.directedEdge: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.directedEdge: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
const edgeData = sourceData.out && sourceData.out[target] || void 0;
|
||
|
|
if (edgeData) return edgeData.key;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the edge matching source & target in a undirected fashion.
|
||
|
|
*
|
||
|
|
* @param {any} source - The edge's source.
|
||
|
|
* @param {any} target - The edge's target.
|
||
|
|
*
|
||
|
|
* @return {any|undefined}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the graph is multi.
|
||
|
|
* @throws {Error} - Will throw if source or target doesn't exist.
|
||
|
|
*/
|
||
|
|
undirectedEdge(source, target) {
|
||
|
|
if (this.type === "directed") return;
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.undirectedEdge: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.undirectedEdge: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
const edgeData = sourceData.undirected && sourceData.undirected[target] || void 0;
|
||
|
|
if (edgeData) return edgeData.key;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the edge matching source & target in a mixed fashion.
|
||
|
|
*
|
||
|
|
* @param {any} source - The edge's source.
|
||
|
|
* @param {any} target - The edge's target.
|
||
|
|
*
|
||
|
|
* @return {any|undefined}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the graph is multi.
|
||
|
|
* @throws {Error} - Will throw if source or target doesn't exist.
|
||
|
|
*/
|
||
|
|
edge(source, target) {
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const sourceData = this._nodes.get(source);
|
||
|
|
if (!sourceData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.edge: could not find the "${source}" source node in the graph.`
|
||
|
|
);
|
||
|
|
if (!this._nodes.has(target))
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.edge: could not find the "${target}" target node in the graph.`
|
||
|
|
);
|
||
|
|
const edgeData = sourceData.out && sourceData.out[target] || sourceData.undirected && sourceData.undirected[target] || void 0;
|
||
|
|
if (edgeData) return edgeData.key;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are directed neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areDirectedNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areDirectedNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return false;
|
||
|
|
return neighbor in nodeData.in || neighbor in nodeData.out;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are out neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areOutNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areOutNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return false;
|
||
|
|
return neighbor in nodeData.out;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are in neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areInNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areInNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return false;
|
||
|
|
return neighbor in nodeData.in;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are undirected neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areUndirectedNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areUndirectedNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "directed") return false;
|
||
|
|
return neighbor in nodeData.undirected;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
if (neighbor in nodeData.in || neighbor in nodeData.out) return true;
|
||
|
|
}
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
if (neighbor in nodeData.undirected) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are inbound neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areInboundNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areInboundNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
if (neighbor in nodeData.in) return true;
|
||
|
|
}
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
if (neighbor in nodeData.undirected) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether two nodes are outbound neighbors.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} neighbor - The neighbor's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
areOutboundNeighbors(node, neighbor) {
|
||
|
|
node = "" + node;
|
||
|
|
neighbor = "" + neighbor;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.areOutboundNeighbors: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
if (neighbor in nodeData.out) return true;
|
||
|
|
}
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
if (neighbor in nodeData.undirected) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's in degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
inDegree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.inDegree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return 0;
|
||
|
|
return nodeData.inDegree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's out degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
outDegree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.outDegree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return 0;
|
||
|
|
return nodeData.outDegree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's directed degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
directedDegree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.directedDegree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return 0;
|
||
|
|
return nodeData.inDegree + nodeData.outDegree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's undirected degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
undirectedDegree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.undirectedDegree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "directed") return 0;
|
||
|
|
return nodeData.undirectedDegree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's inbound degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's inbound degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
inboundDegree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.inboundDegree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let degree = 0;
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
degree += nodeData.undirectedDegree;
|
||
|
|
}
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
degree += nodeData.inDegree;
|
||
|
|
}
|
||
|
|
return degree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's outbound degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's outbound degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
outboundDegree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.outboundDegree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let degree = 0;
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
degree += nodeData.undirectedDegree;
|
||
|
|
}
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
degree += nodeData.outDegree;
|
||
|
|
}
|
||
|
|
return degree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's directed degree.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
degree(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.degree: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let degree = 0;
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
degree += nodeData.undirectedDegree;
|
||
|
|
}
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
degree += nodeData.inDegree + nodeData.outDegree;
|
||
|
|
}
|
||
|
|
return degree;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's in degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
inDegreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.inDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return 0;
|
||
|
|
return nodeData.inDegree - nodeData.directedLoops;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's out degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
outDegreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.outDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return 0;
|
||
|
|
return nodeData.outDegree - nodeData.directedLoops;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's directed degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
directedDegreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.directedDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "undirected") return 0;
|
||
|
|
return nodeData.inDegree + nodeData.outDegree - nodeData.directedLoops * 2;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's undirected degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's in degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
undirectedDegreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.undirectedDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
if (this.type === "directed") return 0;
|
||
|
|
return nodeData.undirectedDegree - nodeData.undirectedLoops * 2;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's inbound degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's inbound degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
inboundDegreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.inboundDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let degree = 0;
|
||
|
|
let loops = 0;
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
degree += nodeData.undirectedDegree;
|
||
|
|
loops += nodeData.undirectedLoops * 2;
|
||
|
|
}
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
degree += nodeData.inDegree;
|
||
|
|
loops += nodeData.directedLoops;
|
||
|
|
}
|
||
|
|
return degree - loops;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's outbound degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's outbound degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
outboundDegreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.outboundDegreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let degree = 0;
|
||
|
|
let loops = 0;
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
degree += nodeData.undirectedDegree;
|
||
|
|
loops += nodeData.undirectedLoops * 2;
|
||
|
|
}
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
degree += nodeData.outDegree;
|
||
|
|
loops += nodeData.directedLoops;
|
||
|
|
}
|
||
|
|
return degree - loops;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given node's directed degree without considering self loops.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {number} - The node's degree.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node isn't in the graph.
|
||
|
|
*/
|
||
|
|
degreeWithoutSelfLoops(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.degreeWithoutSelfLoops: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let degree = 0;
|
||
|
|
let loops = 0;
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
degree += nodeData.undirectedDegree;
|
||
|
|
loops += nodeData.undirectedLoops * 2;
|
||
|
|
}
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
degree += nodeData.inDegree + nodeData.outDegree;
|
||
|
|
loops += nodeData.directedLoops * 2;
|
||
|
|
}
|
||
|
|
return degree - loops;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given edge's source.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {any} - The edge's source.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
source(edge) {
|
||
|
|
edge = "" + edge;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.source: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return data.source.key;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given edge's target.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {any} - The edge's target.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
target(edge) {
|
||
|
|
edge = "" + edge;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.target: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return data.target.key;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the given edge's extremities.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {array} - The edge's extremities.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
extremities(edge) {
|
||
|
|
edge = "" + edge;
|
||
|
|
const edgeData = this._edges.get(edge);
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.extremities: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return [edgeData.source.key, edgeData.target.key];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Given a node & an edge, returns the other extremity of the edge.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {any} - The related node.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph or if the
|
||
|
|
* edge & node are not related.
|
||
|
|
*/
|
||
|
|
opposite(node, edge) {
|
||
|
|
node = "" + node;
|
||
|
|
edge = "" + edge;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.opposite: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
const source = data.source.key;
|
||
|
|
const target = data.target.key;
|
||
|
|
if (node === source) return target;
|
||
|
|
if (node === target) return source;
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.opposite: the "${node}" node is not attached to the "${edge}" edge (${source}, ${target}).`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Returns whether the given edge has the given node as extremity.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @param {any} node - The node's key.
|
||
|
|
* @return {boolean} - The related node.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if either the node or the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
hasExtremity(edge, node) {
|
||
|
|
edge = "" + edge;
|
||
|
|
node = "" + node;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.hasExtremity: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return data.source.key === node || data.target.key === node;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the given edge is undirected.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
isUndirected(edge) {
|
||
|
|
edge = "" + edge;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.isUndirected: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return data.undirected;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the given edge is directed.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
isDirected(edge) {
|
||
|
|
edge = "" + edge;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.isDirected: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return !data.undirected;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the given edge is a self loop.
|
||
|
|
*
|
||
|
|
* @param {any} edge - The edge's key.
|
||
|
|
* @return {boolean}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge isn't in the graph.
|
||
|
|
*/
|
||
|
|
isSelfLoop(edge) {
|
||
|
|
edge = "" + edge;
|
||
|
|
const data = this._edges.get(edge);
|
||
|
|
if (!data)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.isSelfLoop: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
return data.source === data.target;
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Mutation
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method used to add a node to the graph.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node.
|
||
|
|
* @param {object} [attributes] - Optional attributes.
|
||
|
|
* @return {any} - The node.
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the given node already exist.
|
||
|
|
* @throws {Error} - Will throw if the given attributes are not an object.
|
||
|
|
*/
|
||
|
|
addNode(node, attributes) {
|
||
|
|
const nodeData = addNode(this, node, attributes);
|
||
|
|
return nodeData.key;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to merge a node into the graph.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node.
|
||
|
|
* @param {object} [attributes] - Optional attributes.
|
||
|
|
* @return {any} - The node.
|
||
|
|
*/
|
||
|
|
mergeNode(node, attributes) {
|
||
|
|
if (attributes && !isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.mergeNode: invalid attributes. Expecting an object but got "${attributes}"`
|
||
|
|
);
|
||
|
|
node = "" + node;
|
||
|
|
attributes = attributes || {};
|
||
|
|
let data = this._nodes.get(node);
|
||
|
|
if (data) {
|
||
|
|
if (attributes) {
|
||
|
|
assign(data.attributes, attributes);
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
type: "merge",
|
||
|
|
key: node,
|
||
|
|
attributes: data.attributes,
|
||
|
|
data: attributes
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return [node, false];
|
||
|
|
}
|
||
|
|
data = new this.NodeDataClass(node, attributes);
|
||
|
|
this._nodes.set(node, data);
|
||
|
|
this.emit("nodeAdded", {
|
||
|
|
key: node,
|
||
|
|
attributes
|
||
|
|
});
|
||
|
|
return [node, true];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to add a node if it does not exist in the graph or else to
|
||
|
|
* update its attributes using a function.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node.
|
||
|
|
* @param {function} [updater] - Optional updater function.
|
||
|
|
* @return {any} - The node.
|
||
|
|
*/
|
||
|
|
updateNode(node, updater) {
|
||
|
|
if (updater && typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
`Graph.updateNode: invalid updater function. Expecting a function but got "${updater}"`
|
||
|
|
);
|
||
|
|
node = "" + node;
|
||
|
|
let data = this._nodes.get(node);
|
||
|
|
if (data) {
|
||
|
|
if (updater) {
|
||
|
|
const oldAttributes = data.attributes;
|
||
|
|
data.attributes = updater(oldAttributes);
|
||
|
|
this.emit("nodeAttributesUpdated", {
|
||
|
|
type: "replace",
|
||
|
|
key: node,
|
||
|
|
attributes: data.attributes
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return [node, false];
|
||
|
|
}
|
||
|
|
const attributes = updater ? updater({}) : {};
|
||
|
|
data = new this.NodeDataClass(node, attributes);
|
||
|
|
this._nodes.set(node, data);
|
||
|
|
this.emit("nodeAdded", {
|
||
|
|
key: node,
|
||
|
|
attributes
|
||
|
|
});
|
||
|
|
return [node, true];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to drop a single node & all its attached edges from the graph.
|
||
|
|
*
|
||
|
|
* @param {any} node - The node.
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the node doesn't exist.
|
||
|
|
*/
|
||
|
|
dropNode(node) {
|
||
|
|
node = "" + node;
|
||
|
|
const nodeData = this._nodes.get(node);
|
||
|
|
if (!nodeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.dropNode: could not find the "${node}" node in the graph.`
|
||
|
|
);
|
||
|
|
let edgeData;
|
||
|
|
if (this.type !== "undirected") {
|
||
|
|
for (const neighbor in nodeData.out) {
|
||
|
|
edgeData = nodeData.out[neighbor];
|
||
|
|
do {
|
||
|
|
dropEdgeFromData(this, edgeData);
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData);
|
||
|
|
}
|
||
|
|
for (const neighbor in nodeData.in) {
|
||
|
|
edgeData = nodeData.in[neighbor];
|
||
|
|
do {
|
||
|
|
dropEdgeFromData(this, edgeData);
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (this.type !== "directed") {
|
||
|
|
for (const neighbor in nodeData.undirected) {
|
||
|
|
edgeData = nodeData.undirected[neighbor];
|
||
|
|
do {
|
||
|
|
dropEdgeFromData(this, edgeData);
|
||
|
|
edgeData = edgeData.next;
|
||
|
|
} while (edgeData);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this._nodes.delete(node);
|
||
|
|
this.emit("nodeDropped", {
|
||
|
|
key: node,
|
||
|
|
attributes: nodeData.attributes
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to drop a single edge from the graph.
|
||
|
|
*
|
||
|
|
* Arity 1:
|
||
|
|
* @param {any} edge - The edge.
|
||
|
|
*
|
||
|
|
* Arity 2:
|
||
|
|
* @param {any} source - Source node.
|
||
|
|
* @param {any} target - Target node.
|
||
|
|
*
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge doesn't exist.
|
||
|
|
*/
|
||
|
|
dropEdge(edge) {
|
||
|
|
let edgeData;
|
||
|
|
if (arguments.length > 1) {
|
||
|
|
const source = "" + arguments[0];
|
||
|
|
const target = "" + arguments[1];
|
||
|
|
edgeData = getMatchingEdge(this, source, target, this.type);
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.dropEdge: could not find the "${source}" -> "${target}" edge in the graph.`
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
edge = "" + edge;
|
||
|
|
edgeData = this._edges.get(edge);
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.dropEdge: could not find the "${edge}" edge in the graph.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
dropEdgeFromData(this, edgeData);
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to drop a single directed edge from the graph.
|
||
|
|
*
|
||
|
|
* @param {any} source - Source node.
|
||
|
|
* @param {any} target - Target node.
|
||
|
|
*
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge doesn't exist.
|
||
|
|
*/
|
||
|
|
dropDirectedEdge(source, target) {
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
source = "" + source;
|
||
|
|
target = "" + target;
|
||
|
|
const edgeData = getMatchingEdge(this, source, target, "directed");
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.dropDirectedEdge: could not find a "${source}" -> "${target}" edge in the graph.`
|
||
|
|
);
|
||
|
|
dropEdgeFromData(this, edgeData);
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to drop a single undirected edge from the graph.
|
||
|
|
*
|
||
|
|
* @param {any} source - Source node.
|
||
|
|
* @param {any} target - Target node.
|
||
|
|
*
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if the edge doesn't exist.
|
||
|
|
*/
|
||
|
|
dropUndirectedEdge(source, target) {
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
const edgeData = getMatchingEdge(this, source, target, "undirected");
|
||
|
|
if (!edgeData)
|
||
|
|
throw new NotFoundGraphError(
|
||
|
|
`Graph.dropUndirectedEdge: could not find a "${source}" -> "${target}" edge in the graph.`
|
||
|
|
);
|
||
|
|
dropEdgeFromData(this, edgeData);
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to remove every edge & every node from the graph.
|
||
|
|
*
|
||
|
|
* @return {Graph}
|
||
|
|
*/
|
||
|
|
clear() {
|
||
|
|
this._edges.clear();
|
||
|
|
this._nodes.clear();
|
||
|
|
this._resetInstanceCounters();
|
||
|
|
this.emit("cleared");
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to remove every edge from the graph.
|
||
|
|
*
|
||
|
|
* @return {Graph}
|
||
|
|
*/
|
||
|
|
clearEdges() {
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
step.value.clear();
|
||
|
|
}
|
||
|
|
this._edges.clear();
|
||
|
|
this._resetInstanceCounters();
|
||
|
|
this.emit("edgesCleared");
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Attributes-related methods
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method returning the desired graph's attribute.
|
||
|
|
*
|
||
|
|
* @param {string} name - Name of the attribute.
|
||
|
|
* @return {any}
|
||
|
|
*/
|
||
|
|
getAttribute(name) {
|
||
|
|
return this._attributes[name];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the graph's attributes.
|
||
|
|
*
|
||
|
|
* @return {object}
|
||
|
|
*/
|
||
|
|
getAttributes() {
|
||
|
|
return this._attributes;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether the graph has the desired attribute.
|
||
|
|
*
|
||
|
|
* @param {string} name - Name of the attribute.
|
||
|
|
* @return {boolean}
|
||
|
|
*/
|
||
|
|
hasAttribute(name) {
|
||
|
|
return this._attributes.hasOwnProperty(name);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method setting a value for the desired graph's attribute.
|
||
|
|
*
|
||
|
|
* @param {string} name - Name of the attribute.
|
||
|
|
* @param {any} value - Value for the attribute.
|
||
|
|
* @return {Graph}
|
||
|
|
*/
|
||
|
|
setAttribute(name, value) {
|
||
|
|
this._attributes[name] = value;
|
||
|
|
this.emit("attributesUpdated", {
|
||
|
|
type: "set",
|
||
|
|
attributes: this._attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method using a function to update the desired graph's attribute's value.
|
||
|
|
*
|
||
|
|
* @param {string} name - Name of the attribute.
|
||
|
|
* @param {function} updater - Function use to update the attribute's value.
|
||
|
|
* @return {Graph}
|
||
|
|
*/
|
||
|
|
updateAttribute(name, updater) {
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.updateAttribute: updater should be a function."
|
||
|
|
);
|
||
|
|
const value = this._attributes[name];
|
||
|
|
this._attributes[name] = updater(value);
|
||
|
|
this.emit("attributesUpdated", {
|
||
|
|
type: "set",
|
||
|
|
attributes: this._attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method removing the desired graph's attribute.
|
||
|
|
*
|
||
|
|
* @param {string} name - Name of the attribute.
|
||
|
|
* @return {Graph}
|
||
|
|
*/
|
||
|
|
removeAttribute(name) {
|
||
|
|
delete this._attributes[name];
|
||
|
|
this.emit("attributesUpdated", {
|
||
|
|
type: "remove",
|
||
|
|
attributes: this._attributes,
|
||
|
|
name
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method replacing the graph's attributes.
|
||
|
|
*
|
||
|
|
* @param {object} attributes - New attributes.
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if given attributes are not a plain object.
|
||
|
|
*/
|
||
|
|
replaceAttributes(attributes) {
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.replaceAttributes: provided attributes are not a plain object."
|
||
|
|
);
|
||
|
|
this._attributes = attributes;
|
||
|
|
this.emit("attributesUpdated", {
|
||
|
|
type: "replace",
|
||
|
|
attributes: this._attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method merging the graph's attributes.
|
||
|
|
*
|
||
|
|
* @param {object} attributes - Attributes to merge.
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if given attributes are not a plain object.
|
||
|
|
*/
|
||
|
|
mergeAttributes(attributes) {
|
||
|
|
if (!isPlainObject(attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.mergeAttributes: provided attributes are not a plain object."
|
||
|
|
);
|
||
|
|
assign(this._attributes, attributes);
|
||
|
|
this.emit("attributesUpdated", {
|
||
|
|
type: "merge",
|
||
|
|
attributes: this._attributes,
|
||
|
|
data: attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method updating the graph's attributes.
|
||
|
|
*
|
||
|
|
* @param {function} updater - Function used to update the attributes.
|
||
|
|
* @return {Graph}
|
||
|
|
*
|
||
|
|
* @throws {Error} - Will throw if given updater is not a function.
|
||
|
|
*/
|
||
|
|
updateAttributes(updater) {
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.updateAttributes: provided updater is not a function."
|
||
|
|
);
|
||
|
|
this._attributes = updater(this._attributes);
|
||
|
|
this.emit("attributesUpdated", {
|
||
|
|
type: "update",
|
||
|
|
attributes: this._attributes
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to update each node's attributes using the given function.
|
||
|
|
*
|
||
|
|
* @param {function} updater - Updater function to use.
|
||
|
|
* @param {object} [hints] - Optional hints.
|
||
|
|
*/
|
||
|
|
updateEachNodeAttributes(updater, hints) {
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.updateEachNodeAttributes: expecting an updater function."
|
||
|
|
);
|
||
|
|
if (hints && !validateHints(hints))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.updateEachNodeAttributes: invalid hints. Expecting an object having the following shape: {attributes?: [string]}"
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
nodeData.attributes = updater(nodeData.key, nodeData.attributes);
|
||
|
|
}
|
||
|
|
this.emit("eachNodeAttributesUpdated", {
|
||
|
|
hints: hints ? hints : null
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to update each edge's attributes using the given function.
|
||
|
|
*
|
||
|
|
* @param {function} updater - Updater function to use.
|
||
|
|
* @param {object} [hints] - Optional hints.
|
||
|
|
*/
|
||
|
|
updateEachEdgeAttributes(updater, hints) {
|
||
|
|
if (typeof updater !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.updateEachEdgeAttributes: expecting an updater function."
|
||
|
|
);
|
||
|
|
if (hints && !validateHints(hints))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.updateEachEdgeAttributes: invalid hints. Expecting an object having the following shape: {attributes?: [string]}"
|
||
|
|
);
|
||
|
|
const iterator = this._edges.values();
|
||
|
|
let step, edgeData, sourceData, targetData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
edgeData = step.value;
|
||
|
|
sourceData = edgeData.source;
|
||
|
|
targetData = edgeData.target;
|
||
|
|
edgeData.attributes = updater(
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.attributes,
|
||
|
|
sourceData.key,
|
||
|
|
targetData.key,
|
||
|
|
sourceData.attributes,
|
||
|
|
targetData.attributes,
|
||
|
|
edgeData.undirected
|
||
|
|
);
|
||
|
|
}
|
||
|
|
this.emit("eachEdgeAttributesUpdated", {
|
||
|
|
hints: hints ? hints : null
|
||
|
|
});
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Iteration-related methods
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method iterating over the graph's adjacency using the given callback.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback to use.
|
||
|
|
*/
|
||
|
|
forEachAdjacencyEntry(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.forEachAdjacencyEntry: expecting a callback."
|
||
|
|
);
|
||
|
|
forEachAdjacency(false, false, false, this, callback);
|
||
|
|
}
|
||
|
|
forEachAdjacencyEntryWithOrphans(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.forEachAdjacencyEntryWithOrphans: expecting a callback."
|
||
|
|
);
|
||
|
|
forEachAdjacency(false, false, true, this, callback);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method iterating over the graph's assymetric adjacency using the given callback.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback to use.
|
||
|
|
*/
|
||
|
|
forEachAssymetricAdjacencyEntry(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.forEachAssymetricAdjacencyEntry: expecting a callback."
|
||
|
|
);
|
||
|
|
forEachAdjacency(false, true, false, this, callback);
|
||
|
|
}
|
||
|
|
forEachAssymetricAdjacencyEntryWithOrphans(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.forEachAssymetricAdjacencyEntryWithOrphans: expecting a callback."
|
||
|
|
);
|
||
|
|
forEachAdjacency(false, true, true, this, callback);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning the list of the graph's nodes.
|
||
|
|
*
|
||
|
|
* @return {array} - The nodes.
|
||
|
|
*/
|
||
|
|
nodes() {
|
||
|
|
return Array.from(this._nodes.keys());
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method iterating over the graph's nodes using the given callback.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (key, attributes, index).
|
||
|
|
*/
|
||
|
|
forEachNode(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.forEachNode: expecting a callback."
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
callback(nodeData.key, nodeData.attributes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method iterating attempting to find a node matching the given predicate
|
||
|
|
* function.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (key, attributes).
|
||
|
|
*/
|
||
|
|
findNode(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.findNode: expecting a callback."
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
if (callback(nodeData.key, nodeData.attributes)) return nodeData.key;
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method mapping nodes.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (key, attributes).
|
||
|
|
*/
|
||
|
|
mapNodes(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.mapNode: expecting a callback."
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
const result = new Array(this.order);
|
||
|
|
let i = 0;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
result[i++] = callback(nodeData.key, nodeData.attributes);
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether some node verify the given predicate.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (key, attributes).
|
||
|
|
*/
|
||
|
|
someNode(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.someNode: expecting a callback."
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
if (callback(nodeData.key, nodeData.attributes)) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning whether all node verify the given predicate.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (key, attributes).
|
||
|
|
*/
|
||
|
|
everyNode(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.everyNode: expecting a callback."
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
if (!callback(nodeData.key, nodeData.attributes)) return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method filtering nodes.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (key, attributes).
|
||
|
|
*/
|
||
|
|
filterNodes(callback) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.filterNodes: expecting a callback."
|
||
|
|
);
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
const result = [];
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
if (callback(nodeData.key, nodeData.attributes))
|
||
|
|
result.push(nodeData.key);
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method reducing nodes.
|
||
|
|
*
|
||
|
|
* @param {function} callback - Callback (accumulator, key, attributes).
|
||
|
|
*/
|
||
|
|
reduceNodes(callback, initialValue) {
|
||
|
|
if (typeof callback !== "function")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.reduceNodes: expecting a callback."
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
let accumulator = initialValue;
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
let step, nodeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
nodeData = step.value;
|
||
|
|
accumulator = callback(accumulator, nodeData.key, nodeData.attributes);
|
||
|
|
}
|
||
|
|
return accumulator;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning an iterator over the graph's node entries.
|
||
|
|
*
|
||
|
|
* @return {Iterator}
|
||
|
|
*/
|
||
|
|
nodeEntries() {
|
||
|
|
const iterator = this._nodes.values();
|
||
|
|
return {
|
||
|
|
[Symbol.iterator]() {
|
||
|
|
return this;
|
||
|
|
},
|
||
|
|
next() {
|
||
|
|
const step = iterator.next();
|
||
|
|
if (step.done) return step;
|
||
|
|
const data = step.value;
|
||
|
|
return {
|
||
|
|
value: { node: data.key, attributes: data.attributes },
|
||
|
|
done: false
|
||
|
|
};
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Serialization
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method used to export the whole graph.
|
||
|
|
*
|
||
|
|
* @return {object} - The serialized graph.
|
||
|
|
*/
|
||
|
|
export() {
|
||
|
|
const nodes = new Array(this._nodes.size);
|
||
|
|
let i = 0;
|
||
|
|
this._nodes.forEach((data, key) => {
|
||
|
|
nodes[i++] = serializeNode(key, data);
|
||
|
|
});
|
||
|
|
const edges = new Array(this._edges.size);
|
||
|
|
i = 0;
|
||
|
|
this._edges.forEach((data, key) => {
|
||
|
|
edges[i++] = serializeEdge(this.type, key, data);
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
options: {
|
||
|
|
type: this.type,
|
||
|
|
multi: this.multi,
|
||
|
|
allowSelfLoops: this.allowSelfLoops
|
||
|
|
},
|
||
|
|
attributes: this.getAttributes(),
|
||
|
|
nodes,
|
||
|
|
edges
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used to import a serialized graph.
|
||
|
|
*
|
||
|
|
* @param {object|Graph} data - The serialized graph.
|
||
|
|
* @param {boolean} merge - Whether to merge data.
|
||
|
|
* @return {Graph} - Returns itself for chaining.
|
||
|
|
*/
|
||
|
|
import(data, merge = false) {
|
||
|
|
if (data instanceof _Graph) {
|
||
|
|
data.forEachNode((n, a) => {
|
||
|
|
if (merge) this.mergeNode(n, a);
|
||
|
|
else this.addNode(n, a);
|
||
|
|
});
|
||
|
|
data.forEachEdge((e, a, s, t, _sa, _ta, u) => {
|
||
|
|
if (merge) {
|
||
|
|
if (u) this.mergeUndirectedEdgeWithKey(e, s, t, a);
|
||
|
|
else this.mergeDirectedEdgeWithKey(e, s, t, a);
|
||
|
|
} else {
|
||
|
|
if (u) this.addUndirectedEdgeWithKey(e, s, t, a);
|
||
|
|
else this.addDirectedEdgeWithKey(e, s, t, a);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
if (!isPlainObject(data))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: invalid argument. Expecting a serialized graph or, alternatively, a Graph instance."
|
||
|
|
);
|
||
|
|
if (data.attributes) {
|
||
|
|
if (!isPlainObject(data.attributes))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: invalid attributes. Expecting a plain object."
|
||
|
|
);
|
||
|
|
if (merge) this.mergeAttributes(data.attributes);
|
||
|
|
else this.replaceAttributes(data.attributes);
|
||
|
|
}
|
||
|
|
let i, l, list, node, edge;
|
||
|
|
if (data.nodes) {
|
||
|
|
list = data.nodes;
|
||
|
|
if (!Array.isArray(list))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: invalid nodes. Expecting an array."
|
||
|
|
);
|
||
|
|
for (i = 0, l = list.length; i < l; i++) {
|
||
|
|
node = list[i];
|
||
|
|
validateSerializedNode(node);
|
||
|
|
const { key, attributes } = node;
|
||
|
|
if (merge) this.mergeNode(key, attributes);
|
||
|
|
else this.addNode(key, attributes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (data.edges) {
|
||
|
|
let undirectedByDefault = false;
|
||
|
|
if (this.type === "undirected") {
|
||
|
|
undirectedByDefault = true;
|
||
|
|
}
|
||
|
|
list = data.edges;
|
||
|
|
if (!Array.isArray(list))
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"Graph.import: invalid edges. Expecting an array."
|
||
|
|
);
|
||
|
|
for (i = 0, l = list.length; i < l; i++) {
|
||
|
|
edge = list[i];
|
||
|
|
validateSerializedEdge(edge);
|
||
|
|
const {
|
||
|
|
source,
|
||
|
|
target,
|
||
|
|
attributes,
|
||
|
|
undirected = undirectedByDefault
|
||
|
|
} = edge;
|
||
|
|
let method;
|
||
|
|
if ("key" in edge) {
|
||
|
|
method = merge ? undirected ? this.mergeUndirectedEdgeWithKey : this.mergeDirectedEdgeWithKey : undirected ? this.addUndirectedEdgeWithKey : this.addDirectedEdgeWithKey;
|
||
|
|
method.call(this, edge.key, source, target, attributes);
|
||
|
|
} else {
|
||
|
|
method = merge ? undirected ? this.mergeUndirectedEdge : this.mergeDirectedEdge : undirected ? this.addUndirectedEdge : this.addDirectedEdge;
|
||
|
|
method.call(this, source, target, attributes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Utils
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method returning a null copy of the graph, i.e. a graph without nodes
|
||
|
|
* & edges but with the exact same options.
|
||
|
|
*
|
||
|
|
* @param {object} options - Options to merge with the current ones.
|
||
|
|
* @return {Graph} - The null copy.
|
||
|
|
*/
|
||
|
|
nullCopy(options) {
|
||
|
|
const graph = new _Graph(assign({}, this._options, options));
|
||
|
|
graph.replaceAttributes(assign({}, this.getAttributes()));
|
||
|
|
return graph;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning an empty copy of the graph, i.e. a graph without edges but
|
||
|
|
* with the exact same options.
|
||
|
|
*
|
||
|
|
* @param {object} options - Options to merge with the current ones.
|
||
|
|
* @return {Graph} - The empty copy.
|
||
|
|
*/
|
||
|
|
emptyCopy(options) {
|
||
|
|
const graph = this.nullCopy(options);
|
||
|
|
this._nodes.forEach((nodeData, key) => {
|
||
|
|
const attributes = assign({}, nodeData.attributes);
|
||
|
|
nodeData = new graph.NodeDataClass(key, attributes);
|
||
|
|
graph._nodes.set(key, nodeData);
|
||
|
|
});
|
||
|
|
return graph;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning an exact copy of the graph.
|
||
|
|
*
|
||
|
|
* @param {object} options - Upgrade options.
|
||
|
|
* @return {Graph} - The copy.
|
||
|
|
*/
|
||
|
|
copy(options) {
|
||
|
|
options = options || {};
|
||
|
|
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.`
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
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."
|
||
|
|
);
|
||
|
|
const graph = this.emptyCopy(options);
|
||
|
|
const iterator = this._edges.values();
|
||
|
|
let step, edgeData;
|
||
|
|
while (step = iterator.next(), step.done !== true) {
|
||
|
|
edgeData = step.value;
|
||
|
|
addEdge(
|
||
|
|
graph,
|
||
|
|
"copy",
|
||
|
|
false,
|
||
|
|
edgeData.undirected,
|
||
|
|
edgeData.key,
|
||
|
|
edgeData.source.key,
|
||
|
|
edgeData.target.key,
|
||
|
|
assign({}, edgeData.attributes)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
return graph;
|
||
|
|
}
|
||
|
|
/**---------------------------------------------------------------------------
|
||
|
|
* Known methods
|
||
|
|
**---------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* Method used by JavaScript to perform JSON serialization.
|
||
|
|
*
|
||
|
|
* @return {object} - The serialized graph.
|
||
|
|
*/
|
||
|
|
toJSON() {
|
||
|
|
return this.export();
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method returning [object Graph].
|
||
|
|
*/
|
||
|
|
toString() {
|
||
|
|
return "[object Graph]";
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Method used internally by node's console to display a custom object.
|
||
|
|
*
|
||
|
|
* @return {object} - Formatted object representation of the graph.
|
||
|
|
*/
|
||
|
|
inspect() {
|
||
|
|
const nodes = {};
|
||
|
|
this._nodes.forEach((data, key) => {
|
||
|
|
nodes[key] = data.attributes;
|
||
|
|
});
|
||
|
|
const edges = {}, multiIndex = {};
|
||
|
|
this._edges.forEach((data, key) => {
|
||
|
|
const direction = data.undirected ? "--" : "->";
|
||
|
|
let label = "";
|
||
|
|
let source = data.source.key;
|
||
|
|
let target = data.target.key;
|
||
|
|
let tmp;
|
||
|
|
if (data.undirected && source > target) {
|
||
|
|
tmp = source;
|
||
|
|
source = target;
|
||
|
|
target = tmp;
|
||
|
|
}
|
||
|
|
const desc = `(${source})${direction}(${target})`;
|
||
|
|
if (!key.startsWith("geid_")) {
|
||
|
|
label += `[${key}]: `;
|
||
|
|
} else if (this.multi) {
|
||
|
|
if (typeof multiIndex[desc] === "undefined") {
|
||
|
|
multiIndex[desc] = 0;
|
||
|
|
} else {
|
||
|
|
multiIndex[desc]++;
|
||
|
|
}
|
||
|
|
label += `${multiIndex[desc]}. `;
|
||
|
|
}
|
||
|
|
label += desc;
|
||
|
|
edges[label] = data.attributes;
|
||
|
|
});
|
||
|
|
const dummy = {};
|
||
|
|
for (const k in this) {
|
||
|
|
if (this.hasOwnProperty(k) && !EMITTER_PROPS.has(k) && typeof this[k] !== "function" && typeof k !== "symbol")
|
||
|
|
dummy[k] = this[k];
|
||
|
|
}
|
||
|
|
dummy.attributes = this._attributes;
|
||
|
|
dummy.nodes = nodes;
|
||
|
|
dummy.edges = edges;
|
||
|
|
privateProperty(dummy, "constructor", this.constructor);
|
||
|
|
return dummy;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
if (typeof Symbol !== "undefined")
|
||
|
|
Graph.prototype[Symbol.for("nodejs.util.inspect.custom")] = Graph.prototype.inspect;
|
||
|
|
EDGE_ADD_METHODS.forEach((method) => {
|
||
|
|
["add", "merge", "update"].forEach((verb) => {
|
||
|
|
const name = method.name(verb);
|
||
|
|
const fn = verb === "add" ? addEdge : mergeEdge;
|
||
|
|
if (method.generateKey) {
|
||
|
|
Graph.prototype[name] = function(source, target, attributes) {
|
||
|
|
return fn(
|
||
|
|
this,
|
||
|
|
name,
|
||
|
|
true,
|
||
|
|
(method.type || this.type) === "undirected",
|
||
|
|
null,
|
||
|
|
source,
|
||
|
|
target,
|
||
|
|
attributes,
|
||
|
|
verb === "update"
|
||
|
|
);
|
||
|
|
};
|
||
|
|
} else {
|
||
|
|
Graph.prototype[name] = function(edge, source, target, attributes) {
|
||
|
|
return fn(
|
||
|
|
this,
|
||
|
|
name,
|
||
|
|
false,
|
||
|
|
(method.type || this.type) === "undirected",
|
||
|
|
edge,
|
||
|
|
source,
|
||
|
|
target,
|
||
|
|
attributes,
|
||
|
|
verb === "update"
|
||
|
|
);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
attachNodeAttributesMethods(Graph);
|
||
|
|
attachEdgeAttributesMethods(Graph);
|
||
|
|
attachEdgeIterationMethods(Graph);
|
||
|
|
attachNeighborIterationMethods(Graph);
|
||
|
|
var DirectedGraph = class extends Graph {
|
||
|
|
constructor(options) {
|
||
|
|
const finalOptions = assign({ type: "directed" }, options);
|
||
|
|
if ("multi" in finalOptions && finalOptions.multi !== false)
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"DirectedGraph.from: inconsistent indication that the graph should be multi in given options!"
|
||
|
|
);
|
||
|
|
if (finalOptions.type !== "directed")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
'DirectedGraph.from: inconsistent "' + finalOptions.type + '" type in given options!'
|
||
|
|
);
|
||
|
|
super(finalOptions);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var UndirectedGraph = class extends Graph {
|
||
|
|
constructor(options) {
|
||
|
|
const finalOptions = assign({ type: "undirected" }, options);
|
||
|
|
if ("multi" in finalOptions && finalOptions.multi !== false)
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"UndirectedGraph.from: inconsistent indication that the graph should be multi in given options!"
|
||
|
|
);
|
||
|
|
if (finalOptions.type !== "undirected")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
'UndirectedGraph.from: inconsistent "' + finalOptions.type + '" type in given options!'
|
||
|
|
);
|
||
|
|
super(finalOptions);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var MultiGraph = class extends Graph {
|
||
|
|
constructor(options) {
|
||
|
|
const finalOptions = assign({ multi: true }, options);
|
||
|
|
if ("multi" in finalOptions && finalOptions.multi !== true)
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"MultiGraph.from: inconsistent indication that the graph should be simple in given options!"
|
||
|
|
);
|
||
|
|
super(finalOptions);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var MultiDirectedGraph = class extends Graph {
|
||
|
|
constructor(options) {
|
||
|
|
const finalOptions = assign({ type: "directed", multi: true }, options);
|
||
|
|
if ("multi" in finalOptions && finalOptions.multi !== true)
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"MultiDirectedGraph.from: inconsistent indication that the graph should be simple in given options!"
|
||
|
|
);
|
||
|
|
if (finalOptions.type !== "directed")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
'MultiDirectedGraph.from: inconsistent "' + finalOptions.type + '" type in given options!'
|
||
|
|
);
|
||
|
|
super(finalOptions);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
var MultiUndirectedGraph = class extends Graph {
|
||
|
|
constructor(options) {
|
||
|
|
const finalOptions = assign({ type: "undirected", multi: true }, options);
|
||
|
|
if ("multi" in finalOptions && finalOptions.multi !== true)
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
"MultiUndirectedGraph.from: inconsistent indication that the graph should be simple in given options!"
|
||
|
|
);
|
||
|
|
if (finalOptions.type !== "undirected")
|
||
|
|
throw new InvalidArgumentsGraphError(
|
||
|
|
'MultiUndirectedGraph.from: inconsistent "' + finalOptions.type + '" type in given options!'
|
||
|
|
);
|
||
|
|
super(finalOptions);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
function attachStaticFromMethod(Class) {
|
||
|
|
Class.from = function(data, options) {
|
||
|
|
const finalOptions = assign({}, data.options, options);
|
||
|
|
const instance = new Class(finalOptions);
|
||
|
|
instance.import(data);
|
||
|
|
return instance;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
attachStaticFromMethod(Graph);
|
||
|
|
attachStaticFromMethod(DirectedGraph);
|
||
|
|
attachStaticFromMethod(UndirectedGraph);
|
||
|
|
attachStaticFromMethod(MultiGraph);
|
||
|
|
attachStaticFromMethod(MultiDirectedGraph);
|
||
|
|
attachStaticFromMethod(MultiUndirectedGraph);
|
||
|
|
Graph.Graph = Graph;
|
||
|
|
Graph.DirectedGraph = DirectedGraph;
|
||
|
|
Graph.UndirectedGraph = UndirectedGraph;
|
||
|
|
Graph.MultiGraph = MultiGraph;
|
||
|
|
Graph.MultiDirectedGraph = MultiDirectedGraph;
|
||
|
|
Graph.MultiUndirectedGraph = MultiUndirectedGraph;
|
||
|
|
Graph.InvalidArgumentsGraphError = InvalidArgumentsGraphError;
|
||
|
|
Graph.NotFoundGraphError = NotFoundGraphError;
|
||
|
|
Graph.UsageGraphError = UsageGraphError;
|
||
|
|
export {
|
||
|
|
DirectedGraph,
|
||
|
|
Graph,
|
||
|
|
InvalidArgumentsGraphError,
|
||
|
|
MultiDirectedGraph,
|
||
|
|
MultiGraph,
|
||
|
|
MultiUndirectedGraph,
|
||
|
|
NotFoundGraphError,
|
||
|
|
UndirectedGraph,
|
||
|
|
UsageGraphError,
|
||
|
|
Graph as default
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=graphology.js.map
|