(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.graphology = factory()); })(this, (function () { 'use strict'; function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct.bind(); } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } /** * Graphology Utilities * ===================== * * Collection of helpful functions used by the implementation. */ /** * Object.assign-like polyfill. * * @param {object} target - First object. * @param {object} [...objects] - Objects to merge. * @return {object} */ function assignPolyfill() { var target = arguments[0]; for (var i = 1, l = arguments.length; i < l; i++) { if (!arguments[i]) continue; for (var k in arguments[i]) { target[k] = arguments[i][k]; } } return target; } var assign = assignPolyfill; if (typeof Object.assign === 'function') assign = Object.assign; /** * Function returning the first matching edge for given path. * Note: this function does not check the existence of source & target. This * must be performed by the caller. * * @param {Graph} graph - Target graph. * @param {any} source - Source node. * @param {any} target - Target node. * @param {string} type - Type of the edge (mixed, directed or undirected). * @return {string|null} */ function getMatchingEdge(graph, source, target, type) { var sourceData = graph._nodes.get(source); var 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; } /** * Checks whether the given value is a plain object. * * @param {mixed} value - Target value. * @return {boolean} */ function isPlainObject(value) { // NOTE: as per https://github.com/graphology/graphology/issues/149 // this function has been loosened not to reject object instances // coming from other JavaScript contexts. It has also been chosen // not to improve it to avoid obvious false positives and avoid // taking a performance hit. People should really use TypeScript // if they want to avoid feeding subtly irrelvant attribute objects. return _typeof(value) === 'object' && value !== null; } /** * Checks whether the given object is empty. * * @param {object} o - Target Object. * @return {boolean} */ function isEmpty(o) { var k; for (k in o) { return false; } return true; } /** * Creates a "private" property for the given member name by concealing it * using the `enumerable` option. * * @param {object} target - Target object. * @param {string} name - Member name. */ function privateProperty(target, name, value) { Object.defineProperty(target, name, { enumerable: false, configurable: false, writable: true, value: value }); } /** * Creates a read-only property for the given member name & the given getter. * * @param {object} target - Target object. * @param {string} name - Member name. * @param {mixed} value - The attached getter or fixed value. */ function readOnlyProperty(target, name, value) { var descriptor = { enumerable: true, configurable: true }; if (typeof value === 'function') { descriptor.get = value; } else { descriptor.value = value; descriptor.writable = false; } Object.defineProperty(target, name, descriptor); } /** * Returns whether the given object constitute valid hints. * * @param {object} hints - Target object. */ function validateHints(hints) { if (!isPlainObject(hints)) return false; if (hints.attributes && !Array.isArray(hints.attributes)) return false; return true; } /** * Creates a function generating incremental ids for edges. * * @return {function} */ function incrementalIdStartingFromRandomByte() { var i = Math.floor(Math.random() * 256) & 0xff; return function () { return i++; }; } var events = {exports: {}}; var R = typeof Reflect === 'object' ? Reflect : null; var ReflectApply = R && typeof R.apply === 'function' ? R.apply : function ReflectApply(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 ReflectOwnKeys(target) { return Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)); }; } else { ReflectOwnKeys = function ReflectOwnKeys(target) { return Object.getOwnPropertyNames(target); }; } function ProcessEmitWarning(warning) { if (console && console.warn) console.warn(warning); } var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) { return value !== value; }; function EventEmitter() { EventEmitter.init.call(this); } events.exports = EventEmitter; events.exports.once = once; // Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter; EventEmitter.prototype._events = undefined; EventEmitter.prototype._eventsCount = 0; EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. 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(EventEmitter, '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; } }); EventEmitter.init = function () { if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) { this._events = Object.create(null); this._eventsCount = 0; } this._maxListeners = this._maxListeners || undefined; }; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.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 === undefined) return EventEmitter.defaultMaxListeners; return that._maxListeners; } EventEmitter.prototype.getMaxListeners = function getMaxListeners() { return _getMaxListeners(this); }; EventEmitter.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 !== undefined) doError = doError && events.error === undefined;else if (!doError) return false; // If there is no 'error' event listener then throw. if (doError) { var er; if (args.length > 0) er = args[0]; if (er instanceof Error) { // Note: The comments on the `throw` lines are intentional, they show // up in Node's output if this results in an unhandled exception. throw er; // Unhandled 'error' event } // At least give some kind of context to the user var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : '')); err.context = er; throw err; // Unhandled 'error' event } var handler = events[type]; if (handler === undefined) 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 === undefined) { events = target._events = Object.create(null); target._eventsCount = 0; } else { // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (events.newListener !== undefined) { target.emit('newListener', type, listener.listener ? listener.listener : listener); // Re-assign `events` because a newListener handler could have caused the // this._events to be assigned to a new object events = target._events; } existing = events[type]; } if (existing === undefined) { // Optimize the case of one listener. Don't need the extra array object. existing = events[type] = listener; ++target._eventsCount; } else { if (typeof existing === 'function') { // Adding the second element, need to change to array. existing = events[type] = prepend ? [listener, existing] : [existing, listener]; // If we've already got an array, just append. } else if (prepend) { existing.unshift(listener); } else { existing.push(listener); } // Check for listener leak m = _getMaxListeners(target); if (m > 0 && existing.length > m && !existing.warned) { existing.warned = true; // No error code for this since it is a Warning // eslint-disable-next-line no-restricted-syntax 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; } EventEmitter.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener, false); }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.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: undefined, target: target, type: type, listener: listener }; var wrapped = onceWrapper.bind(state); wrapped.listener = listener; state.wrapFn = wrapped; return wrapped; } EventEmitter.prototype.once = function once(type, listener) { checkListener(listener); this.on(type, _onceWrap(this, type, listener)); return this; }; EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) { checkListener(listener); this.prependListener(type, _onceWrap(this, type, listener)); return this; }; // Emits a 'removeListener' event if and only if the listener was removed. EventEmitter.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i, originalListener; checkListener(listener); events = this._events; if (events === undefined) return this; list = events[type]; if (list === undefined) return this; if (list === listener || list.listener === listener) { if (--this._eventsCount === 0) this._events = 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 !== undefined) this.emit('removeListener', type, originalListener || listener); } return this; }; EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) { var listeners, events, i; events = this._events; if (events === undefined) return this; // not listening for removeListener, no need to emit if (events.removeListener === undefined) { if (arguments.length === 0) { this._events = Object.create(null); this._eventsCount = 0; } else if (events[type] !== undefined) { if (--this._eventsCount === 0) this._events = Object.create(null);else delete events[type]; } return this; } // emit removeListener for all listeners on all events 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 = Object.create(null); this._eventsCount = 0; return this; } listeners = events[type]; if (typeof listeners === 'function') { this.removeListener(type, listeners); } else if (listeners !== undefined) { // LIFO order 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 === undefined) return []; var evlistener = events[type]; if (evlistener === undefined) return []; if (typeof evlistener === 'function') return unwrap ? [evlistener.listener || evlistener] : [evlistener]; return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length); } EventEmitter.prototype.listeners = function listeners(type) { return _listeners(this, type, true); }; EventEmitter.prototype.rawListeners = function rawListeners(type) { return _listeners(this, type, false); }; EventEmitter.listenerCount = function (emitter, type) { if (typeof emitter.listenerCount === 'function') { return emitter.listenerCount(type); } else { return listenerCount.call(emitter, type); } }; EventEmitter.prototype.listenerCount = listenerCount; function listenerCount(type) { var events = this._events; if (events !== undefined) { var evlistener = events[type]; if (typeof evlistener === 'function') { return 1; } else if (evlistener !== undefined) { return evlistener.length; } } return 0; } EventEmitter.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') { // EventTarget does not have `error` event semantics like Node // EventEmitters, we do not listen for `error` events here. emitter.addEventListener(name, function wrapListener(arg) { // IE does not have builtin `{ once: true }` support so we // have to do it manually. 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); } } /** * Obliterator Iterator Class * =========================== * * Simple class representing the library's iterators. */ /** * Iterator class. * * @constructor * @param {function} next - Next function. */ function Iterator$2(next) { if (typeof next !== 'function') throw new Error('obliterator/iterator: expecting a function!'); this.next = next; } /** * If symbols are supported, we add `next` to `Symbol.iterator`. */ if (typeof Symbol !== 'undefined') Iterator$2.prototype[Symbol.iterator] = function () { return this; }; /** * Returning an iterator of the given values. * * @param {any...} values - Values. * @return {Iterator} */ Iterator$2.of = function () { var args = arguments, l = args.length, i = 0; return new Iterator$2(function () { if (i >= l) return { done: true }; return { done: false, value: args[i++] }; }); }; /** * Returning an empty iterator. * * @return {Iterator} */ Iterator$2.empty = function () { var iterator = new Iterator$2(function () { return { done: true }; }); return iterator; }; /** * Returning an iterator over the given indexed sequence. * * @param {string|Array} sequence - Target sequence. * @return {Iterator} */ Iterator$2.fromSequence = function (sequence) { var i = 0, l = sequence.length; return new Iterator$2(function () { if (i >= l) return { done: true }; return { done: false, value: sequence[i++] }; }); }; /** * Returning whether the given value is an iterator. * * @param {any} value - Value. * @return {boolean} */ Iterator$2.is = function (value) { if (value instanceof Iterator$2) return true; return typeof value === 'object' && value !== null && typeof value.next === 'function'; }; /** * Exporting. */ var iterator = Iterator$2; var support$1 = {}; support$1.ARRAY_BUFFER_SUPPORT = typeof ArrayBuffer !== 'undefined'; support$1.SYMBOL_SUPPORT = typeof Symbol !== 'undefined'; /** * Obliterator Iter Function * ========================== * * Function coercing values to an iterator. It can be quite useful when needing * to handle iterables and iterators the same way. */ var Iterator$1 = iterator; var support = support$1; var ARRAY_BUFFER_SUPPORT = support.ARRAY_BUFFER_SUPPORT; var SYMBOL_SUPPORT = support.SYMBOL_SUPPORT; function iterOrNull(target) { // Indexed sequence if (typeof target === 'string' || Array.isArray(target) || ARRAY_BUFFER_SUPPORT && ArrayBuffer.isView(target)) return Iterator$1.fromSequence(target); // Invalid value if (typeof target !== 'object' || target === null) return null; // Iterable if (SYMBOL_SUPPORT && typeof target[Symbol.iterator] === 'function') return target[Symbol.iterator](); // Iterator duck-typing if (typeof target.next === 'function') return target; // Invalid object return null; } var iter$2 = function iter(target) { var iterator = iterOrNull(target); if (!iterator) throw new Error('obliterator: target is not iterable nor a valid iterator.'); return iterator; }; /* eslint no-constant-condition: 0 */ /** * Obliterator Take Function * ========================== * * Function taking n or every value of the given iterator and returns them * into an array. */ var iter$1 = iter$2; /** * Take. * * @param {Iterable} iterable - Target iterable. * @param {number} [n] - Optional number of items to take. * @return {array} */ var take = function take(iterable, n) { var l = arguments.length > 1 ? n : Infinity, array = l !== Infinity ? new Array(l) : [], step, i = 0; var iterator = iter$1(iterable); while (true) { if (i === l) return array; step = iterator.next(); if (step.done) { if (i !== n) array.length = i; return array; } array[i++] = step.value; } }; /** * Graphology Custom Errors * ========================= * * Defining custom errors for ease of use & easy unit tests across * implementations (normalized typology rather than relying on error * messages to check whether the correct error was found). */ var GraphError = /*#__PURE__*/function (_Error) { _inheritsLoose(GraphError, _Error); function GraphError(message) { var _this; _this = _Error.call(this) || this; _this.name = 'GraphError'; _this.message = message; return _this; } return GraphError; }( /*#__PURE__*/_wrapNativeSuper(Error)); var InvalidArgumentsGraphError = /*#__PURE__*/function (_GraphError) { _inheritsLoose(InvalidArgumentsGraphError, _GraphError); function InvalidArgumentsGraphError(message) { var _this2; _this2 = _GraphError.call(this, message) || this; _this2.name = 'InvalidArgumentsGraphError'; // This is V8 specific to enhance stack readability if (typeof Error.captureStackTrace === 'function') Error.captureStackTrace(_assertThisInitialized(_this2), InvalidArgumentsGraphError.prototype.constructor); return _this2; } return InvalidArgumentsGraphError; }(GraphError); var NotFoundGraphError = /*#__PURE__*/function (_GraphError2) { _inheritsLoose(NotFoundGraphError, _GraphError2); function NotFoundGraphError(message) { var _this3; _this3 = _GraphError2.call(this, message) || this; _this3.name = 'NotFoundGraphError'; // This is V8 specific to enhance stack readability if (typeof Error.captureStackTrace === 'function') Error.captureStackTrace(_assertThisInitialized(_this3), NotFoundGraphError.prototype.constructor); return _this3; } return NotFoundGraphError; }(GraphError); var UsageGraphError = /*#__PURE__*/function (_GraphError3) { _inheritsLoose(UsageGraphError, _GraphError3); function UsageGraphError(message) { var _this4; _this4 = _GraphError3.call(this, message) || this; _this4.name = 'UsageGraphError'; // This is V8 specific to enhance stack readability if (typeof Error.captureStackTrace === 'function') Error.captureStackTrace(_assertThisInitialized(_this4), UsageGraphError.prototype.constructor); return _this4; } return UsageGraphError; }(GraphError); /** * Graphology Internal Data Classes * ================================= * * Internal classes hopefully reduced to structs by engines & storing * necessary information for nodes & edges. * * Note that those classes don't rely on the `class` keyword to avoid some * cruft introduced by most of ES2015 transpilers. */ /** * MixedNodeData class. * * @constructor * @param {string} string - The node's key. * @param {object} attributes - Node's attributes. */ function MixedNodeData(key, attributes) { // Attributes this.key = key; this.attributes = attributes; this.clear(); } MixedNodeData.prototype.clear = function () { // Degrees this.inDegree = 0; this.outDegree = 0; this.undirectedDegree = 0; this.undirectedLoops = 0; this.directedLoops = 0; // Indices this["in"] = {}; this.out = {}; this.undirected = {}; }; /** * DirectedNodeData class. * * @constructor * @param {string} string - The node's key. * @param {object} attributes - Node's attributes. */ function DirectedNodeData(key, attributes) { // Attributes this.key = key; this.attributes = attributes; this.clear(); } DirectedNodeData.prototype.clear = function () { // Degrees this.inDegree = 0; this.outDegree = 0; this.directedLoops = 0; // Indices this["in"] = {}; this.out = {}; }; /** * UndirectedNodeData class. * * @constructor * @param {string} string - The node's key. * @param {object} attributes - Node's attributes. */ function UndirectedNodeData(key, attributes) { // Attributes this.key = key; this.attributes = attributes; this.clear(); } UndirectedNodeData.prototype.clear = function () { // Degrees this.undirectedDegree = 0; this.undirectedLoops = 0; // Indices this.undirected = {}; }; /** * EdgeData class. * * @constructor * @param {boolean} undirected - Whether the edge is undirected. * @param {string} string - The edge's key. * @param {string} source - Source of the edge. * @param {string} target - Target of the edge. * @param {object} attributes - Edge's attributes. */ function EdgeData(undirected, key, source, target, attributes) { // Attributes this.key = key; this.attributes = attributes; this.undirected = undirected; // Extremities this.source = source; this.target = target; } EdgeData.prototype.attach = function () { var outKey = 'out'; var inKey = 'in'; if (this.undirected) outKey = inKey = 'undirected'; var source = this.source.key; var target = this.target.key; // Handling source this.source[outKey][target] = this; if (this.undirected && source === target) return; // Handling target this.target[inKey][source] = this; }; EdgeData.prototype.attachMulti = function () { var outKey = 'out'; var inKey = 'in'; var source = this.source.key; var target = this.target.key; if (this.undirected) outKey = inKey = 'undirected'; // Handling source var adj = this.source[outKey]; var head = adj[target]; if (typeof head === 'undefined') { adj[target] = this; // Self-loop optimization if (!(this.undirected && source === target)) { // Handling target this.target[inKey][source] = this; } return; } // Prepending to doubly-linked list head.previous = this; this.next = head; // Pointing to new head // NOTE: use mutating swap later to avoid lookup? adj[target] = this; this.target[inKey][source] = this; }; EdgeData.prototype.detach = function () { var source = this.source.key; var target = this.target.key; var outKey = 'out'; var inKey = 'in'; if (this.undirected) outKey = inKey = 'undirected'; delete this.source[outKey][target]; // No-op delete in case of undirected self-loop delete this.target[inKey][source]; }; EdgeData.prototype.detachMulti = function () { var source = this.source.key; var target = this.target.key; var outKey = 'out'; var inKey = 'in'; if (this.undirected) outKey = inKey = 'undirected'; // Deleting from doubly-linked list if (this.previous === undefined) { // We are dealing with the head // Should we delete the adjacency entry because it is now empty? if (this.next === undefined) { delete this.source[outKey][target]; // No-op delete in case of undirected self-loop delete this.target[inKey][source]; } else { // Detaching this.next.previous = undefined; // NOTE: could avoid the lookups by creating a #.become mutating method this.source[outKey][target] = this.next; // No-op delete in case of undirected self-loop this.target[inKey][source] = this.next; } } else { // We are dealing with another list node this.previous.next = this.next; // If not last if (this.next !== undefined) { this.next.previous = this.previous; } } }; /** * Graphology Node Attributes methods * =================================== */ var NODE = 0; var SOURCE = 1; var TARGET = 2; var OPPOSITE = 3; function findRelevantNodeData(graph, method, mode, nodeOrEdge, nameOrEdge, add1, add2) { var nodeData, edgeData, arg1, arg2; nodeOrEdge = '' + nodeOrEdge; if (mode === NODE) { nodeData = graph._nodes.get(nodeOrEdge); if (!nodeData) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(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.".concat(method, ": could not find the \"").concat(nameOrEdge, "\" edge in the graph.")); var source = edgeData.source.key; var target = edgeData.target.key; if (nodeOrEdge === source) { nodeData = edgeData.target; } else if (nodeOrEdge === target) { nodeData = edgeData.source; } else { throw new NotFoundGraphError("Graph.".concat(method, ": the \"").concat(nodeOrEdge, "\" node is not attached to the \"").concat(nameOrEdge, "\" edge (").concat(source, ", ").concat(target, ").")); } arg1 = add1; arg2 = add2; } else { edgeData = graph._edges.get(nodeOrEdge); if (!edgeData) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(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) { var _findRelevantNodeData = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1), data = _findRelevantNodeData[0], name = _findRelevantNodeData[1]; return data.attributes[name]; }; } function attachNodeAttributesGetter(Class, method, mode) { Class.prototype[method] = function (nodeOrEdge, nameOrEdge) { var _findRelevantNodeData2 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge), data = _findRelevantNodeData2[0]; return data.attributes; }; } function attachNodeAttributeChecker(Class, method, mode) { Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) { var _findRelevantNodeData3 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1), data = _findRelevantNodeData3[0], name = _findRelevantNodeData3[1]; return data.attributes.hasOwnProperty(name); }; } function attachNodeAttributeSetter(Class, method, mode) { Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1, add2) { var _findRelevantNodeData4 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1, add2), data = _findRelevantNodeData4[0], name = _findRelevantNodeData4[1], value = _findRelevantNodeData4[2]; data.attributes[name] = value; // Emitting this.emit('nodeAttributesUpdated', { key: data.key, type: 'set', attributes: data.attributes, name: name }); return this; }; } function attachNodeAttributeUpdater(Class, method, mode) { Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1, add2) { var _findRelevantNodeData5 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1, add2), data = _findRelevantNodeData5[0], name = _findRelevantNodeData5[1], updater = _findRelevantNodeData5[2]; if (typeof updater !== 'function') throw new InvalidArgumentsGraphError("Graph.".concat(method, ": updater should be a function.")); var attributes = data.attributes; var value = updater(attributes[name]); attributes[name] = value; // Emitting this.emit('nodeAttributesUpdated', { key: data.key, type: 'set', attributes: data.attributes, name: name }); return this; }; } function attachNodeAttributeRemover(Class, method, mode) { Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) { var _findRelevantNodeData6 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1), data = _findRelevantNodeData6[0], name = _findRelevantNodeData6[1]; delete data.attributes[name]; // Emitting this.emit('nodeAttributesUpdated', { key: data.key, type: 'remove', attributes: data.attributes, name: name }); return this; }; } function attachNodeAttributesReplacer(Class, method, mode) { Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) { var _findRelevantNodeData7 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1), data = _findRelevantNodeData7[0], attributes = _findRelevantNodeData7[1]; if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.".concat(method, ": provided attributes are not a plain object.")); data.attributes = attributes; // Emitting 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) { var _findRelevantNodeData8 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1), data = _findRelevantNodeData8[0], attributes = _findRelevantNodeData8[1]; if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.".concat(method, ": provided attributes are not a plain object.")); assign(data.attributes, attributes); // Emitting 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) { var _findRelevantNodeData9 = findRelevantNodeData(this, method, mode, nodeOrEdge, nameOrEdge, add1), data = _findRelevantNodeData9[0], updater = _findRelevantNodeData9[1]; if (typeof updater !== 'function') throw new InvalidArgumentsGraphError("Graph.".concat(method, ": provided updater is not a function.")); data.attributes = updater(data.attributes); // Emitting this.emit('nodeAttributesUpdated', { key: data.key, type: 'update', attributes: data.attributes }); return this; }; } /** * List of methods to attach. */ var NODE_ATTRIBUTES_METHODS = [{ name: function name(element) { return "get".concat(element, "Attribute"); }, attacher: attachNodeAttributeGetter }, { name: function name(element) { return "get".concat(element, "Attributes"); }, attacher: attachNodeAttributesGetter }, { name: function name(element) { return "has".concat(element, "Attribute"); }, attacher: attachNodeAttributeChecker }, { name: function name(element) { return "set".concat(element, "Attribute"); }, attacher: attachNodeAttributeSetter }, { name: function name(element) { return "update".concat(element, "Attribute"); }, attacher: attachNodeAttributeUpdater }, { name: function name(element) { return "remove".concat(element, "Attribute"); }, attacher: attachNodeAttributeRemover }, { name: function name(element) { return "replace".concat(element, "Attributes"); }, attacher: attachNodeAttributesReplacer }, { name: function name(element) { return "merge".concat(element, "Attributes"); }, attacher: attachNodeAttributesMerger }, { name: function name(element) { return "update".concat(element, "Attributes"); }, attacher: attachNodeAttributesUpdater }]; /** * Attach every attributes-related methods to a Graph class. * * @param {function} Graph - Target class. */ function attachNodeAttributesMethods(Graph) { NODE_ATTRIBUTES_METHODS.forEach(function (_ref) { var name = _ref.name, attacher = _ref.attacher; // For nodes attacher(Graph, name('Node'), NODE); // For sources attacher(Graph, name('Source'), SOURCE); // For targets attacher(Graph, name('Target'), TARGET); // For opposites attacher(Graph, name('Opposite'), OPPOSITE); }); } /** * Graphology Edge Attributes methods * =================================== */ /** * Attach an attribute getter method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributeGetter(Class, method, type) { /** * Get the desired attribute for the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {string} name - Attribute's name. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {string} name - Attribute's name. * * @return {mixed} - The attribute's value. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, name) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 2) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element; var target = '' + name; name = arguments[2]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } return data.attributes[name]; }; } /** * Attach an attributes getter method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributesGetter(Class, method, type) { /** * Retrieves all the target element's attributes. * * Arity 2: * @param {any} element - Target element. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * * @return {object} - The element's attributes. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 1) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element, target = '' + arguments[1]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } return data.attributes; }; } /** * Attach an attribute checker method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributeChecker(Class, method, type) { /** * Checks whether the desired attribute is set for the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {string} name - Attribute's name. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {string} name - Attribute's name. * * @return {boolean} * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, name) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 2) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element; var target = '' + name; name = arguments[2]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } return data.attributes.hasOwnProperty(name); }; } /** * Attach an attribute setter method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributeSetter(Class, method, type) { /** * Set the desired attribute for the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {string} name - Attribute's name. * @param {mixed} value - New attribute value. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {string} name - Attribute's name. * @param {mixed} value - New attribute value. * * @return {Graph} - Returns itself for chaining. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, name, value) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 3) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element; var target = '' + name; name = arguments[2]; value = arguments[3]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } data.attributes[name] = value; // Emitting this.emit('edgeAttributesUpdated', { key: data.key, type: 'set', attributes: data.attributes, name: name }); return this; }; } /** * Attach an attribute updater method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributeUpdater(Class, method, type) { /** * Update the desired attribute for the given element (node or edge) using * the provided function. * * Arity 2: * @param {any} element - Target element. * @param {string} name - Attribute's name. * @param {function} updater - Updater function. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {string} name - Attribute's name. * @param {function} updater - Updater function. * * @return {Graph} - Returns itself for chaining. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, name, updater) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 3) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element; var target = '' + name; name = arguments[2]; updater = arguments[3]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } if (typeof updater !== 'function') throw new InvalidArgumentsGraphError("Graph.".concat(method, ": updater should be a function.")); data.attributes[name] = updater(data.attributes[name]); // Emitting this.emit('edgeAttributesUpdated', { key: data.key, type: 'set', attributes: data.attributes, name: name }); return this; }; } /** * Attach an attribute remover method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributeRemover(Class, method, type) { /** * Remove the desired attribute for the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {string} name - Attribute's name. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {string} name - Attribute's name. * * @return {Graph} - Returns itself for chaining. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, name) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 2) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element; var target = '' + name; name = arguments[2]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } delete data.attributes[name]; // Emitting this.emit('edgeAttributesUpdated', { key: data.key, type: 'remove', attributes: data.attributes, name: name }); return this; }; } /** * Attach an attribute replacer method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributesReplacer(Class, method, type) { /** * Replace the attributes for the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {object} attributes - New attributes. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {object} attributes - New attributes. * * @return {Graph} - Returns itself for chaining. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, attributes) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 2) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element, target = '' + attributes; attributes = arguments[2]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.".concat(method, ": provided attributes are not a plain object.")); data.attributes = attributes; // Emitting this.emit('edgeAttributesUpdated', { key: data.key, type: 'replace', attributes: data.attributes }); return this; }; } /** * Attach an attribute merger method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributesMerger(Class, method, type) { /** * Merge the attributes for the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {object} attributes - Attributes to merge. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {object} attributes - Attributes to merge. * * @return {Graph} - Returns itself for chaining. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, attributes) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 2) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element, target = '' + attributes; attributes = arguments[2]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.".concat(method, ": provided attributes are not a plain object.")); assign(data.attributes, attributes); // Emitting this.emit('edgeAttributesUpdated', { key: data.key, type: 'merge', attributes: data.attributes, data: attributes }); return this; }; } /** * Attach an attribute updater method onto the provided class. * * @param {function} Class - Target class. * @param {string} method - Method name. * @param {string} type - Type of the edge to find. */ function attachEdgeAttributesUpdater(Class, method, type) { /** * Update the attributes of the given element (node or edge). * * Arity 2: * @param {any} element - Target element. * @param {function} updater - Updater function. * * Arity 3 (only for edges): * @param {any} source - Source element. * @param {any} target - Target element. * @param {function} updater - Updater function. * * @return {Graph} - Returns itself for chaining. * * @throws {Error} - Will throw if too many arguments are provided. * @throws {Error} - Will throw if any of the elements is not found. */ Class.prototype[method] = function (element, updater) { var data; if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type) throw new UsageGraphError("Graph.".concat(method, ": cannot find this type of edges in your ").concat(this.type, " graph.")); if (arguments.length > 2) { if (this.multi) throw new UsageGraphError("Graph.".concat(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.")); var source = '' + element, target = '' + updater; updater = arguments[2]; data = getMatchingEdge(this, source, target, type); if (!data) throw new NotFoundGraphError("Graph.".concat(method, ": could not find an edge for the given path (\"").concat(source, "\" - \"").concat(target, "\").")); } else { if (type !== 'mixed') throw new UsageGraphError("Graph.".concat(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.".concat(method, ": could not find the \"").concat(element, "\" edge in the graph.")); } if (typeof updater !== 'function') throw new InvalidArgumentsGraphError("Graph.".concat(method, ": provided updater is not a function.")); data.attributes = updater(data.attributes); // Emitting this.emit('edgeAttributesUpdated', { key: data.key, type: 'update', attributes: data.attributes }); return this; }; } /** * List of methods to attach. */ var EDGE_ATTRIBUTES_METHODS = [{ name: function name(element) { return "get".concat(element, "Attribute"); }, attacher: attachEdgeAttributeGetter }, { name: function name(element) { return "get".concat(element, "Attributes"); }, attacher: attachEdgeAttributesGetter }, { name: function name(element) { return "has".concat(element, "Attribute"); }, attacher: attachEdgeAttributeChecker }, { name: function name(element) { return "set".concat(element, "Attribute"); }, attacher: attachEdgeAttributeSetter }, { name: function name(element) { return "update".concat(element, "Attribute"); }, attacher: attachEdgeAttributeUpdater }, { name: function name(element) { return "remove".concat(element, "Attribute"); }, attacher: attachEdgeAttributeRemover }, { name: function name(element) { return "replace".concat(element, "Attributes"); }, attacher: attachEdgeAttributesReplacer }, { name: function name(element) { return "merge".concat(element, "Attributes"); }, attacher: attachEdgeAttributesMerger }, { name: function name(element) { return "update".concat(element, "Attributes"); }, attacher: attachEdgeAttributesUpdater }]; /** * Attach every attributes-related methods to a Graph class. * * @param {function} Graph - Target class. */ function attachEdgeAttributesMethods(Graph) { EDGE_ATTRIBUTES_METHODS.forEach(function (_ref) { var name = _ref.name, attacher = _ref.attacher; // For edges attacher(Graph, name('Edge'), 'mixed'); // For directed edges attacher(Graph, name('DirectedEdge'), 'directed'); // For undirected edges attacher(Graph, name('UndirectedEdge'), 'undirected'); }); } /** * Obliterator Chain Function * =========================== * * Variadic function combining the given iterables. */ var Iterator = iterator; var iter = iter$2; /** * Chain. * * @param {...Iterator} iterables - Target iterables. * @return {Iterator} */ var chain = function chain() { var iterables = arguments; var current = null; var i = -1; /* eslint-disable no-constant-condition */ return new Iterator(function next() { var step = null; do { if (current === null) { i++; if (i >= iterables.length) return { done: true }; current = iter(iterables[i]); } step = current.next(); if (step.done === true) { current = null; continue; } break; } while (true); return step; }); }; /** * Graphology Edge Iteration * ========================== * * Attaching some methods to the Graph class to be able to iterate over a * graph's edges. */ /** * Definitions. */ 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 iterating over edges from the given object to match one of them. * * @param {object} object - Target object. * @param {function} callback - Function to call. */ function forEachSimple(breakable, object, callback, avoid) { var shouldBreak = false; for (var k in object) { if (k === avoid) continue; var 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) { var edgeData, source, target; var shouldBreak = false; for (var 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 !== undefined); } return; } /** * Function returning an iterator over edges from the given object. * * @param {object} object - Target object. * @return {Iterator} */ function createIterator(object, avoid) { var keys = Object.keys(object); var l = keys.length; var edgeData; var i = 0; return new iterator(function next() { do { if (!edgeData) { if (i >= l) return { done: true }; var k = keys[i++]; if (k === avoid) { edgeData = undefined; 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 iterating over the egdes from the object at given key to match * one of them. * * @param {object} object - Target object. * @param {mixed} k - Neighbor key. * @param {function} callback - Callback to use. */ function forEachForKeySimple(breakable, object, k, callback) { var edgeData = object[k]; if (!edgeData) return; var sourceData = edgeData.source; var 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) { var edgeData = object[k]; if (!edgeData) return; var 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 !== undefined); return; } /** * Function returning an iterator over the egdes from the object at given key. * * @param {object} object - Target object. * @param {mixed} k - Neighbor key. * @return {Iterator} */ function createIteratorForKey(object, k) { var edgeData = object[k]; if (edgeData.next !== undefined) { return new iterator(function () { if (!edgeData) return { done: true }; var 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: value }; }); } return iterator.of({ 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 creating an array of edges for the given type. * * @param {Graph} graph - Target Graph instance. * @param {string} type - Type of edges to retrieve. * @return {array} - Array of edges. */ function createEdgeArray(graph, type) { if (graph.size === 0) return []; if (type === 'mixed' || type === graph.type) { if (typeof Array.from === 'function') return Array.from(graph._edges.keys()); return take(graph._edges.keys(), graph._edges.size); } var size = type === 'undirected' ? graph.undirectedSize : graph.directedSize; var list = new Array(size), mask = type === 'undirected'; var iterator = graph._edges.values(); var i = 0; var step, data; while (step = iterator.next(), step.done !== true) { data = step.value; if (data.undirected === mask) list[i++] = data.key; } return list; } /** * Function iterating over a graph's edges using a callback to match one of * them. * * @param {Graph} graph - Target Graph instance. * @param {string} type - Type of edges to retrieve. * @param {function} callback - Function to call. */ function forEachEdge(breakable, graph, type, callback) { if (graph.size === 0) return; var shouldFilter = type !== 'mixed' && type !== graph.type; var mask = type === 'undirected'; var step, data; var shouldBreak = false; var iterator = graph._edges.values(); while (step = iterator.next(), step.done !== true) { data = step.value; if (shouldFilter && data.undirected !== mask) continue; var _data = data, key = _data.key, attributes = _data.attributes, source = _data.source, target = _data.target; shouldBreak = callback(key, attributes, source.key, target.key, source.attributes, target.attributes, data.undirected); if (breakable && shouldBreak) return key; } return; } /** * Function creating an iterator of edges for the given type. * * @param {Graph} graph - Target Graph instance. * @param {string} type - Type of edges to retrieve. * @return {Iterator} */ function createEdgeIterator(graph, type) { if (graph.size === 0) return iterator.empty(); var shouldFilter = type !== 'mixed' && type !== graph.type; var mask = type === 'undirected'; var iterator$1 = graph._edges.values(); return new iterator(function next() { var step, data; // eslint-disable-next-line no-constant-condition while (true) { step = iterator$1.next(); if (step.done) return step; data = step.value; if (shouldFilter && data.undirected !== mask) continue; break; } var 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: value, done: false }; }); } /** * Function iterating over a node's edges using a callback to match one of them. * * @param {boolean} multi - Whether the graph is multi or not. * @param {string} type - Type of edges to retrieve. * @param {string} direction - In or out? * @param {any} nodeData - Target node's data. * @param {function} callback - Function to call. */ function forEachEdgeForNode(breakable, multi, type, direction, nodeData, callback) { var fn = multi ? forEachMulti : forEachSimple; var 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 : undefined); if (breakable && found) return found; } } if (type !== 'directed') { found = fn(breakable, nodeData.undirected, callback); if (breakable && found) return found; } return; } /** * Function creating an array of edges for the given type & the given node. * * @param {boolean} multi - Whether the graph is multi or not. * @param {string} type - Type of edges to retrieve. * @param {string} direction - In or out? * @param {any} nodeData - Target node's data. * @return {array} - Array of edges. */ function createEdgeArrayForNode(multi, type, direction, nodeData) { var edges = []; // TODO: possibility to know size beforehand or factorize with map forEachEdgeForNode(false, multi, type, direction, nodeData, function (key) { edges.push(key); }); return edges; } /** * Function iterating over a node's edges using a callback. * * @param {string} type - Type of edges to retrieve. * @param {string} direction - In or out? * @param {any} nodeData - Target node's data. * @return {Iterator} */ function createEdgeIteratorForNode(type, direction, nodeData) { var iterator$1 = iterator.empty(); if (type !== 'undirected') { if (direction !== 'out' && typeof nodeData["in"] !== 'undefined') iterator$1 = chain(iterator$1, createIterator(nodeData["in"])); if (direction !== 'in' && typeof nodeData.out !== 'undefined') iterator$1 = chain(iterator$1, createIterator(nodeData.out, !direction ? nodeData.key : undefined)); } if (type !== 'directed' && typeof nodeData.undirected !== 'undefined') { iterator$1 = chain(iterator$1, createIterator(nodeData.undirected)); } return iterator$1; } /** * Function iterating over edges for the given path using a callback to match * one of them. * * @param {string} type - Type of edges to retrieve. * @param {boolean} multi - Whether the graph is multi. * @param {string} direction - In or out? * @param {NodeData} sourceData - Source node's data. * @param {string} target - Target node. * @param {function} callback - Function to call. */ function forEachEdgeForPath(breakable, type, multi, direction, sourceData, target, callback) { var fn = multi ? forEachForKeyMulti : forEachForKeySimple; var 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 creating an array of edges for the given path. * * @param {string} type - Type of edges to retrieve. * @param {boolean} multi - Whether the graph is multi. * @param {string} direction - In or out? * @param {NodeData} sourceData - Source node's data. * @param {any} target - Target node. * @return {array} - Array of edges. */ function createEdgeArrayForPath(type, multi, direction, sourceData, target) { var edges = []; // TODO: possibility to know size beforehand or factorize with map forEachEdgeForPath(false, type, multi, direction, sourceData, target, function (key) { edges.push(key); }); return edges; } /** * Function returning an iterator over edges for the given path. * * @param {string} type - Type of edges to retrieve. * @param {string} direction - In or out? * @param {NodeData} sourceData - Source node's data. * @param {string} target - Target node. * @param {function} callback - Function to call. */ function createEdgeIteratorForPath(type, direction, sourceData, target) { var iterator$1 = iterator.empty(); if (type !== 'undirected') { if (typeof sourceData["in"] !== 'undefined' && direction !== 'out' && target in sourceData["in"]) iterator$1 = chain(iterator$1, createIteratorForKey(sourceData["in"], target)); if (typeof sourceData.out !== 'undefined' && direction !== 'in' && target in sourceData.out && (direction || sourceData.key !== target)) iterator$1 = chain(iterator$1, createIteratorForKey(sourceData.out, target)); } if (type !== 'directed') { if (typeof sourceData.undirected !== 'undefined' && target in sourceData.undirected) iterator$1 = chain(iterator$1, createIteratorForKey(sourceData.undirected, target)); } return iterator$1; } /** * Function attaching an edge array creator method to the Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachEdgeArrayCreator(Class, description) { var name = description.name, type = description.type, direction = description.direction; /** * Function returning an array of certain edges. * * Arity 0: Return all the relevant edges. * * Arity 1: Return all of a node's relevant edges. * @param {any} node - Target node. * * Arity 2: Return the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * * @return {array|number} - The edges or the number of edges. * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[name] = function (source, target) { // Early termination if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type) return []; if (!arguments.length) return createEdgeArray(this, type); if (arguments.length === 1) { source = '' + source; var nodeData = this._nodes.get(source); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(source, "\" node in the graph.")); // Iterating over a node's edges return createEdgeArrayForNode(this.multi, type === 'mixed' ? this.type : type, direction, nodeData); } if (arguments.length === 2) { source = '' + source; target = '' + target; var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(target, "\" target node in the graph.")); // Iterating over the edges between source & target return createEdgeArrayForPath(type, this.multi, direction, sourceData, target); } throw new InvalidArgumentsGraphError("Graph.".concat(name, ": too many arguments (expecting 0, 1 or 2 and got ").concat(arguments.length, ").")); }; } /** * Function attaching a edge callback iterator method to the Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachForEachEdge(Class, description) { var name = description.name, type = description.type, direction = description.direction; var forEachName = 'forEach' + name[0].toUpperCase() + name.slice(1, -1); /** * Function iterating over the graph's relevant edges by applying the given * callback. * * Arity 1: Iterate over all the relevant edges. * @param {function} callback - Callback to use. * * Arity 2: Iterate over all of a node's relevant edges. * @param {any} node - Target node. * @param {function} callback - Callback to use. * * Arity 3: Iterate over the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} callback - Callback to use. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[forEachName] = function (source, target, callback) { // Early termination 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; var nodeData = this._nodes.get(source); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(forEachName, ": could not find the \"").concat(source, "\" node in the graph.")); // Iterating over a node's edges // TODO: maybe attach the sub method to the instance dynamically? return forEachEdgeForNode(false, this.multi, type === 'mixed' ? this.type : type, direction, nodeData, callback); } if (arguments.length === 3) { source = '' + source; target = '' + target; var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.".concat(forEachName, ": could not find the \"").concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.".concat(forEachName, ": could not find the \"").concat(target, "\" target node in the graph.")); // Iterating over the edges between source & target return forEachEdgeForPath(false, type, this.multi, direction, sourceData, target, callback); } throw new InvalidArgumentsGraphError("Graph.".concat(forEachName, ": too many arguments (expecting 1, 2 or 3 and got ").concat(arguments.length, ").")); }; /** * Function mapping the graph's relevant edges by applying the given * callback. * * Arity 1: Map all the relevant edges. * @param {function} callback - Callback to use. * * Arity 2: Map all of a node's relevant edges. * @param {any} node - Target node. * @param {function} callback - Callback to use. * * Arity 3: Map the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} callback - Callback to use. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ var mapName = 'map' + name[0].toUpperCase() + name.slice(1); Class.prototype[mapName] = function () { var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var result; // We know the result length beforehand if (args.length === 0) { var length = 0; if (type !== 'directed') length += this.undirectedSize; if (type !== 'undirected') length += this.directedSize; result = new Array(length); var i = 0; args.push(function (e, ea, s, t, sa, ta, u) { result[i++] = callback(e, ea, s, t, sa, ta, u); }); } // We don't know the result length beforehand // TODO: we can in some instances of simple graphs, knowing degree else { result = []; args.push(function (e, ea, s, t, sa, ta, u) { result.push(callback(e, ea, s, t, sa, ta, u)); }); } this[forEachName].apply(this, args); return result; }; /** * Function filtering the graph's relevant edges using the provided predicate * function. * * Arity 1: Filter all the relevant edges. * @param {function} predicate - Predicate to use. * * Arity 2: Filter all of a node's relevant edges. * @param {any} node - Target node. * @param {function} predicate - Predicate to use. * * Arity 3: Filter the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} predicate - Predicate to use. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ var filterName = 'filter' + name[0].toUpperCase() + name.slice(1); Class.prototype[filterName] = function () { var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var result = []; args.push(function (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; }; /** * Function reducing the graph's relevant edges using the provided accumulator * function. * * Arity 1: Reduce all the relevant edges. * @param {function} accumulator - Accumulator to use. * @param {any} initialValue - Initial value. * * Arity 2: Reduce all of a node's relevant edges. * @param {any} node - Target node. * @param {function} accumulator - Accumulator to use. * @param {any} initialValue - Initial value. * * Arity 3: Reduce the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} accumulator - Accumulator to use. * @param {any} initialValue - Initial value. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ var reduceName = 'reduce' + name[0].toUpperCase() + name.slice(1); Class.prototype[reduceName] = function () { var args = Array.prototype.slice.call(arguments); if (args.length < 2 || args.length > 4) { throw new InvalidArgumentsGraphError("Graph.".concat(reduceName, ": invalid number of arguments (expecting 2, 3 or 4 and got ").concat(args.length, ").")); } if (typeof args[args.length - 1] === 'function' && typeof args[args.length - 2] !== 'function') { throw new InvalidArgumentsGraphError("Graph.".concat(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.")); } var callback; var 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]]; } var accumulator = initialValue; args.push(function (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 attaching a breakable edge callback iterator method to the Graph * prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachFindEdge(Class, description) { var name = description.name, type = description.type, direction = description.direction; var findEdgeName = 'find' + name[0].toUpperCase() + name.slice(1, -1); /** * Function iterating over the graph's relevant edges in order to match * one of them using the provided predicate function. * * Arity 1: Iterate over all the relevant edges. * @param {function} callback - Callback to use. * * Arity 2: Iterate over all of a node's relevant edges. * @param {any} node - Target node. * @param {function} callback - Callback to use. * * Arity 3: Iterate over the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} callback - Callback to use. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[findEdgeName] = function (source, target, callback) { // Early termination 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; var nodeData = this._nodes.get(source); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(findEdgeName, ": could not find the \"").concat(source, "\" node in the graph.")); // Iterating over a node's edges // TODO: maybe attach the sub method to the instance dynamically? return forEachEdgeForNode(true, this.multi, type === 'mixed' ? this.type : type, direction, nodeData, callback); } if (arguments.length === 3) { source = '' + source; target = '' + target; var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.".concat(findEdgeName, ": could not find the \"").concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.".concat(findEdgeName, ": could not find the \"").concat(target, "\" target node in the graph.")); // Iterating over the edges between source & target return forEachEdgeForPath(true, type, this.multi, direction, sourceData, target, callback); } throw new InvalidArgumentsGraphError("Graph.".concat(findEdgeName, ": too many arguments (expecting 1, 2 or 3 and got ").concat(arguments.length, ").")); }; /** * Function iterating over the graph's relevant edges in order to assert * whether any one of them matches the provided predicate function. * * Arity 1: Iterate over all the relevant edges. * @param {function} callback - Callback to use. * * Arity 2: Iterate over all of a node's relevant edges. * @param {any} node - Target node. * @param {function} callback - Callback to use. * * Arity 3: Iterate over the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} callback - Callback to use. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ var someName = 'some' + name[0].toUpperCase() + name.slice(1, -1); Class.prototype[someName] = function () { var args = Array.prototype.slice.call(arguments); var callback = args.pop(); args.push(function (e, ea, s, t, sa, ta, u) { return callback(e, ea, s, t, sa, ta, u); }); var found = this[findEdgeName].apply(this, args); if (found) return true; return false; }; /** * Function iterating over the graph's relevant edges in order to assert * whether all of them matche the provided predicate function. * * Arity 1: Iterate over all the relevant edges. * @param {function} callback - Callback to use. * * Arity 2: Iterate over all of a node's relevant edges. * @param {any} node - Target node. * @param {function} callback - Callback to use. * * Arity 3: Iterate over the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * @param {function} callback - Callback to use. * * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ var everyName = 'every' + name[0].toUpperCase() + name.slice(1, -1); Class.prototype[everyName] = function () { var args = Array.prototype.slice.call(arguments); var callback = args.pop(); args.push(function (e, ea, s, t, sa, ta, u) { return !callback(e, ea, s, t, sa, ta, u); }); var found = this[findEdgeName].apply(this, args); if (found) return false; return true; }; } /** * Function attaching an edge iterator method to the Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachEdgeIteratorCreator(Class, description) { var originalName = description.name, type = description.type, direction = description.direction; var name = originalName.slice(0, -1) + 'Entries'; /** * Function returning an iterator over the graph's edges. * * Arity 0: Iterate over all the relevant edges. * * Arity 1: Iterate over all of a node's relevant edges. * @param {any} node - Target node. * * Arity 2: Iterate over the relevant edges across the given path. * @param {any} source - Source node. * @param {any} target - Target node. * * @return {array|number} - The edges or the number of edges. * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[name] = function (source, target) { // Early termination if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type) return iterator.empty(); if (!arguments.length) return createEdgeIterator(this, type); if (arguments.length === 1) { source = '' + source; var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(source, "\" node in the graph.")); // Iterating over a node's edges return createEdgeIteratorForNode(type, direction, sourceData); } if (arguments.length === 2) { source = '' + source; target = '' + target; var _sourceData = this._nodes.get(source); if (!_sourceData) throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(target, "\" target node in the graph.")); // Iterating over the edges between source & target return createEdgeIteratorForPath(type, direction, _sourceData, target); } throw new InvalidArgumentsGraphError("Graph.".concat(name, ": too many arguments (expecting 0, 1 or 2 and got ").concat(arguments.length, ").")); }; } /** * Function attaching every edge iteration method to the Graph class. * * @param {function} Graph - Graph class. */ function attachEdgeIterationMethods(Graph) { EDGES_ITERATION.forEach(function (description) { attachEdgeArrayCreator(Graph, description); attachForEachEdge(Graph, description); attachFindEdge(Graph, description); attachEdgeIteratorCreator(Graph, description); }); } /** * Graphology Neighbor Iteration * ============================== * * Attaching some methods to the Graph class to be able to iterate over * neighbors. */ /** * Definitions. */ 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' }]; /** * Helpers. */ 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 iterating over the given node's relevant neighbors to match * one of them using a predicated function. * * @param {string} type - Type of neighbors. * @param {string} direction - Direction. * @param {any} nodeData - Target node's data. * @param {function} callback - Callback to use. */ function forEachInObjectOnce(breakable, visited, nodeData, object, callback) { for (var k in object) { var edgeData = object[k]; var sourceData = edgeData.source; var targetData = edgeData.target; var neighborData = sourceData === nodeData ? targetData : sourceData; if (visited && visited.has(neighborData.key)) continue; var shouldBreak = callback(neighborData.key, neighborData.attributes); if (breakable && shouldBreak) return neighborData.key; } return; } function forEachNeighbor(breakable, type, direction, nodeData, callback) { // If we want only undirected or in or out, we can roll some optimizations 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); } // Else we need to keep a set of neighbors not to return duplicates // We cheat by querying the other adjacencies var visited = new CompositeSetWrapper(); var 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 creating an array of relevant neighbors for the given node. * * @param {string} type - Type of neighbors. * @param {string} direction - Direction. * @param {any} nodeData - Target node's data. * @return {Array} - The list of neighbors. */ function createNeighborArrayForNode(type, direction, nodeData) { // If we want only undirected or in or out, we can roll some optimizations if (type !== 'mixed') { if (type === 'undirected') return Object.keys(nodeData.undirected); if (typeof direction === 'string') return Object.keys(nodeData[direction]); } var neighbors = []; forEachNeighbor(false, type, direction, nodeData, function (key) { neighbors.push(key); }); return neighbors; } /** * Function returning an iterator over the given node's relevant neighbors. * * @param {string} type - Type of neighbors. * @param {string} direction - Direction. * @param {any} nodeData - Target node's data. * @return {Iterator} */ function createDedupedObjectIterator(visited, nodeData, object) { var keys = Object.keys(object); var l = keys.length; var i = 0; return new iterator(function next() { var neighborData = null; do { if (i >= l) { if (visited) visited.wrap(object); return { done: true }; } var edgeData = object[keys[i++]]; var sourceData = edgeData.source; var 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 we want only undirected or in or out, we can roll some optimizations if (type !== 'mixed') { if (type === 'undirected') return createDedupedObjectIterator(null, nodeData, nodeData.undirected); if (typeof direction === 'string') return createDedupedObjectIterator(null, nodeData, nodeData[direction]); } var iterator$1 = iterator.empty(); // Else we need to keep a set of neighbors not to return duplicates // We cheat by querying the other adjacencies var visited = new CompositeSetWrapper(); if (type !== 'undirected') { if (direction !== 'out') { iterator$1 = chain(iterator$1, createDedupedObjectIterator(visited, nodeData, nodeData["in"])); } if (direction !== 'in') { iterator$1 = chain(iterator$1, createDedupedObjectIterator(visited, nodeData, nodeData.out)); } } if (type !== 'directed') { iterator$1 = chain(iterator$1, createDedupedObjectIterator(visited, nodeData, nodeData.undirected)); } return iterator$1; } /** * Function attaching a neighbors array creator method to the Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachNeighborArrayCreator(Class, description) { var name = description.name, type = description.type, direction = description.direction; /** * Function returning an array of certain neighbors. * * @param {any} node - Target node. * @return {array} - The neighbors of neighbors. * * @throws {Error} - Will throw if node is not found in the graph. */ Class.prototype[name] = function (node) { // Early termination if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type) return []; node = '' + node; var nodeData = this._nodes.get(node); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(name, ": could not find the \"").concat(node, "\" node in the graph.")); // Here, we want to iterate over a node's relevant neighbors return createNeighborArrayForNode(type === 'mixed' ? this.type : type, direction, nodeData); }; } /** * Function attaching a neighbors callback iterator method to the Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachForEachNeighbor(Class, description) { var name = description.name, type = description.type, direction = description.direction; var forEachName = 'forEach' + name[0].toUpperCase() + name.slice(1, -1); /** * Function iterating over all the relevant neighbors using a callback. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[forEachName] = function (node, callback) { // Early termination if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type) return; node = '' + node; var nodeData = this._nodes.get(node); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(forEachName, ": could not find the \"").concat(node, "\" node in the graph.")); // Here, we want to iterate over a node's relevant neighbors forEachNeighbor(false, type === 'mixed' ? this.type : type, direction, nodeData, callback); }; /** * Function mapping the relevant neighbors using a callback. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * * @throws {Error} - Will throw if there are too many arguments. */ var mapName = 'map' + name[0].toUpperCase() + name.slice(1); Class.prototype[mapName] = function (node, callback) { // TODO: optimize when size is known beforehand var result = []; this[forEachName](node, function (n, a) { result.push(callback(n, a)); }); return result; }; /** * Function filtering the relevant neighbors using a callback. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * * @throws {Error} - Will throw if there are too many arguments. */ var filterName = 'filter' + name[0].toUpperCase() + name.slice(1); Class.prototype[filterName] = function (node, callback) { var result = []; this[forEachName](node, function (n, a) { if (callback(n, a)) result.push(n); }); return result; }; /** * Function reducing the relevant neighbors using a callback. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * * @throws {Error} - Will throw if there are too many arguments. */ var reduceName = 'reduce' + name[0].toUpperCase() + name.slice(1); Class.prototype[reduceName] = function (node, callback, initialValue) { if (arguments.length < 3) throw new InvalidArgumentsGraphError("Graph.".concat(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.")); var accumulator = initialValue; this[forEachName](node, function (n, a) { accumulator = callback(accumulator, n, a); }); return accumulator; }; } /** * Function attaching a breakable neighbors callback iterator method to the * Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachFindNeighbor(Class, description) { var name = description.name, type = description.type, direction = description.direction; var capitalizedSingular = name[0].toUpperCase() + name.slice(1, -1); var findName = 'find' + capitalizedSingular; /** * Function iterating over all the relevant neighbors using a callback. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * @return {undefined} * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[findName] = function (node, callback) { // Early termination if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type) return; node = '' + node; var nodeData = this._nodes.get(node); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(findName, ": could not find the \"").concat(node, "\" node in the graph.")); // Here, we want to iterate over a node's relevant neighbors return forEachNeighbor(true, type === 'mixed' ? this.type : type, direction, nodeData, callback); }; /** * Function iterating over all the relevant neighbors to find if any of them * matches the given predicate. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * @return {boolean} * * @throws {Error} - Will throw if there are too many arguments. */ var someName = 'some' + capitalizedSingular; Class.prototype[someName] = function (node, callback) { var found = this[findName](node, callback); if (found) return true; return false; }; /** * Function iterating over all the relevant neighbors to find if all of them * matche the given predicate. * * @param {any} node - Target node. * @param {function} callback - Callback to use. * @return {boolean} * * @throws {Error} - Will throw if there are too many arguments. */ var everyName = 'every' + capitalizedSingular; Class.prototype[everyName] = function (node, callback) { var found = this[findName](node, function (n, a) { return !callback(n, a); }); if (found) return false; return true; }; } /** * Function attaching a neighbors callback iterator method to the Graph prototype. * * @param {function} Class - Target class. * @param {object} description - Method description. */ function attachNeighborIteratorCreator(Class, description) { var name = description.name, type = description.type, direction = description.direction; var iteratorName = name.slice(0, -1) + 'Entries'; /** * Function returning an iterator over all the relevant neighbors. * * @param {any} node - Target node. * @return {Iterator} * * @throws {Error} - Will throw if there are too many arguments. */ Class.prototype[iteratorName] = function (node) { // Early termination if (type !== 'mixed' && this.type !== 'mixed' && type !== this.type) return iterator.empty(); node = '' + node; var nodeData = this._nodes.get(node); if (typeof nodeData === 'undefined') throw new NotFoundGraphError("Graph.".concat(iteratorName, ": could not find the \"").concat(node, "\" node in the graph.")); // Here, we want to iterate over a node's relevant neighbors return createNeighborIterator(type === 'mixed' ? this.type : type, direction, nodeData); }; } /** * Function attaching every neighbor iteration method to the Graph class. * * @param {function} Graph - Graph class. */ function attachNeighborIterationMethods(Graph) { NEIGHBORS_ITERATION.forEach(function (description) { attachNeighborArrayCreator(Graph, description); attachForEachNeighbor(Graph, description); attachFindNeighbor(Graph, description); attachNeighborIteratorCreator(Graph, description); }); } /** * Graphology Adjacency Iteration * =============================== * * Attaching some methods to the Graph class to be able to iterate over a * graph's adjacency. */ /** * Function iterating over a simple graph's adjacency using a callback. * * @param {boolean} breakable - Can we break? * @param {boolean} assymetric - Whether to emit undirected edges only once. * @param {boolean} disconnectedNodes - Whether to emit disconnected nodes. * @param {Graph} graph - Target Graph instance. * @param {callback} function - Iteration callback. */ function forEachAdjacency(breakable, assymetric, disconnectedNodes, graph, callback) { var iterator = graph._nodes.values(); var type = graph.type; var step, sourceData, neighbor, adj, edgeData, targetData, shouldBreak; while (step = iterator.next(), step.done !== true) { var 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; } /** * Graphology Serialization Utilities * =================================== * * Collection of functions used by the graph serialization schemes. */ /** * Formats internal node data into a serialized node. * * @param {any} key - The node's key. * @param {object} data - Internal node's data. * @return {array} - The serialized node. */ function serializeNode(key, data) { var serialized = { key: key }; if (!isEmpty(data.attributes)) serialized.attributes = assign({}, data.attributes); return serialized; } /** * Formats internal edge data into a serialized edge. * * @param {string} type - The graph's type. * @param {any} key - The edge's key. * @param {object} data - Internal edge's data. * @return {array} - The serialized edge. */ function serializeEdge(type, key, data) { var serialized = { key: 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; } /** * Checks whether the given value is a serialized node. * * @param {mixed} value - Target value. * @return {string|null} */ 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.'); } /** * Checks whether the given value is a serialized edge. * * @param {mixed} value - Target value. * @return {string|null} */ 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.'); } /** * Constants. */ var INSTANCE_ID = incrementalIdStartingFromRandomByte(); /** * Enums. */ var TYPES = new Set(['directed', 'undirected', 'mixed']); var EMITTER_PROPS = new Set(['domain', '_events', '_eventsCount', '_maxListeners']); var EDGE_ADD_METHODS = [{ name: function name(verb) { return "".concat(verb, "Edge"); }, generateKey: true }, { name: function name(verb) { return "".concat(verb, "DirectedEdge"); }, generateKey: true, type: 'directed' }, { name: function name(verb) { return "".concat(verb, "UndirectedEdge"); }, generateKey: true, type: 'undirected' }, { name: function name(verb) { return "".concat(verb, "EdgeWithKey"); } }, { name: function name(verb) { return "".concat(verb, "DirectedEdgeWithKey"); }, type: 'directed' }, { name: function name(verb) { return "".concat(verb, "UndirectedEdgeWithKey"); }, type: 'undirected' }]; /** * Default options. */ var DEFAULTS = { allowSelfLoops: true, multi: false, type: 'mixed' }; /** * Abstract functions used by the Graph class for various methods. */ /** * Internal method used to add a node to the given graph * * @param {Graph} graph - Target graph. * @param {any} node - The node's key. * @param {object} [attributes] - Optional attributes. * @return {NodeData} - Created node data. */ function _addNode(graph, node, attributes) { if (attributes && !isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.addNode: invalid attributes. Expecting an object but got \"".concat(attributes, "\"")); // String coercion node = '' + node; attributes = attributes || {}; if (graph._nodes.has(node)) throw new UsageGraphError("Graph.addNode: the \"".concat(node, "\" node already exist in the graph.")); var data = new graph.NodeDataClass(node, attributes); // Adding the node to internal register graph._nodes.set(node, data); // Emitting graph.emit('nodeAdded', { key: node, attributes: attributes }); return data; } /** * Same as the above but without sanity checks because we call this in contexts * where necessary checks were already done. */ function unsafeAddNode(graph, node, attributes) { var data = new graph.NodeDataClass(node, attributes); graph._nodes.set(node, data); graph.emit('nodeAdded', { key: node, attributes: attributes }); return data; } /** * Internal method used to add an arbitrary edge to the given graph. * * @param {Graph} graph - Target graph. * @param {string} name - Name of the child method for errors. * @param {boolean} mustGenerateKey - Should the graph generate an id? * @param {boolean} undirected - Whether the edge is undirected. * @param {any} edge - The edge's key. * @param {any} source - The source node. * @param {any} target - The target node. * @param {object} [attributes] - Optional attributes. * @return {any} - The edge. * * @throws {Error} - Will throw if the graph is of the wrong type. * @throws {Error} - Will throw if the given attributes are not an object. * @throws {Error} - Will throw if source or target doesn't exist. * @throws {Error} - Will throw if the edge already exist. */ function addEdge(graph, name, mustGenerateKey, undirected, edge, source, target, attributes) { // Checking validity of operation if (!undirected && graph.type === 'undirected') throw new UsageGraphError("Graph.".concat(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.".concat(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.".concat(name, ": invalid attributes. Expecting an object but got \"").concat(attributes, "\"")); // Coercion of source & target: source = '' + source; target = '' + target; attributes = attributes || {}; if (!graph.allowSelfLoops && source === target) throw new UsageGraphError("Graph.".concat(name, ": source & target are the same (\"").concat(source, "\"), thus creating a loop explicitly forbidden by this graph 'allowSelfLoops' option set to false.")); var sourceData = graph._nodes.get(source), targetData = graph._nodes.get(target); if (!sourceData) throw new NotFoundGraphError("Graph.".concat(name, ": source node \"").concat(source, "\" not found.")); if (!targetData) throw new NotFoundGraphError("Graph.".concat(name, ": target node \"").concat(target, "\" not found.")); // Must the graph generate an id for this edge? var eventData = { key: null, undirected: undirected, source: source, target: target, attributes: attributes }; if (mustGenerateKey) { // NOTE: in this case we can guarantee that the key does not already // exist and is already correctly casted as a string edge = graph._edgeKeyGenerator(); } else { // Coercion of edge key edge = '' + edge; // Here, we have a key collision if (graph._edges.has(edge)) throw new UsageGraphError("Graph.".concat(name, ": the \"").concat(edge, "\" edge already exists in the graph.")); } // Here, we might have a source / target collision if (!graph.multi && (undirected ? typeof sourceData.undirected[target] !== 'undefined' : typeof sourceData.out[target] !== 'undefined')) { throw new UsageGraphError("Graph.".concat(name, ": an edge linking \"").concat(source, "\" to \"").concat(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.")); } // Storing some data var edgeData = new EdgeData(undirected, edge, sourceData, targetData, attributes); // Adding the edge to the internal register graph._edges.set(edge, edgeData); // Incrementing node degree counters var 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++; } } // Updating relevant index if (graph.multi) edgeData.attachMulti();else edgeData.attach(); if (undirected) graph._undirectedSize++;else graph._directedSize++; // Emitting eventData.key = edge; graph.emit('edgeAdded', eventData); return edge; } /** * Internal method used to add an arbitrary edge to the given graph. * * @param {Graph} graph - Target graph. * @param {string} name - Name of the child method for errors. * @param {boolean} mustGenerateKey - Should the graph generate an id? * @param {boolean} undirected - Whether the edge is undirected. * @param {any} edge - The edge's key. * @param {any} source - The source node. * @param {any} target - The target node. * @param {object} [attributes] - Optional attributes. * @param {boolean} [asUpdater] - Are we updating or merging? * @return {any} - The edge. * * @throws {Error} - Will throw if the graph is of the wrong type. * @throws {Error} - Will throw if the given attributes are not an object. * @throws {Error} - Will throw if source or target doesn't exist. * @throws {Error} - Will throw if the edge already exist. */ function mergeEdge(graph, name, mustGenerateKey, undirected, edge, source, target, attributes, asUpdater) { // Checking validity of operation if (!undirected && graph.type === 'undirected') throw new UsageGraphError("Graph.".concat(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.".concat(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.".concat(name, ": invalid updater function. Expecting a function but got \"").concat(attributes, "\"")); } else { if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.".concat(name, ": invalid attributes. Expecting an object but got \"").concat(attributes, "\"")); } } // Coercion of source & target: source = '' + source; target = '' + target; var updater; if (asUpdater) { updater = attributes; attributes = undefined; } if (!graph.allowSelfLoops && source === target) throw new UsageGraphError("Graph.".concat(name, ": source & target are the same (\"").concat(source, "\"), thus creating a loop explicitly forbidden by this graph 'allowSelfLoops' option set to false.")); var sourceData = graph._nodes.get(source); var targetData = graph._nodes.get(target); var edgeData; // Do we need to handle duplicate? var alreadyExistingEdgeData; if (!mustGenerateKey) { edgeData = graph._edges.get(edge); if (edgeData) { // Here, we need to ensure, if the user gave a key, that source & target // are consistent if (edgeData.source.key !== source || edgeData.target.key !== target) { // If source or target inconsistent if (!undirected || edgeData.source.key !== target || edgeData.target.key !== source) { // If directed, or source/target aren't flipped throw new UsageGraphError("Graph.".concat(name, ": inconsistency detected when attempting to merge the \"").concat(edge, "\" edge with \"").concat(source, "\" source & \"").concat(target, "\" target vs. (\"").concat(edgeData.source.key, "\", \"").concat(edgeData.target.key, "\").")); } } alreadyExistingEdgeData = edgeData; } } // Here, we might have a source / target collision if (!alreadyExistingEdgeData && !graph.multi && sourceData) { alreadyExistingEdgeData = undirected ? sourceData.undirected[target] : sourceData.out[target]; } // Handling duplicates if (alreadyExistingEdgeData) { var info = [alreadyExistingEdgeData.key, false, false, false]; // We can skip the attribute merging part if the user did not provide them if (asUpdater ? !updater : !attributes) return info; // Updating the attributes if (asUpdater) { var oldAttributes = alreadyExistingEdgeData.attributes; alreadyExistingEdgeData.attributes = updater(oldAttributes); graph.emit('edgeAttributesUpdated', { type: 'replace', key: alreadyExistingEdgeData.key, attributes: alreadyExistingEdgeData.attributes }); } // Merging the 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); // Must the graph generate an id for this edge? var eventData = { key: null, undirected: undirected, source: source, target: target, attributes: attributes }; if (mustGenerateKey) { // NOTE: in this case we can guarantee that the key does not already // exist and is already correctly casted as a string edge = graph._edgeKeyGenerator(); } else { // Coercion of edge key edge = '' + edge; // Here, we have a key collision if (graph._edges.has(edge)) throw new UsageGraphError("Graph.".concat(name, ": the \"").concat(edge, "\" edge already exists in the graph.")); } var sourceWasAdded = false; var 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; } // Storing some data edgeData = new EdgeData(undirected, edge, sourceData, targetData, attributes); // Adding the edge to the internal register graph._edges.set(edge, edgeData); // Incrementing node degree counters var 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++; } } // Updating relevant index if (graph.multi) edgeData.attachMulti();else edgeData.attach(); if (undirected) graph._undirectedSize++;else graph._directedSize++; // Emitting eventData.key = edge; graph.emit('edgeAdded', eventData); return [edge, true, sourceWasAdded, targetWasAdded]; } /** * Internal method used to drop an edge. * * @param {Graph} graph - Target graph. * @param {EdgeData} edgeData - Data of the edge to drop. */ function dropEdgeFromData(graph, edgeData) { // Dropping the edge from the register graph._edges["delete"](edgeData.key); // Updating related degrees var sourceData = edgeData.source, targetData = edgeData.target, attributes = edgeData.attributes; var undirected = edgeData.undirected; var 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--; } } // Clearing index if (graph.multi) edgeData.detachMulti();else edgeData.detach(); if (undirected) graph._undirectedSize--;else graph._directedSize--; // Emitting graph.emit('edgeDropped', { key: edgeData.key, attributes: attributes, source: sourceData.key, target: targetData.key, undirected: undirected }); } /** * Graph class * * @constructor * @param {object} [options] - Options: * @param {boolean} [allowSelfLoops] - Allow self loops? * @param {string} [type] - Type of the graph. * @param {boolean} [map] - Allow references as keys? * @param {boolean} [multi] - Allow parallel edges? * * @throws {Error} - Will throw if the arguments are not valid. */ var Graph = /*#__PURE__*/function (_EventEmitter) { _inheritsLoose(Graph, _EventEmitter); function Graph(options) { var _this; _this = _EventEmitter.call(this) || this; //-- Solving options options = assign({}, DEFAULTS, options); // Enforcing options validity if (typeof options.multi !== 'boolean') throw new InvalidArgumentsGraphError("Graph.constructor: invalid 'multi' option. Expecting a boolean but got \"".concat(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 \"".concat(options.type, "\".")); if (typeof options.allowSelfLoops !== 'boolean') throw new InvalidArgumentsGraphError("Graph.constructor: invalid 'allowSelfLoops' option. Expecting a boolean but got \"".concat(options.allowSelfLoops, "\".")); //-- Private properties // Utilities var NodeDataClass = options.type === 'mixed' ? MixedNodeData : options.type === 'directed' ? DirectedNodeData : UndirectedNodeData; privateProperty(_assertThisInitialized(_this), 'NodeDataClass', NodeDataClass); // Internal edge key generator // NOTE: this internal generator produce keys that are strings // composed of a weird prefix, an incremental instance id starting from // a random byte and finally an internal instance incremental id. // All this to avoid intra-frame and cross-frame adversarial inputs // that can force a single #.addEdge call to degenerate into a O(n) // available key search loop. // It also ensures that automatically generated edge keys are unlikely // to produce collisions with arbitrary keys given by users. var instancePrefix = 'geid_' + INSTANCE_ID() + '_'; var edgeId = 0; var edgeKeyGenerator = function edgeKeyGenerator() { var availableEdgeKey; do { availableEdgeKey = instancePrefix + edgeId++; } while (_this._edges.has(availableEdgeKey)); return availableEdgeKey; }; // Indexes privateProperty(_assertThisInitialized(_this), '_attributes', {}); privateProperty(_assertThisInitialized(_this), '_nodes', new Map()); privateProperty(_assertThisInitialized(_this), '_edges', new Map()); privateProperty(_assertThisInitialized(_this), '_directedSize', 0); privateProperty(_assertThisInitialized(_this), '_undirectedSize', 0); privateProperty(_assertThisInitialized(_this), '_directedSelfLoopCount', 0); privateProperty(_assertThisInitialized(_this), '_undirectedSelfLoopCount', 0); privateProperty(_assertThisInitialized(_this), '_edgeKeyGenerator', edgeKeyGenerator); // Options privateProperty(_assertThisInitialized(_this), '_options', options); // Emitter properties EMITTER_PROPS.forEach(function (prop) { return privateProperty(_assertThisInitialized(_this), prop, _this[prop]); }); //-- Properties readers readOnlyProperty(_assertThisInitialized(_this), 'order', function () { return _this._nodes.size; }); readOnlyProperty(_assertThisInitialized(_this), 'size', function () { return _this._edges.size; }); readOnlyProperty(_assertThisInitialized(_this), 'directedSize', function () { return _this._directedSize; }); readOnlyProperty(_assertThisInitialized(_this), 'undirectedSize', function () { return _this._undirectedSize; }); readOnlyProperty(_assertThisInitialized(_this), 'selfLoopCount', function () { return _this._directedSelfLoopCount + _this._undirectedSelfLoopCount; }); readOnlyProperty(_assertThisInitialized(_this), 'directedSelfLoopCount', function () { return _this._directedSelfLoopCount; }); readOnlyProperty(_assertThisInitialized(_this), 'undirectedSelfLoopCount', function () { return _this._undirectedSelfLoopCount; }); readOnlyProperty(_assertThisInitialized(_this), 'multi', _this._options.multi); readOnlyProperty(_assertThisInitialized(_this), 'type', _this._options.type); readOnlyProperty(_assertThisInitialized(_this), 'allowSelfLoops', _this._options.allowSelfLoops); readOnlyProperty(_assertThisInitialized(_this), 'implementation', function () { return 'graphology'; }); return _this; } var _proto = Graph.prototype; _proto._resetInstanceCounters = function _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} */; _proto.hasNode = function 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. */; _proto.hasDirectedEdge = function hasDirectedEdge(source, target) { // Early termination if (this.type === 'undirected') return false; if (arguments.length === 1) { var edge = '' + source; var edgeData = this._edges.get(edge); return !!edgeData && !edgeData.undirected; } else if (arguments.length === 2) { source = '' + source; target = '' + target; // If the node source or the target is not in the graph we break var nodeData = this._nodes.get(source); if (!nodeData) return false; // Is there a directed edge pointing toward target? return nodeData.out.hasOwnProperty(target); } throw new InvalidArgumentsGraphError("Graph.hasDirectedEdge: invalid arity (".concat(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. */; _proto.hasUndirectedEdge = function hasUndirectedEdge(source, target) { // Early termination if (this.type === 'directed') return false; if (arguments.length === 1) { var edge = '' + source; var edgeData = this._edges.get(edge); return !!edgeData && edgeData.undirected; } else if (arguments.length === 2) { source = '' + source; target = '' + target; // If the node source or the target is not in the graph we break var nodeData = this._nodes.get(source); if (!nodeData) return false; // Is there a directed edge pointing toward target? return nodeData.undirected.hasOwnProperty(target); } throw new InvalidArgumentsGraphError("Graph.hasDirectedEdge: invalid arity (".concat(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. */; _proto.hasEdge = function hasEdge(source, target) { if (arguments.length === 1) { var edge = '' + source; return this._edges.has(edge); } else if (arguments.length === 2) { source = '' + source; target = '' + target; // If the node source or the target is not in the graph we break var nodeData = this._nodes.get(source); if (!nodeData) return false; // Is there a directed edge pointing toward target? return typeof nodeData.out !== 'undefined' && nodeData.out.hasOwnProperty(target) || typeof nodeData.undirected !== 'undefined' && nodeData.undirected.hasOwnProperty(target); } throw new InvalidArgumentsGraphError("Graph.hasEdge: invalid arity (".concat(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. */; _proto.directedEdge = function 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.'); var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.directedEdge: could not find the \"".concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.directedEdge: could not find the \"".concat(target, "\" target node in the graph.")); var edgeData = sourceData.out && sourceData.out[target] || undefined; 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. */; _proto.undirectedEdge = function 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.'); var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.undirectedEdge: could not find the \"".concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.undirectedEdge: could not find the \"".concat(target, "\" target node in the graph.")); var edgeData = sourceData.undirected && sourceData.undirected[target] || undefined; 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. */; _proto.edge = function 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; var sourceData = this._nodes.get(source); if (!sourceData) throw new NotFoundGraphError("Graph.edge: could not find the \"".concat(source, "\" source node in the graph.")); if (!this._nodes.has(target)) throw new NotFoundGraphError("Graph.edge: could not find the \"".concat(target, "\" target node in the graph.")); var edgeData = sourceData.out && sourceData.out[target] || sourceData.undirected && sourceData.undirected[target] || undefined; 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. */; _proto.areDirectedNeighbors = function areDirectedNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areDirectedNeighbors: could not find the \"".concat(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. */; _proto.areOutNeighbors = function areOutNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areOutNeighbors: could not find the \"".concat(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. */; _proto.areInNeighbors = function areInNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areInNeighbors: could not find the \"".concat(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. */; _proto.areUndirectedNeighbors = function areUndirectedNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areUndirectedNeighbors: could not find the \"".concat(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. */; _proto.areNeighbors = function areNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areNeighbors: could not find the \"".concat(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. */; _proto.areInboundNeighbors = function areInboundNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areInboundNeighbors: could not find the \"".concat(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. */; _proto.areOutboundNeighbors = function areOutboundNeighbors(node, neighbor) { node = '' + node; neighbor = '' + neighbor; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.areOutboundNeighbors: could not find the \"".concat(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. */; _proto.inDegree = function inDegree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.inDegree: could not find the \"".concat(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. */; _proto.outDegree = function outDegree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.outDegree: could not find the \"".concat(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. */; _proto.directedDegree = function directedDegree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.directedDegree: could not find the \"".concat(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. */; _proto.undirectedDegree = function undirectedDegree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.undirectedDegree: could not find the \"".concat(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. */; _proto.inboundDegree = function inboundDegree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.inboundDegree: could not find the \"".concat(node, "\" node in the graph.")); var 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. */; _proto.outboundDegree = function outboundDegree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.outboundDegree: could not find the \"".concat(node, "\" node in the graph.")); var 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. */; _proto.degree = function degree(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.degree: could not find the \"".concat(node, "\" node in the graph.")); var 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. */; _proto.inDegreeWithoutSelfLoops = function inDegreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.inDegreeWithoutSelfLoops: could not find the \"".concat(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. */; _proto.outDegreeWithoutSelfLoops = function outDegreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.outDegreeWithoutSelfLoops: could not find the \"".concat(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. */; _proto.directedDegreeWithoutSelfLoops = function directedDegreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.directedDegreeWithoutSelfLoops: could not find the \"".concat(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. */; _proto.undirectedDegreeWithoutSelfLoops = function undirectedDegreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.undirectedDegreeWithoutSelfLoops: could not find the \"".concat(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. */; _proto.inboundDegreeWithoutSelfLoops = function inboundDegreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.inboundDegreeWithoutSelfLoops: could not find the \"".concat(node, "\" node in the graph.")); var degree = 0; var 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. */; _proto.outboundDegreeWithoutSelfLoops = function outboundDegreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.outboundDegreeWithoutSelfLoops: could not find the \"".concat(node, "\" node in the graph.")); var degree = 0; var 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. */; _proto.degreeWithoutSelfLoops = function degreeWithoutSelfLoops(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.degreeWithoutSelfLoops: could not find the \"".concat(node, "\" node in the graph.")); var degree = 0; var 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. */; _proto.source = function source(edge) { edge = '' + edge; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.source: could not find the \"".concat(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. */; _proto.target = function target(edge) { edge = '' + edge; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.target: could not find the \"".concat(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. */; _proto.extremities = function extremities(edge) { edge = '' + edge; var edgeData = this._edges.get(edge); if (!edgeData) throw new NotFoundGraphError("Graph.extremities: could not find the \"".concat(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. */; _proto.opposite = function opposite(node, edge) { node = '' + node; edge = '' + edge; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.opposite: could not find the \"".concat(edge, "\" edge in the graph.")); var source = data.source.key; var target = data.target.key; if (node === source) return target; if (node === target) return source; throw new NotFoundGraphError("Graph.opposite: the \"".concat(node, "\" node is not attached to the \"").concat(edge, "\" edge (").concat(source, ", ").concat(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. */; _proto.hasExtremity = function hasExtremity(edge, node) { edge = '' + edge; node = '' + node; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.hasExtremity: could not find the \"".concat(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. */; _proto.isUndirected = function isUndirected(edge) { edge = '' + edge; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.isUndirected: could not find the \"".concat(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. */; _proto.isDirected = function isDirected(edge) { edge = '' + edge; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.isDirected: could not find the \"".concat(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. */; _proto.isSelfLoop = function isSelfLoop(edge) { edge = '' + edge; var data = this._edges.get(edge); if (!data) throw new NotFoundGraphError("Graph.isSelfLoop: could not find the \"".concat(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. */; _proto.addNode = function addNode(node, attributes) { var 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. */; _proto.mergeNode = function mergeNode(node, attributes) { if (attributes && !isPlainObject(attributes)) throw new InvalidArgumentsGraphError("Graph.mergeNode: invalid attributes. Expecting an object but got \"".concat(attributes, "\"")); // String coercion node = '' + node; attributes = attributes || {}; // If the node already exists, we merge the attributes var 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); // Adding the node to internal register this._nodes.set(node, data); // Emitting this.emit('nodeAdded', { key: node, attributes: 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. */; _proto.updateNode = function updateNode(node, updater) { if (updater && typeof updater !== 'function') throw new InvalidArgumentsGraphError("Graph.updateNode: invalid updater function. Expecting a function but got \"".concat(updater, "\"")); // String coercion node = '' + node; // If the node already exists, we update the attributes var data = this._nodes.get(node); if (data) { if (updater) { var oldAttributes = data.attributes; data.attributes = updater(oldAttributes); this.emit('nodeAttributesUpdated', { type: 'replace', key: node, attributes: data.attributes }); } return [node, false]; } var attributes = updater ? updater({}) : {}; data = new this.NodeDataClass(node, attributes); // Adding the node to internal register this._nodes.set(node, data); // Emitting this.emit('nodeAdded', { key: node, attributes: 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. */; _proto.dropNode = function dropNode(node) { node = '' + node; var nodeData = this._nodes.get(node); if (!nodeData) throw new NotFoundGraphError("Graph.dropNode: could not find the \"".concat(node, "\" node in the graph.")); var edgeData; // Removing attached edges // NOTE: we could be faster here, but this is such a pain to maintain if (this.type !== 'undirected') { for (var neighbor in nodeData.out) { edgeData = nodeData.out[neighbor]; do { dropEdgeFromData(this, edgeData); edgeData = edgeData.next; } while (edgeData); } for (var _neighbor in nodeData["in"]) { edgeData = nodeData["in"][_neighbor]; do { dropEdgeFromData(this, edgeData); edgeData = edgeData.next; } while (edgeData); } } if (this.type !== 'directed') { for (var _neighbor2 in nodeData.undirected) { edgeData = nodeData.undirected[_neighbor2]; do { dropEdgeFromData(this, edgeData); edgeData = edgeData.next; } while (edgeData); } } // Dropping the node from the register this._nodes["delete"](node); // Emitting 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. */; _proto.dropEdge = function dropEdge(edge) { var edgeData; if (arguments.length > 1) { var source = '' + arguments[0]; var target = '' + arguments[1]; edgeData = getMatchingEdge(this, source, target, this.type); if (!edgeData) throw new NotFoundGraphError("Graph.dropEdge: could not find the \"".concat(source, "\" -> \"").concat(target, "\" edge in the graph.")); } else { edge = '' + edge; edgeData = this._edges.get(edge); if (!edgeData) throw new NotFoundGraphError("Graph.dropEdge: could not find the \"".concat(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. */; _proto.dropDirectedEdge = function 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; var edgeData = getMatchingEdge(this, source, target, 'directed'); if (!edgeData) throw new NotFoundGraphError("Graph.dropDirectedEdge: could not find a \"".concat(source, "\" -> \"").concat(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. */; _proto.dropUndirectedEdge = function 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.'); var edgeData = getMatchingEdge(this, source, target, 'undirected'); if (!edgeData) throw new NotFoundGraphError("Graph.dropUndirectedEdge: could not find a \"".concat(source, "\" -> \"").concat(target, "\" edge in the graph.")); dropEdgeFromData(this, edgeData); return this; } /** * Method used to remove every edge & every node from the graph. * * @return {Graph} */; _proto.clear = function clear() { // Clearing edges this._edges.clear(); // Clearing nodes this._nodes.clear(); // Reset counters this._resetInstanceCounters(); // Emitting this.emit('cleared'); } /** * Method used to remove every edge from the graph. * * @return {Graph} */; _proto.clearEdges = function clearEdges() { // Clearing structure index var iterator = this._nodes.values(); var step; while (step = iterator.next(), step.done !== true) { step.value.clear(); } // Clearing edges this._edges.clear(); // Reset counters this._resetInstanceCounters(); // Emitting this.emit('edgesCleared'); } /**--------------------------------------------------------------------------- * Attributes-related methods **--------------------------------------------------------------------------- */ /** * Method returning the desired graph's attribute. * * @param {string} name - Name of the attribute. * @return {any} */; _proto.getAttribute = function getAttribute(name) { return this._attributes[name]; } /** * Method returning the graph's attributes. * * @return {object} */; _proto.getAttributes = function getAttributes() { return this._attributes; } /** * Method returning whether the graph has the desired attribute. * * @param {string} name - Name of the attribute. * @return {boolean} */; _proto.hasAttribute = function 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} */; _proto.setAttribute = function setAttribute(name, value) { this._attributes[name] = value; // Emitting this.emit('attributesUpdated', { type: 'set', attributes: this._attributes, name: 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} */; _proto.updateAttribute = function updateAttribute(name, updater) { if (typeof updater !== 'function') throw new InvalidArgumentsGraphError('Graph.updateAttribute: updater should be a function.'); var value = this._attributes[name]; this._attributes[name] = updater(value); // Emitting this.emit('attributesUpdated', { type: 'set', attributes: this._attributes, name: name }); return this; } /** * Method removing the desired graph's attribute. * * @param {string} name - Name of the attribute. * @return {Graph} */; _proto.removeAttribute = function removeAttribute(name) { delete this._attributes[name]; // Emitting this.emit('attributesUpdated', { type: 'remove', attributes: this._attributes, name: 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. */; _proto.replaceAttributes = function replaceAttributes(attributes) { if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError('Graph.replaceAttributes: provided attributes are not a plain object.'); this._attributes = attributes; // Emitting 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. */; _proto.mergeAttributes = function mergeAttributes(attributes) { if (!isPlainObject(attributes)) throw new InvalidArgumentsGraphError('Graph.mergeAttributes: provided attributes are not a plain object.'); assign(this._attributes, attributes); // Emitting 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. */; _proto.updateAttributes = function updateAttributes(updater) { if (typeof updater !== 'function') throw new InvalidArgumentsGraphError('Graph.updateAttributes: provided updater is not a function.'); this._attributes = updater(this._attributes); // Emitting 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. */; _proto.updateEachNodeAttributes = function 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]}'); var iterator = this._nodes.values(); var 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. */; _proto.updateEachEdgeAttributes = function 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]}'); var iterator = this._edges.values(); var 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. */; _proto.forEachAdjacencyEntry = function forEachAdjacencyEntry(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.forEachAdjacencyEntry: expecting a callback.'); forEachAdjacency(false, false, false, this, callback); }; _proto.forEachAdjacencyEntryWithOrphans = function 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. */; _proto.forEachAssymetricAdjacencyEntry = function forEachAssymetricAdjacencyEntry(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.forEachAssymetricAdjacencyEntry: expecting a callback.'); forEachAdjacency(false, true, false, this, callback); }; _proto.forEachAssymetricAdjacencyEntryWithOrphans = function 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. */; _proto.nodes = function nodes() { if (typeof Array.from === 'function') return Array.from(this._nodes.keys()); return take(this._nodes.keys(), this._nodes.size); } /** * Method iterating over the graph's nodes using the given callback. * * @param {function} callback - Callback (key, attributes, index). */; _proto.forEachNode = function forEachNode(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.forEachNode: expecting a callback.'); var iterator = this._nodes.values(); var 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). */; _proto.findNode = function findNode(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.findNode: expecting a callback.'); var iterator = this._nodes.values(); var 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). */; _proto.mapNodes = function mapNodes(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.mapNode: expecting a callback.'); var iterator = this._nodes.values(); var step, nodeData; var result = new Array(this.order); var 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). */; _proto.someNode = function someNode(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.someNode: expecting a callback.'); var iterator = this._nodes.values(); var 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). */; _proto.everyNode = function everyNode(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.everyNode: expecting a callback.'); var iterator = this._nodes.values(); var 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). */; _proto.filterNodes = function filterNodes(callback) { if (typeof callback !== 'function') throw new InvalidArgumentsGraphError('Graph.filterNodes: expecting a callback.'); var iterator = this._nodes.values(); var step, nodeData; var 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). */; _proto.reduceNodes = function 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.'); var accumulator = initialValue; var iterator = this._nodes.values(); var 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} */; _proto.nodeEntries = function nodeEntries() { var iterator$1 = this._nodes.values(); return new iterator(function () { var step = iterator$1.next(); if (step.done) return step; var 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. */; _proto["export"] = function _export() { var _this2 = this; var nodes = new Array(this._nodes.size); var i = 0; this._nodes.forEach(function (data, key) { nodes[i++] = serializeNode(key, data); }); var edges = new Array(this._edges.size); i = 0; this._edges.forEach(function (data, key) { edges[i++] = serializeEdge(_this2.type, key, data); }); return { options: { type: this.type, multi: this.multi, allowSelfLoops: this.allowSelfLoops }, attributes: this.getAttributes(), nodes: nodes, edges: 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. */; _proto["import"] = function _import(data) { var _this3 = this; var merge = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; // Importing a Graph instance directly if (data instanceof Graph) { // Nodes data.forEachNode(function (n, a) { if (merge) _this3.mergeNode(n, a);else _this3.addNode(n, a); }); // Edges data.forEachEdge(function (e, a, s, t, _sa, _ta, u) { if (merge) { if (u) _this3.mergeUndirectedEdgeWithKey(e, s, t, a);else _this3.mergeDirectedEdgeWithKey(e, s, t, a); } else { if (u) _this3.addUndirectedEdgeWithKey(e, s, t, a);else _this3.addDirectedEdgeWithKey(e, s, t, a); } }); return this; } // Importing a serialized graph 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); } var 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]; // Validating validateSerializedNode(node); // Adding the node var _node = node, key = _node.key, attributes = _node.attributes; if (merge) this.mergeNode(key, attributes);else this.addNode(key, attributes); } } if (data.edges) { var 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]; // Validating validateSerializedEdge(edge); // Adding the edge var _edge = edge, source = _edge.source, target = _edge.target, _attributes = _edge.attributes, _edge$undirected = _edge.undirected, undirected = _edge$undirected === void 0 ? undirectedByDefault : _edge$undirected; var method = void 0; 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. */; _proto.nullCopy = function nullCopy(options) { var 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. */; _proto.emptyCopy = function emptyCopy(options) { var graph = this.nullCopy(options); this._nodes.forEach(function (nodeData, key) { var attributes = assign({}, nodeData.attributes); // NOTE: no need to emit events since user cannot access the instance yet 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. */; _proto.copy = function 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 \"".concat(this.type, "\" type to \"").concat(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.'); var graph = this.emptyCopy(options); var iterator = this._edges.values(); var step, edgeData; while (step = iterator.next(), step.done !== true) { edgeData = step.value; // NOTE: no need to emit events since user cannot access the instance yet 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. */; _proto.toJSON = function toJSON() { return this["export"](); } /** * Method returning [object Graph]. */; _proto.toString = function toString() { return '[object Graph]'; } /** * Method used internally by node's console to display a custom object. * * @return {object} - Formatted object representation of the graph. */; _proto.inspect = function inspect() { var _this4 = this; var nodes = {}; this._nodes.forEach(function (data, key) { nodes[key] = data.attributes; }); var edges = {}, multiIndex = {}; this._edges.forEach(function (data, key) { var direction = data.undirected ? '--' : '->'; var label = ''; var source = data.source.key; var target = data.target.key; var tmp; if (data.undirected && source > target) { tmp = source; source = target; target = tmp; } var desc = "(".concat(source, ")").concat(direction, "(").concat(target, ")"); if (!key.startsWith('geid_')) { label += "[".concat(key, "]: "); } else if (_this4.multi) { if (typeof multiIndex[desc] === 'undefined') { multiIndex[desc] = 0; } else { multiIndex[desc]++; } label += "".concat(multiIndex[desc], ". "); } label += desc; edges[label] = data.attributes; }); var dummy = {}; for (var 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; }; return Graph; }(events.exports.EventEmitter); if (typeof Symbol !== 'undefined') Graph.prototype[Symbol["for"]('nodejs.util.inspect.custom')] = Graph.prototype.inspect; /** * Related to edge addition. */ EDGE_ADD_METHODS.forEach(function (method) { ['add', 'merge', 'update'].forEach(function (verb) { var name = method.name(verb); var 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'); }; } }); }); /** * Attributes-related. */ attachNodeAttributesMethods(Graph); attachEdgeAttributesMethods(Graph); /** * Edge iteration-related. */ attachEdgeIterationMethods(Graph); /** * Neighbor iteration-related. */ attachNeighborIterationMethods(Graph); /** * Alternative constructors. */ var DirectedGraph = /*#__PURE__*/function (_Graph) { _inheritsLoose(DirectedGraph, _Graph); function DirectedGraph(options) { var 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!'); return _Graph.call(this, finalOptions) || this; } return DirectedGraph; }(Graph); var UndirectedGraph = /*#__PURE__*/function (_Graph2) { _inheritsLoose(UndirectedGraph, _Graph2); function UndirectedGraph(options) { var 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!'); return _Graph2.call(this, finalOptions) || this; } return UndirectedGraph; }(Graph); var MultiGraph = /*#__PURE__*/function (_Graph3) { _inheritsLoose(MultiGraph, _Graph3); function MultiGraph(options) { var 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!'); return _Graph3.call(this, finalOptions) || this; } return MultiGraph; }(Graph); var MultiDirectedGraph = /*#__PURE__*/function (_Graph4) { _inheritsLoose(MultiDirectedGraph, _Graph4); function MultiDirectedGraph(options) { var 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!'); return _Graph4.call(this, finalOptions) || this; } return MultiDirectedGraph; }(Graph); var MultiUndirectedGraph = /*#__PURE__*/function (_Graph5) { _inheritsLoose(MultiUndirectedGraph, _Graph5); function MultiUndirectedGraph(options) { var 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!'); return _Graph5.call(this, finalOptions) || this; } return MultiUndirectedGraph; }(Graph); /** * Attaching static #.from method to each of the constructors. */ function attachStaticFromMethod(Class) { /** * Builds a graph from serialized data or another graph's data. * * @param {Graph|SerializedGraph} data - Hydratation data. * @param {object} [options] - Options. * @return {Class} */ Class.from = function (data, options) { // Merging given options with serialized ones var finalOptions = assign({}, data.options, options); var 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; /** * Graphology CommonJS Endoint * ============================ * * Endpoint for CommonJS modules consumers. */ return Graph; })); //# sourceMappingURL=graphology.umd.js.map