UNPKG

graphology

Version:

A robust and multipurpose Graph object for JavaScript.

1,542 lines (1,299 loc) 195 kB
(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"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _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 : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || 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; } 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 Graph implementation instance. * * @param {mixed} value - Target value. * @return {boolean} */ function isGraph(value) { return value !== null && _typeof(value) === 'object' && typeof value.addUndirectedEdgeWithKey === 'function' && typeof value.dropNode === 'function'; } /** * Checks whether the given value is a plain object. * * @param {mixed} value - Target value. * @return {boolean} */ function isPlainObject(value) { return _typeof(value) === 'object' && value !== null && value.constructor === Object; } /** * 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 incrementalId() { var i = 0; 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$1(next) { // Hiding the given function Object.defineProperty(this, '_next', { writable: false, enumerable: false, value: next }); // Is the iterator complete? this.done = false; } /** * Next function. * * @return {object} */ // NOTE: maybe this should dropped for performance? Iterator$1.prototype.next = function () { if (this.done) return { done: true }; var step = this._next(); if (step.done) this.done = true; return step; }; /** * If symbols are supported, we add `next` to `Symbol.iterator`. */ if (typeof Symbol !== 'undefined') Iterator$1.prototype[Symbol.iterator] = function () { return this; }; /** * Returning an iterator of the given values. * * @param {any...} values - Values. * @return {Iterator} */ Iterator$1.of = function () { var args = arguments, l = args.length, i = 0; return new Iterator$1(function () { if (i >= l) return { done: true }; return { done: false, value: args[i++] }; }); }; /** * Returning an empty iterator. * * @return {Iterator} */ Iterator$1.empty = function () { var iterator = new Iterator$1(null); iterator.done = true; return iterator; }; /** * Returning whether the given value is an iterator. * * @param {any} value - Value. * @return {boolean} */ Iterator$1.is = function (value) { if (value instanceof Iterator$1) return true; return typeof value === 'object' && value !== null && typeof value.next === 'function'; }; /** * Exporting. */ var iterator = Iterator$1; /* eslint no-constant-condition: 0 */ /** * Obliterator Take Function * ========================== * * Function taking n or every value of the given iterator and returns them * into an array. */ /** * Take. * * @param {Iterator} iterator - Target iterator. * @param {number} [n] - Optional number of items to take. * @return {array} */ var take = function take(iterator, n) { var l = arguments.length > 1 ? n : Infinity, array = l !== Infinity ? new Array(l) : [], step, i = 0; while (true) { if (i === l) return array; step = iterator.next(); if (step.done) { if (i !== n) return array.slice(0, 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, data) { var _this; _this = _Error.call(this) || this; _this.name = 'GraphError'; _this.message = message || ''; _this.data = data || {}; return _this; } return GraphError; }( /*#__PURE__*/_wrapNativeSuper(Error)); var InvalidArgumentsGraphError = /*#__PURE__*/function (_GraphError) { _inheritsLoose(InvalidArgumentsGraphError, _GraphError); function InvalidArgumentsGraphError(message, data) { var _this2; _this2 = _GraphError.call(this, message, data) || 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, data) { var _this3; _this3 = _GraphError2.call(this, message, data) || 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, data) { var _this4; _this4 = _GraphError3.call(this, message, data) || 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; // Degrees this.inDegree = 0; this.outDegree = 0; this.undirectedDegree = 0; this.directedSelfLoops = 0; this.undirectedSelfLoops = 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; // Degrees this.inDegree = 0; this.outDegree = 0; this.directedSelfLoops = 0; // Indices this["in"] = {}; this.out = {}; } DirectedNodeData.prototype.upgradeToMixed = function () { // Degrees this.undirectedDegree = 0; this.undirectedSelfLoops = 0; // Indices this.undirected = {}; }; /** * 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; // Degrees this.undirectedDegree = 0; this.undirectedSelfLoops = 0; // Indices this.undirected = {}; } UndirectedNodeData.prototype.upgradeToMixed = function () { // Degrees this.inDegree = 0; this.outDegree = 0; this.directedSelfLoops = 0; // Indices this["in"] = {}; this.out = {}; }; /** * EdgeData class. * * @constructor * @param {boolean} undirected - Whether the edge is undirected. * @param {string} string - The edge's key. * @param {boolean} generatedKey - Was its key generated? * @param {string} source - Source of the edge. * @param {string} target - Target of the edge. * @param {object} attributes - Edge's attributes. */ function EdgeData(undirected, key, generatedKey, source, target, attributes) { // Attributes this.key = key; this.attributes = attributes; this.undirected = undirected; // Extremities this.source = source; this.target = target; // Was its key generated? this.generatedKey = generatedKey; } /** * Graphology Indexes Functions * ============================= * * Bunch of functions used to compute or clear indexes. */ /** * Function updating the 'structure' index with the given edge's data. * Note that in the case of the multi graph, related edges are stored in a * set that is the same for A -> B & B <- A. * * @param {Graph} graph - Target Graph instance. * @param {EdgeData} edgeData - Added edge's data. * @param {NodeData} sourceData - Source node's data. * @param {NodeData} targetData - Target node's data. */ function updateStructureIndex(graph, undirected, edgeData, source, target, sourceData, targetData) { var multi = graph.multi; var outKey = 'out'; var inKey = 'in'; if (undirected) outKey = inKey = 'undirected'; var adj, container; if (multi) { // Handling source adj = sourceData[outKey]; container = adj[target]; if (typeof container === 'undefined') { container = new Set(); adj[target] = container; } container.add(edgeData); // If selfLoop, we break here if (source === target && undirected) return; // Handling target (we won't add the edge because it was already taken // care of with source above) adj = targetData[inKey]; if (typeof adj[source] === 'undefined') adj[source] = container; } else { // Handling source sourceData[outKey][target] = edgeData; // If selfLoop, we break here if (source === target && undirected) return; // Handling target targetData[inKey][source] = edgeData; } } /** * Function clearing the 'structure' index data related to the given edge. * * @param {Graph} graph - Target Graph instance. * @param {EdgeData} edgeData - Dropped edge's data. */ function clearEdgeFromStructureIndex(graph, undirected, edgeData) { var multi = graph.multi; var sourceData = edgeData.source, targetData = edgeData.target; var source = sourceData.key, target = targetData.key; // NOTE: since the edge set is the same for source & target, we can only // affect source var outKey = undirected ? 'undirected' : 'out', sourceIndex = sourceData[outKey]; var inKey = undirected ? 'undirected' : 'in'; if (target in sourceIndex) { if (multi) { var set = sourceIndex[target]; if (set.size === 1) { delete sourceIndex[target]; delete targetData[inKey][source]; } else { set["delete"](edgeData); } } else delete sourceIndex[target]; } if (multi) return; var targetIndex = targetData[inKey]; delete targetIndex[source]; } /** * Function clearing the whole 'structure' index. * * @param {Graph} graph - Target Graph instance. */ function clearStructureIndex(graph) { graph._nodes.forEach(function (data) { // Clearing now useless properties if (typeof data["in"] !== 'undefined') { data["in"] = {}; data.out = {}; } if (typeof data.undirected !== 'undefined') { data.undirected = {}; } }); } /** * Function used to upgrade a simple `structure` index to a multi on. * * @param {Graph} graph - Target Graph instance. */ function upgradeStructureIndexToMulti(graph) { graph._nodes.forEach(function (data, node) { // Directed if (data.out) { for (var neighbor in data.out) { var edges = new Set(); edges.add(data.out[neighbor]); data.out[neighbor] = edges; graph._nodes.get(neighbor)["in"][node] = edges; } } // Undirected if (data.undirected) { for (var _neighbor in data.undirected) { if (_neighbor > node) continue; var _edges = new Set(); _edges.add(data.undirected[_neighbor]); data.undirected[_neighbor] = _edges; graph._nodes.get(_neighbor).undirected[node] = _edges; } } }); } /** * Graphology Attributes methods * ============================== * * Attributes-related methods being exactly the same for nodes & edges, * we abstract them here for factorization reasons. */ /** * 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 attachAttributeGetter(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, 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 { 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 (type !== 'mixed' && data.undirected !== (type === 'undirected')) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(element, "\" ").concat(type, " 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 attachAttributesGetter(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 { 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 (type !== 'mixed' && data.undirected !== (type === 'undirected')) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(element, "\" ").concat(type, " 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 attachAttributeChecker(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, 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 { 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 (type !== 'mixed' && data.undirected !== (type === 'undirected')) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(element, "\" ").concat(type, " 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 attachAttributeSetter(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, 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 { 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 (type !== 'mixed' && data.undirected !== (type === 'undirected')) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(element, "\" ").concat(type, " 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 attachAttributeUpdater(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, 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 { 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.")); if (type !== 'mixed' && data.undirected !== (type === 'undirected')) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(element, "\" ").concat(type, " edge in the graph.")); 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 attachAttributeRemover(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, 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 { 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 (type !== 'mixed' && data.undirected !== (type === 'undirected')) throw new NotFoundGraphError("Graph.".concat(method, ": could not find the \"").concat(element, "\" ").concat(type, " 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 attachAttributesReplacer(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;