graphology
Version:
A robust and multipurpose Graph object for JavaScript.
1,561 lines (1,344 loc) • 169 kB
JavaScript
'use strict';
var events = require('events');
var Iterator = require('obliterator/iterator');
var take = require('obliterator/take');
var chain = require('obliterator/chain');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var Iterator__default = /*#__PURE__*/_interopDefaultLegacy(Iterator);
var take__default = /*#__PURE__*/_interopDefaultLegacy(take);
var chain__default = /*#__PURE__*/_interopDefaultLegacy(chain);
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++;
};
}
/**
* 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;
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 {
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."));
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 = 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 attachAttributesMerger(Class, method, type) {
/**
* Replace 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 {
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."));
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."));
assign(data.attributes, attributes); // Emitting
this.emit('edgeAttributesUpdated', {
key: data.key,
type: 'merge',
attributes: data.attributes,
data: attributes
});
return this;
};
}
/**
* List of methods to attach.
*/
var ATTRIBUTES_METHODS = [{
name: function name(element) {
return "get".concat(element, "Attribute");
},
attacher: attachAttributeGetter
}, {
name: function name(element) {
return "get".concat(element, "Attributes");
},
attacher: attachAttributesGetter
}, {
name: function name(element) {
return "has".concat(element, "Attribute");
},
attacher: attachAttributeChecker
}, {
name: function name(element) {
return "set".concat(element, "Attribute");
},
attacher: attachAttributeSetter
}, {
name: function name(element) {
return "update".concat(element, "Attribute");
},
attacher: attachAttributeUpdater
}, {
name: function name(element) {
return "remove".concat(element, "Attribute");
},
attacher: attachAttributeRemover
}, {
name: function name(element) {
return "replace".concat(element, "Attributes");
},
attacher: attachAttributesReplacer
}, {
name: function name(element) {
return "merge".concat(element, "Attributes");
},
attacher: attachAttributesMerger
}];
/**
* Attach every attributes-related methods to a Graph class.
*
* @param {function} Graph - Target class.
*/
function attachAttributesMethods(Graph) {
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');
});
}
/**
* 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 collecting edges from the given object.
*
* @param {array} edges - Edges array to populate.
* @param {object} object - Target object.
* @return {array} - The found edges.
*/
function collectSimple(edges, object) {
for (var k in object) {
edges.push(object[k].key);
}
}
function collectMulti(edges, object) {
for (var k in object) {
object[k].forEach(function (edgeData) {
return edges.push(edgeData.key);
});
}
}
/**
* Function iterating over edges from the given object using a callback.
*
* @param {object} object - Target object.
* @param {function} callback - Function to call.
*/
function forEachSimple(object, callback, avoid) {
for (var k in object) {
if (k === avoid) continue;
var edgeData = object[k];
callback(edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes, edgeData.undirected, edgeData.generatedKey);
}
}
function forEachMulti(object, callback, avoid) {
for (var k in object) {
if (k === avoid) continue;
object[k].forEach(function (edgeData) {
return callback(edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes, edgeData.undirected, edgeData.generatedKey);
});
}
}
/**
* Function iterating over edges from the given object using a callback until
* the return value of the callback is truthy.
*
* @param {object} object - Target object.
* @param {function} callback - Function to call.
*/
function forEachSimpleUntil(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, edgeData.generatedKey);
if (shouldBreak) return true;
}
return false;
}
function forEachMultiUntil(object, callback, avoid) {
var iterator, step, edgeData, source, target;
var shouldBreak = false;
for (var k in object) {
if (k === avoid) continue;
iterator = object[k].values();
while (step = iterator.next(), step.done !== true) {
edgeData = step.value;
source = edgeData.source;
target = edgeData.target;
shouldBreak = callback(edgeData.key, edgeData.attributes, source.key, target.key, source.attributes, target.attributes, edgeData.undirected, edgeData.generatedKey);
if (shouldBreak) return true;
}
}
return false;
}
/**
* 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),
l = keys.length;
var inner = null,
i = 0;
return new Iterator__default["default"](function next() {
var edgeData;
if (inner) {
var step = inner.next();
if (step.done) {
inner = null;
i++;
return next();
}
edgeData = step.value;
} else {
if (i >= l) return {
done: true
};
var k = keys[i];
if (k === avoid) {
i++;
return next();
}
edgeData = object[k];
if (edgeData instanceof Set) {
inner = edgeData.values();
return next();
}
i++;
}
return {
done: false,
value: [edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes]
};
});
}
/**
* Function collecting edges from the given object at given key.
*
* @param {array} edges - Edges array to populate.
* @param {object} object - Target object.
* @param {mixed} k - Neighbor key.
* @return {array} - The found edges.
*/
function collectForKeySimple(edges, object, k) {
var edgeData = object[k];
if (!edgeData) return;
edges.push(edgeData.key);
}
function collectForKeyMulti(edges, object, k) {
var edgesData = object[k];
if (!edgesData) return;
edgesData.forEach(function (edgeData) {
return edges.push(edgeData.key);
});
}
/**
* Function iterating over the egdes from the object at given key using
* a callback.
*
* @param {object} object - Target object.
* @param {mixed} k - Neighbor key.
* @param {function} callback - Callback to use.
*/
function forEachForKeySimple(object, k, callback) {
var edgeData = object[k];
if (!edgeData) return;
var sourceData = edgeData.source;
var targetData = edgeData.target;
callback(edgeData.key, edgeData.attributes, sourceData.key, targetData.key, sourceData.attributes, targetData.attributes, edgeData.undirected, edgeData.generatedKey);
}
function forEachForKeyMulti(object, k, callback) {
var edgesData = object[k];
if (!edgesData) return;
edgesData.forEach(function (edgeData) {
return callback(edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes, edgeData.undirected, edgeData.generatedKey);
});
}
/**
* Function iterating over the egdes from the object at given key using
* a callback until it returns a truthy value to stop iteration.
*
* @param {object} object - Target object.
* @param {mixed} k - Neighbor key.
* @param {function} callback - Callback to use.
*/
function forEachForKeySimpleUntil(object, k, callback) {
var edgeData = object[k];
if (!edgeData) return false;
var sourceData = edgeData.source;
var targetData = edgeData.target;
return callback(edgeData.key, edgeData.attributes, sourceData.key, targetData.key, sourceData.attributes, targetData.attributes, edgeData.undirected, edgeData.generatedKey);
}
function forEachForKeyMultiUntil(object, k, callback) {
var edgesData = object[k];
if (!edgesData) return false;
var shouldBreak = false;
var iterator = edgesData.values();
var step, edgeData;
while (step = iterator.next(), step.done !== true) {
edgeData = step.value;
shouldBreak = callback(edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes, edgeData.undirected, edgeData.generatedKey);
if (shouldBreak) return true;
}
return false;
}
/**
* 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 v = object[k];
if (v instanceof Set) {
var iterator = v.values();
return new Iterator__default["default"](function () {
var step = iterator.next();
if (step.done) return step;
var edgeData = step.value;
return {
done: false,
value: [edgeData.key, edgeData.attributes, edgeData.source.key, edgeData.target.key, edgeData.source.attributes, edgeData.target.attributes]
};
});
}
return Iterator__default["default"].of([v.key, v.attributes, v.source.key, v.target.key, v.source.attributes, v.target.attributes]);
}
/**
* 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__default["default"](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.
*
* @param {Graph} graph - Target Graph instance.
* @param {string} type - Type of edges to retrieve.
* @param {function} callback - Function to call.
*/
function forEachEdge(graph, type, callback) {
if (graph.size === 0) return;
var shouldFilter = type !== 'mixed' && type !== graph.type;
var mask = type === 'undirected';
var step, data;
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;
callback(key, attributes, source.key, target.key, source.attributes, target.attributes, data.undirected, data.generatedKey);
}
}
/**
* Function iterating over a graph's edges using a callback until it returns
* a truthy value to stop iteration.
*
* @param {Graph} graph - Target Graph instance.
* @param {string} type - Type of edges to retrieve.
* @param {function} callback - Function to call.
*/
function forEachEdgeUntil(graph, type, callback) {
if (graph.size === 0) return false;
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 _data2 = data,
key = _data2.key,
attributes = _data2.attributes,
source = _data2.source,
target = _data2.target;
shouldBreak = callback(key, attributes, source.key, target.key, source.attributes, target.attributes, data.undirected, data.generatedKey);
if (shouldBreak) return true;
}
return false;
}
/**
* 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__default["default"].empty();
var shouldFilter = type !== 'mixed' && type !== graph.type;
var mask = type === 'undirected';
var iterator = graph._edges.values();
return new Iterator__default["default"](function next() {
var step, data; // eslint-disable-next-line no-constant-condition
while (true) {
step = iterator.next();
if (step.done) return step;
data = step.value;
if (shouldFilter && data.undirected !== mask) continue;
break;
}
var value = [data.key, data.attributes, data.source.key, data.target.key, data.source.attributes, data.target.attributes];
return {
value: value,
done: false
};
});
}
/**
* 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 = [];
var fn = multi ? collectMulti : collectSimple;
if (type !== 'undirected') {
if (direction !== 'out') fn(edges, nodeData["in"]);
if (direction !== 'in') fn(edges, nodeData.out); // Handling self loop edge case
if (!direction && nodeData.directedSelfLoops > 0) edges.splice(edges.lastIndexOf(nodeData.key), 1);
}
if (type !== 'directed') {
fn(edges, nodeData.undirected);
}
return edges;
}
/**
* Function iterating over a node's edges using a callback.
*
* @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.
*/
func