eventemitter3-graphology
Version:
A robust and multipurpose Graph object for JavaScript.
1 lines • 336 kB
Source Map (JSON)
{"version":3,"file":"graphology.mjs","sources":["../src/utils.js","../src/errors.js","../src/data.js","../src/attributes/nodes.js","../src/attributes/edges.js","../src/iteration/edges.js","../src/iteration/neighbors.js","../src/iteration/adjacency.js","../src/serialization.js","../src/graph.js","../src/classes.js","../src/endpoint.esm.js"],"sourcesContent":["/**\n * Graphology Utilities\n * =====================\n *\n * Collection of helpful functions used by the implementation.\n */\n\n/**\n * Object.assign-like polyfill.\n *\n * @param {object} target - First object.\n * @param {object} [...objects] - Objects to merge.\n * @return {object}\n */\nfunction assignPolyfill() {\n const target = arguments[0];\n\n for (let i = 1, l = arguments.length; i < l; i++) {\n if (!arguments[i]) continue;\n\n for (const k in arguments[i]) target[k] = arguments[i][k];\n }\n\n return target;\n}\n\nlet assign = assignPolyfill;\n\nif (typeof Object.assign === 'function') assign = Object.assign;\n\nexport {assign};\n\n/**\n * Function returning the first matching edge for given path.\n * Note: this function does not check the existence of source & target. This\n * must be performed by the caller.\n *\n * @param {Graph} graph - Target graph.\n * @param {any} source - Source node.\n * @param {any} target - Target node.\n * @param {string} type - Type of the edge (mixed, directed or undirected).\n * @return {string|null}\n */\nexport function getMatchingEdge(graph, source, target, type) {\n const sourceData = graph._nodes.get(source);\n\n let edge = null;\n\n if (!sourceData) return edge;\n\n if (type === 'mixed') {\n edge =\n (sourceData.out && sourceData.out[target]) ||\n (sourceData.undirected && sourceData.undirected[target]);\n } else if (type === 'directed') {\n edge = sourceData.out && sourceData.out[target];\n } else {\n edge = sourceData.undirected && sourceData.undirected[target];\n }\n\n return edge;\n}\n\n/**\n * Checks whether the given value is a plain object.\n *\n * @param {mixed} value - Target value.\n * @return {boolean}\n */\nexport function isPlainObject(value) {\n // NOTE: as per https://github.com/graphology/graphology/issues/149\n // this function has been loosened not to reject object instances\n // coming from other JavaScript contexts. It has also been chosen\n // not to improve it to avoid obvious false positives and avoid\n // taking a performance hit. People should really use TypeScript\n // if they want to avoid feeding subtly irrelvant attribute objects.\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Checks whether the given object is empty.\n *\n * @param {object} o - Target Object.\n * @return {boolean}\n */\nexport function isEmpty(o) {\n let k;\n\n for (k in o) return false;\n\n return true;\n}\n\n/**\n * Creates a \"private\" property for the given member name by concealing it\n * using the `enumerable` option.\n *\n * @param {object} target - Target object.\n * @param {string} name - Member name.\n */\nexport function privateProperty(target, name, value) {\n Object.defineProperty(target, name, {\n enumerable: false,\n configurable: false,\n writable: true,\n value\n });\n}\n\n/**\n * Creates a read-only property for the given member name & the given getter.\n *\n * @param {object} target - Target object.\n * @param {string} name - Member name.\n * @param {mixed} value - The attached getter or fixed value.\n */\nexport function readOnlyProperty(target, name, value) {\n const descriptor = {\n enumerable: true,\n configurable: true\n };\n\n if (typeof value === 'function') {\n descriptor.get = value;\n } else {\n descriptor.value = value;\n descriptor.writable = false;\n }\n\n Object.defineProperty(target, name, descriptor);\n}\n\n/**\n * Returns whether the given object constitute valid hints.\n *\n * @param {object} hints - Target object.\n */\nexport function validateHints(hints) {\n if (!isPlainObject(hints)) return false;\n\n if (hints.attributes && !Array.isArray(hints.attributes)) return false;\n\n return true;\n}\n\n/**\n * Creates a function generating incremental ids for edges.\n *\n * @return {function}\n */\nexport function incrementalIdStartingFromRandomByte() {\n let i = Math.floor(Math.random() * 256) & 0xff;\n\n return () => {\n return i++;\n };\n}\n\n/**\n * Chains multiple iterators into a single iterator.\n *\n * @param {...Iterator} iterables\n * @returns {Iterator}\n */\nexport function chain() {\n const iterables = arguments;\n let current = null;\n let i = -1;\n\n return {\n [Symbol.iterator]() {\n return this;\n },\n next() {\n let step = null;\n\n do {\n if (current === null) {\n i++;\n if (i >= iterables.length) return {done: true};\n current = iterables[i][Symbol.iterator]();\n }\n step = current.next();\n if (step.done) {\n current = null;\n continue;\n }\n break;\n // eslint-disable-next-line no-constant-condition\n } while (true);\n\n return step;\n }\n };\n}\n\n/**\n * Maps the given iterable using the provided function.\n *\n * @param {Iterable} iterable\n * @param {Function} fn\n * @returns {Iterator}\n */\nexport function map(iterable, fn) {\n return {\n [Symbol.iterator]() {\n return this;\n },\n next() {\n const step = iterable.next();\n if (step.done) return step;\n return {value: fn(step.value), done: false};\n }\n };\n}\n\nexport function emptyIterator() {\n return {\n [Symbol.iterator]() {\n return this;\n },\n next() {\n return {done: true};\n }\n };\n}\n","/**\n * Graphology Custom Errors\n * =========================\n *\n * Defining custom errors for ease of use & easy unit tests across\n * implementations (normalized typology rather than relying on error\n * messages to check whether the correct error was found).\n */\nexport class GraphError extends Error {\n constructor(message) {\n super();\n this.name = 'GraphError';\n this.message = message;\n }\n}\n\nexport class InvalidArgumentsGraphError extends GraphError {\n constructor(message) {\n super(message);\n this.name = 'InvalidArgumentsGraphError';\n\n // This is V8 specific to enhance stack readability\n if (typeof Error.captureStackTrace === 'function')\n Error.captureStackTrace(\n this,\n InvalidArgumentsGraphError.prototype.constructor\n );\n }\n}\n\nexport class NotFoundGraphError extends GraphError {\n constructor(message) {\n super(message);\n this.name = 'NotFoundGraphError';\n\n // This is V8 specific to enhance stack readability\n if (typeof Error.captureStackTrace === 'function')\n Error.captureStackTrace(this, NotFoundGraphError.prototype.constructor);\n }\n}\n\nexport class UsageGraphError extends GraphError {\n constructor(message) {\n super(message);\n this.name = 'UsageGraphError';\n\n // This is V8 specific to enhance stack readability\n if (typeof Error.captureStackTrace === 'function')\n Error.captureStackTrace(this, UsageGraphError.prototype.constructor);\n }\n}\n","/**\n * Graphology Internal Data Classes\n * =================================\n *\n * Internal classes hopefully reduced to structs by engines & storing\n * necessary information for nodes & edges.\n *\n * Note that those classes don't rely on the `class` keyword to avoid some\n * cruft introduced by most of ES2015 transpilers.\n */\n\n/**\n * MixedNodeData class.\n *\n * @constructor\n * @param {string} string - The node's key.\n * @param {object} attributes - Node's attributes.\n */\nexport function MixedNodeData(key, attributes) {\n // Attributes\n this.key = key;\n this.attributes = attributes;\n\n this.clear();\n}\n\nMixedNodeData.prototype.clear = function () {\n // Degrees\n this.inDegree = 0;\n this.outDegree = 0;\n this.undirectedDegree = 0;\n this.undirectedLoops = 0;\n this.directedLoops = 0;\n\n // Indices\n this.in = {};\n this.out = {};\n this.undirected = {};\n};\n\n/**\n * DirectedNodeData class.\n *\n * @constructor\n * @param {string} string - The node's key.\n * @param {object} attributes - Node's attributes.\n */\nexport function DirectedNodeData(key, attributes) {\n // Attributes\n this.key = key;\n this.attributes = attributes;\n\n this.clear();\n}\n\nDirectedNodeData.prototype.clear = function () {\n // Degrees\n this.inDegree = 0;\n this.outDegree = 0;\n this.directedLoops = 0;\n\n // Indices\n this.in = {};\n this.out = {};\n};\n\n/**\n * UndirectedNodeData class.\n *\n * @constructor\n * @param {string} string - The node's key.\n * @param {object} attributes - Node's attributes.\n */\nexport function UndirectedNodeData(key, attributes) {\n // Attributes\n this.key = key;\n this.attributes = attributes;\n\n this.clear();\n}\n\nUndirectedNodeData.prototype.clear = function () {\n // Degrees\n this.undirectedDegree = 0;\n this.undirectedLoops = 0;\n\n // Indices\n this.undirected = {};\n};\n\n/**\n * EdgeData class.\n *\n * @constructor\n * @param {boolean} undirected - Whether the edge is undirected.\n * @param {string} string - The edge's key.\n * @param {string} source - Source of the edge.\n * @param {string} target - Target of the edge.\n * @param {object} attributes - Edge's attributes.\n */\nexport function EdgeData(undirected, key, source, target, attributes) {\n // Attributes\n this.key = key;\n this.attributes = attributes;\n this.undirected = undirected;\n\n // Extremities\n this.source = source;\n this.target = target;\n}\n\nEdgeData.prototype.attach = function () {\n let outKey = 'out';\n let inKey = 'in';\n\n if (this.undirected) outKey = inKey = 'undirected';\n\n const source = this.source.key;\n const target = this.target.key;\n\n // Handling source\n this.source[outKey][target] = this;\n\n if (this.undirected && source === target) return;\n\n // Handling target\n this.target[inKey][source] = this;\n};\n\nEdgeData.prototype.attachMulti = function () {\n let outKey = 'out';\n let inKey = 'in';\n\n const source = this.source.key;\n const target = this.target.key;\n\n if (this.undirected) outKey = inKey = 'undirected';\n\n // Handling source\n const adj = this.source[outKey];\n const head = adj[target];\n\n if (typeof head === 'undefined') {\n adj[target] = this;\n\n // Self-loop optimization\n if (!(this.undirected && source === target)) {\n // Handling target\n this.target[inKey][source] = this;\n }\n\n return;\n }\n\n // Prepending to doubly-linked list\n head.previous = this;\n this.next = head;\n\n // Pointing to new head\n // NOTE: use mutating swap later to avoid lookup?\n adj[target] = this;\n this.target[inKey][source] = this;\n};\n\nEdgeData.prototype.detach = function () {\n const source = this.source.key;\n const target = this.target.key;\n\n let outKey = 'out';\n let inKey = 'in';\n\n if (this.undirected) outKey = inKey = 'undirected';\n\n delete this.source[outKey][target];\n\n // No-op delete in case of undirected self-loop\n delete this.target[inKey][source];\n};\n\nEdgeData.prototype.detachMulti = function () {\n const source = this.source.key;\n const target = this.target.key;\n\n let outKey = 'out';\n let inKey = 'in';\n\n if (this.undirected) outKey = inKey = 'undirected';\n\n // Deleting from doubly-linked list\n if (this.previous === undefined) {\n // We are dealing with the head\n\n // Should we delete the adjacency entry because it is now empty?\n if (this.next === undefined) {\n delete this.source[outKey][target];\n\n // No-op delete in case of undirected self-loop\n delete this.target[inKey][source];\n } else {\n // Detaching\n this.next.previous = undefined;\n\n // NOTE: could avoid the lookups by creating a #.become mutating method\n this.source[outKey][target] = this.next;\n\n // No-op delete in case of undirected self-loop\n this.target[inKey][source] = this.next;\n }\n } else {\n // We are dealing with another list node\n this.previous.next = this.next;\n\n // If not last\n if (this.next !== undefined) {\n this.next.previous = this.previous;\n }\n }\n};\n","/**\n * Graphology Node Attributes methods\n * ===================================\n */\nimport {assign, isPlainObject} from '../utils';\n\nimport {InvalidArgumentsGraphError, NotFoundGraphError} from '../errors';\n\nconst NODE = 0;\nconst SOURCE = 1;\nconst TARGET = 2;\nconst OPPOSITE = 3;\n\nfunction findRelevantNodeData(\n graph,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1,\n add2\n) {\n let nodeData, edgeData, arg1, arg2;\n\n nodeOrEdge = '' + nodeOrEdge;\n\n if (mode === NODE) {\n nodeData = graph._nodes.get(nodeOrEdge);\n\n if (!nodeData)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${nodeOrEdge}\" node in the graph.`\n );\n\n arg1 = nameOrEdge;\n arg2 = add1;\n } else if (mode === OPPOSITE) {\n nameOrEdge = '' + nameOrEdge;\n\n edgeData = graph._edges.get(nameOrEdge);\n\n if (!edgeData)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${nameOrEdge}\" edge in the graph.`\n );\n\n const source = edgeData.source.key;\n const target = edgeData.target.key;\n\n if (nodeOrEdge === source) {\n nodeData = edgeData.target;\n } else if (nodeOrEdge === target) {\n nodeData = edgeData.source;\n } else {\n throw new NotFoundGraphError(\n `Graph.${method}: the \"${nodeOrEdge}\" node is not attached to the \"${nameOrEdge}\" edge (${source}, ${target}).`\n );\n }\n\n arg1 = add1;\n arg2 = add2;\n } else {\n edgeData = graph._edges.get(nodeOrEdge);\n\n if (!edgeData)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${nodeOrEdge}\" edge in the graph.`\n );\n\n if (mode === SOURCE) {\n nodeData = edgeData.source;\n } else {\n nodeData = edgeData.target;\n }\n\n arg1 = nameOrEdge;\n arg2 = add1;\n }\n\n return [nodeData, arg1, arg2];\n}\n\nfunction attachNodeAttributeGetter(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) {\n const [data, name] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1\n );\n\n return data.attributes[name];\n };\n}\n\nfunction attachNodeAttributesGetter(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge) {\n const [data] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge\n );\n\n return data.attributes;\n };\n}\n\nfunction attachNodeAttributeChecker(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) {\n const [data, name] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1\n );\n\n return data.attributes.hasOwnProperty(name);\n };\n}\n\nfunction attachNodeAttributeSetter(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1, add2) {\n const [data, name, value] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1,\n add2\n );\n\n data.attributes[name] = value;\n\n // Emitting\n this.emit('nodeAttributesUpdated', {\n key: data.key,\n type: 'set',\n attributes: data.attributes,\n name\n });\n\n return this;\n };\n}\n\nfunction attachNodeAttributeUpdater(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1, add2) {\n const [data, name, updater] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1,\n add2\n );\n\n if (typeof updater !== 'function')\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: updater should be a function.`\n );\n\n const attributes = data.attributes;\n const value = updater(attributes[name]);\n\n attributes[name] = value;\n\n // Emitting\n this.emit('nodeAttributesUpdated', {\n key: data.key,\n type: 'set',\n attributes: data.attributes,\n name\n });\n\n return this;\n };\n}\n\nfunction attachNodeAttributeRemover(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) {\n const [data, name] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1\n );\n\n delete data.attributes[name];\n\n // Emitting\n this.emit('nodeAttributesUpdated', {\n key: data.key,\n type: 'remove',\n attributes: data.attributes,\n name\n });\n\n return this;\n };\n}\n\nfunction attachNodeAttributesReplacer(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) {\n const [data, attributes] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1\n );\n\n if (!isPlainObject(attributes))\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: provided attributes are not a plain object.`\n );\n\n data.attributes = attributes;\n\n // Emitting\n this.emit('nodeAttributesUpdated', {\n key: data.key,\n type: 'replace',\n attributes: data.attributes\n });\n\n return this;\n };\n}\n\nfunction attachNodeAttributesMerger(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) {\n const [data, attributes] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1\n );\n\n if (!isPlainObject(attributes))\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: provided attributes are not a plain object.`\n );\n\n assign(data.attributes, attributes);\n\n // Emitting\n this.emit('nodeAttributesUpdated', {\n key: data.key,\n type: 'merge',\n attributes: data.attributes,\n data: attributes\n });\n\n return this;\n };\n}\n\nfunction attachNodeAttributesUpdater(Class, method, mode) {\n Class.prototype[method] = function (nodeOrEdge, nameOrEdge, add1) {\n const [data, updater] = findRelevantNodeData(\n this,\n method,\n mode,\n nodeOrEdge,\n nameOrEdge,\n add1\n );\n\n if (typeof updater !== 'function')\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: provided updater is not a function.`\n );\n\n data.attributes = updater(data.attributes);\n\n // Emitting\n this.emit('nodeAttributesUpdated', {\n key: data.key,\n type: 'update',\n attributes: data.attributes\n });\n\n return this;\n };\n}\n\n/**\n * List of methods to attach.\n */\nconst NODE_ATTRIBUTES_METHODS = [\n {\n name: element => `get${element}Attribute`,\n attacher: attachNodeAttributeGetter\n },\n {\n name: element => `get${element}Attributes`,\n attacher: attachNodeAttributesGetter\n },\n {\n name: element => `has${element}Attribute`,\n attacher: attachNodeAttributeChecker\n },\n {\n name: element => `set${element}Attribute`,\n attacher: attachNodeAttributeSetter\n },\n {\n name: element => `update${element}Attribute`,\n attacher: attachNodeAttributeUpdater\n },\n {\n name: element => `remove${element}Attribute`,\n attacher: attachNodeAttributeRemover\n },\n {\n name: element => `replace${element}Attributes`,\n attacher: attachNodeAttributesReplacer\n },\n {\n name: element => `merge${element}Attributes`,\n attacher: attachNodeAttributesMerger\n },\n {\n name: element => `update${element}Attributes`,\n attacher: attachNodeAttributesUpdater\n }\n];\n\n/**\n * Attach every attributes-related methods to a Graph class.\n *\n * @param {function} Graph - Target class.\n */\nexport default function attachNodeAttributesMethods(Graph) {\n NODE_ATTRIBUTES_METHODS.forEach(function ({name, attacher}) {\n // For nodes\n attacher(Graph, name('Node'), NODE);\n\n // For sources\n attacher(Graph, name('Source'), SOURCE);\n\n // For targets\n attacher(Graph, name('Target'), TARGET);\n\n // For opposites\n attacher(Graph, name('Opposite'), OPPOSITE);\n });\n}\n","/**\n * Graphology Edge Attributes methods\n * ===================================\n */\nimport {assign, isPlainObject, getMatchingEdge} from '../utils';\n\nimport {\n InvalidArgumentsGraphError,\n NotFoundGraphError,\n UsageGraphError\n} from '../errors';\n\n/**\n * Attach an attribute getter method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributeGetter(Class, method, type) {\n /**\n * Get the desired attribute for the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {string} name - Attribute's name.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {string} name - Attribute's name.\n *\n * @return {mixed} - The attribute's value.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, name) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 2) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element;\n const target = '' + name;\n\n name = arguments[2];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n return data.attributes[name];\n };\n}\n\n/**\n * Attach an attributes getter method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributesGetter(Class, method, type) {\n /**\n * Retrieves all the target element's attributes.\n *\n * Arity 2:\n * @param {any} element - Target element.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n *\n * @return {object} - The element's attributes.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 1) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element,\n target = '' + arguments[1];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n return data.attributes;\n };\n}\n\n/**\n * Attach an attribute checker method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributeChecker(Class, method, type) {\n /**\n * Checks whether the desired attribute is set for the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {string} name - Attribute's name.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {string} name - Attribute's name.\n *\n * @return {boolean}\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, name) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 2) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element;\n const target = '' + name;\n\n name = arguments[2];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n return data.attributes.hasOwnProperty(name);\n };\n}\n\n/**\n * Attach an attribute setter method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributeSetter(Class, method, type) {\n /**\n * Set the desired attribute for the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {string} name - Attribute's name.\n * @param {mixed} value - New attribute value.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {string} name - Attribute's name.\n * @param {mixed} value - New attribute value.\n *\n * @return {Graph} - Returns itself for chaining.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, name, value) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 3) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element;\n const target = '' + name;\n\n name = arguments[2];\n value = arguments[3];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n data.attributes[name] = value;\n\n // Emitting\n this.emit('edgeAttributesUpdated', {\n key: data.key,\n type: 'set',\n attributes: data.attributes,\n name\n });\n\n return this;\n };\n}\n\n/**\n * Attach an attribute updater method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributeUpdater(Class, method, type) {\n /**\n * Update the desired attribute for the given element (node or edge) using\n * the provided function.\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {string} name - Attribute's name.\n * @param {function} updater - Updater function.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {string} name - Attribute's name.\n * @param {function} updater - Updater function.\n *\n * @return {Graph} - Returns itself for chaining.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, name, updater) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 3) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element;\n const target = '' + name;\n\n name = arguments[2];\n updater = arguments[3];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n if (typeof updater !== 'function')\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: updater should be a function.`\n );\n\n data.attributes[name] = updater(data.attributes[name]);\n\n // Emitting\n this.emit('edgeAttributesUpdated', {\n key: data.key,\n type: 'set',\n attributes: data.attributes,\n name\n });\n\n return this;\n };\n}\n\n/**\n * Attach an attribute remover method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributeRemover(Class, method, type) {\n /**\n * Remove the desired attribute for the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {string} name - Attribute's name.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {string} name - Attribute's name.\n *\n * @return {Graph} - Returns itself for chaining.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, name) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 2) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element;\n const target = '' + name;\n\n name = arguments[2];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n delete data.attributes[name];\n\n // Emitting\n this.emit('edgeAttributesUpdated', {\n key: data.key,\n type: 'remove',\n attributes: data.attributes,\n name\n });\n\n return this;\n };\n}\n\n/**\n * Attach an attribute replacer method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributesReplacer(Class, method, type) {\n /**\n * Replace the attributes for the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {object} attributes - New attributes.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {object} attributes - New attributes.\n *\n * @return {Graph} - Returns itself for chaining.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, attributes) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 2) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element,\n target = '' + attributes;\n\n attributes = arguments[2];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n if (!isPlainObject(attributes))\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: provided attributes are not a plain object.`\n );\n\n data.attributes = attributes;\n\n // Emitting\n this.emit('edgeAttributesUpdated', {\n key: data.key,\n type: 'replace',\n attributes: data.attributes\n });\n\n return this;\n };\n}\n\n/**\n * Attach an attribute merger method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributesMerger(Class, method, type) {\n /**\n * Merge the attributes for the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {object} attributes - Attributes to merge.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {object} attributes - Attributes to merge.\n *\n * @return {Graph} - Returns itself for chaining.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, attributes) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 2) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element,\n target = '' + attributes;\n\n attributes = arguments[2];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n if (!isPlainObject(attributes))\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: provided attributes are not a plain object.`\n );\n\n assign(data.attributes, attributes);\n\n // Emitting\n this.emit('edgeAttributesUpdated', {\n key: data.key,\n type: 'merge',\n attributes: data.attributes,\n data: attributes\n });\n\n return this;\n };\n}\n\n/**\n * Attach an attribute updater method onto the provided class.\n *\n * @param {function} Class - Target class.\n * @param {string} method - Method name.\n * @param {string} type - Type of the edge to find.\n */\nfunction attachEdgeAttributesUpdater(Class, method, type) {\n /**\n * Update the attributes of the given element (node or edge).\n *\n * Arity 2:\n * @param {any} element - Target element.\n * @param {function} updater - Updater function.\n *\n * Arity 3 (only for edges):\n * @param {any} source - Source element.\n * @param {any} target - Target element.\n * @param {function} updater - Updater function.\n *\n * @return {Graph} - Returns itself for chaining.\n *\n * @throws {Error} - Will throw if too many arguments are provided.\n * @throws {Error} - Will throw if any of the elements is not found.\n */\n Class.prototype[method] = function (element, updater) {\n let data;\n\n if (this.type !== 'mixed' && type !== 'mixed' && type !== this.type)\n throw new UsageGraphError(\n `Graph.${method}: cannot find this type of edges in your ${this.type} graph.`\n );\n\n if (arguments.length > 2) {\n if (this.multi)\n throw new UsageGraphError(\n `Graph.${method}: cannot use a {source,target} combo when asking about an edge's attributes in a MultiGraph since we cannot infer the one you want information about.`\n );\n\n const source = '' + element,\n target = '' + updater;\n\n updater = arguments[2];\n\n data = getMatchingEdge(this, source, target, type);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find an edge for the given path (\"${source}\" - \"${target}\").`\n );\n } else {\n if (type !== 'mixed')\n throw new UsageGraphError(\n `Graph.${method}: calling this method with only a key (vs. a source and target) does not make sense since an edge with this key could have the other type.`\n );\n\n element = '' + element;\n data = this._edges.get(element);\n\n if (!data)\n throw new NotFoundGraphError(\n `Graph.${method}: could not find the \"${element}\" edge in the graph.`\n );\n }\n\n if (typeof updater !== 'function')\n throw new InvalidArgumentsGraphError(\n `Graph.${method}: provided updater is not a function.`\n );\n\n data.attributes = updater(data.attributes);\n\n // Emitting\n this.emit('edgeAttributesUpdated', {\n key: data.key,\n type: 'update',\n attributes: data.attributes\n });\n\n return this;\n };\n}\n\n/**\n * List of methods to attach.\n */\nconst EDGE_ATTRIBUTES_METHODS = [\n {\n name: element => `get${element}Attribute`,\n attacher: attachEdgeAttributeGetter\n },\n {\n name: element => `get${element}Attributes`,\n attacher: attachEdgeAttributesGetter\n },\n {\n name: element => `has${element}Attribute`,\n attacher: attachEdgeAttributeChecker\n },\n {\n name: element => `set${element}Attribute`,\n attacher: attachEdgeAttributeSetter\n },\n {\n name: element => `update${element}Attribute`,\n attacher: attachEdgeAttributeUpdater\n },\n {\n name: element => `remove${element}Attribute`,\n attacher: attachEdgeAttributeRemover\n },\n {\n name: element => `replace${element}Attributes`,\n attacher: attachEdgeAttributesReplacer\n },\n {\n name: element => `merge${element}Attributes`,\n attacher: attachEdgeAttributesMerger\n },\n {\n name: element => `update${element}Attributes`,\n attacher: attachEdgeAttributesUpdater\n }\n];\n\n/**\n * Attach every attributes-related methods to a Graph class.\n *\n * @param {function} Graph - Target class.\n */\nexport default function attachEdgeAttributesMethods(Graph) {\n EDGE_ATTRIBUTES_METHODS.forEach(function ({name, attacher}) {\n // For edges\n attacher(Graph, name('Edge'), 'mixed');\n\n // For directed edges\n attacher(Graph, name('DirectedEdge'), 'directed');\n\n // For undirected edges\n attacher(Graph, name('UndirectedEdge'), 'undirected');\n });\n}\n","/**\n * Graphology Edge Iteration\n * ==========================\n *\n * Attaching some methods to the Graph class to be able to iterate over a\n * graph's edges.\n */\nimport {chain, emptyIterator} from '../utils';\n\nimport {InvalidArgumentsGraphError, NotFoundGraphError} from '../errors';\n\n/**\n * Definitions.\n */\nconst EDGES_ITERATION = [\n {\n name: 'edges',\n type: 'mixed'\n },\n {\n name: 'inEdges',\n type: 'directed',\n direction: 'in'\n },\n {\n name: 'outEdges',\n type: 'directed',\n direction: 'out'\n },\n {\n name: 'inboundEdges',\n type: 'mixed',\n direction: 'in'\n },\n {\n name: 'outboundEdges',\n type: 'mixed',\n direction: 'out'\n },\n {\n name: 'directedEdges',\n type: 'directed'\n },\n {\n name: 'undirectedEdges',\n type: 'undirected'\n }\n];\n\n/**\n * Function iterating over edges from the given object to match one of them.\n *\n * @param {object} object - Target object.\n * @param {function} callback - Function to call.\n */\nfunction forEachSimple(breakable, object, callback, avoid) {\n let shouldBreak = false;\n\n for (const k in object) {\n if (k === avoid) continue;\n\n const edgeData = object[k];\n\n shouldBreak = callback(\n edgeData.key,\n edgeData.attributes,\n edgeData.source.key,\n edgeData.target.key,\n edgeData.source.attributes,\n edgeData.target.attributes,\n edgeData.undirected\n );\n\n if (breakable && shouldBreak) return edgeData.key;\n }\n\n return;\n}\n\nfunction forEachMulti(breakable, object, callback, avoid) {\n let edgeData, source, target;\n\n let shouldBreak = false;\n\n for (const k in object) {\n if (k === avoid) continue;\n\n edgeData = object[k];\n\n do {\n source = edgeData.source;\n target = edgeData.target;\n\n shouldBreak = callback(\n edgeData.key,\n edgeData.attributes,\n source.key,\n target.key,\n source.attributes,\n target.attributes,\n edgeData.undirected\n );\n\n if (breakable && shouldBreak) return edgeData.key;\n\n edgeData = edgeData.next;\n } while (edgeData !== undefined);\n }\n\n return;\n}\n\n/**\n * Function returning an iterator over edges from the given object.\n *\n * @param {object} object - Target object.\n * @return {Iterator}\n */\nfunction createIterator(object, avoid) {\n const keys = Object.keys(object);\n const l = keys.length;\n\n let edgeData;\n let i = 0;\n\n return {\n [Symbol.iterator]() {\n return this;\n },\n next() {\n do {\n if (!edgeData) {\n if (i >= l) return {done: true};\n\n const k = keys[i++];\n\n if (k === avoid) {\n edgeData = undefined;\n continue;\n }\n\n edgeData = object[k];\n } else {\n edgeData = edgeData.next;\n }\n } while (!edgeData);\n\n return {\n done: false,\n value: {\n edge: edgeData.key,\n attributes: edgeData.attributes,\n source: edgeData.source.key,\n target: edgeData.target.key,\n sourceAttributes: edgeData.source.attributes,\n targetAttributes: edgeData.target.attributes,\n undirected: edgeData.undirected\n }\n };\n }\n };\n}\n\n/**\n * Function iterating over the egdes from the object at given key to match\n * one of them.\n *\n * @param {object} object - Target object.\n * @param {mixed} k - Neighbor key.\n * @param {function} callback - Callback to use.\n */\nfunction forEachForKeySimple(breakable, object, k, callback) {\n const edgeData = object[k];\n\n if (!edgeData) return;\n\n const sourceData = edgeData.source;\n const targetData = edgeData.target;\n\n if (\n callback(\n edgeData.key,\n edgeData.attributes,\n sourceData.key,\n targetData.key,\n sourceData.attributes,\n targetData.attributes,\n edgeData.undirected\n ) &&\n breakable\n )\n return edgeData.key;\n}\n\nfunction forEachForKeyMulti(breakable, object, k, callback) {\n let edgeData = object[k];\n\n if (!edgeData) return;\n\n let shouldBreak = false;\n\n do {\n shouldBreak = callback(\n edgeData.key,\n edgeData.attributes,\n edgeData.source.key,\n edgeData.target