ngraph.graph
Advanced tools
Comparing version 0.0.4 to 0.0.5
685
index.js
@@ -5,3 +5,2 @@ /** | ||
/** | ||
@@ -14,368 +13,404 @@ * @example | ||
*/ | ||
module.exports = function () { | ||
// Graph structure is maintained as dictionary of nodes | ||
// and array of links. Each node has 'links' property which | ||
// hold all links related to that node. And general links | ||
// array is used to speed up all links enumeration. This is inefficient | ||
// in terms of memory, but simplifies coding. | ||
module.exports = function() { | ||
// Graph structure is maintained as dictionary of nodes | ||
// and array of links. Each node has 'links' property which | ||
// hold all links related to that node. And general links | ||
// array is used to speed up all links enumeration. This is inefficient | ||
// in terms of memory, but simplifies coding. | ||
var nodes = typeof Object.create === 'function' ? Object.create(null) : {}, | ||
links = [], | ||
// Hash of multi-edges. Used to track ids of edges between same nodes | ||
multiEdges = {}, | ||
nodesCount = 0, | ||
suspendEvents = 0, | ||
var nodes = typeof Object.create === 'function' ? Object.create(null) : {}, | ||
links = [], | ||
// Hash of multi-edges. Used to track ids of edges between same nodes | ||
multiEdges = {}, | ||
nodesCount = 0, | ||
suspendEvents = 0, | ||
// Accumlates all changes made during graph updates. | ||
// Each change element contains: | ||
// changeType - one of the strings: 'add', 'remove' or 'update'; | ||
// node - if change is related to node this property is set to changed graph's node; | ||
// link - if change is related to link this property is set to changed graph's link; | ||
changes = [], | ||
// Accumlates all changes made during graph updates. | ||
// Each change element contains: | ||
// changeType - one of the strings: 'add', 'remove' or 'update'; | ||
// node - if change is related to node this property is set to changed graph's node; | ||
// link - if change is related to link this property is set to changed graph's link; | ||
changes = [], | ||
fireGraphChanged = function (graph) { | ||
graph.fire('changed', changes); | ||
}, | ||
fireGraphChanged = function(graph) { | ||
graph.fire('changed', changes); | ||
}, | ||
// Enter, Exit Mofidication allows bulk graph updates without firing events. | ||
enterModification = function () { | ||
suspendEvents += 1; | ||
}, | ||
// Enter, Exit Mofidication allows bulk graph updates without firing events. | ||
enterModification = function() { | ||
suspendEvents += 1; | ||
}, | ||
exitModification = function (graph) { | ||
suspendEvents -= 1; | ||
if (suspendEvents === 0 && changes.length > 0) { | ||
fireGraphChanged(graph); | ||
changes.length = 0; | ||
} | ||
}, | ||
exitModification = function(graph) { | ||
suspendEvents -= 1; | ||
if (suspendEvents === 0 && changes.length > 0) { | ||
fireGraphChanged(graph); | ||
changes.length = 0; | ||
} | ||
}, | ||
recordNodeChange = function (node, changeType) { | ||
changes.push({node : node, changeType : changeType}); | ||
}, | ||
recordNodeChange = function(node, changeType) { | ||
changes.push({ | ||
node: node, | ||
changeType: changeType | ||
}); | ||
}, | ||
recordLinkChange = function (link, changeType) { | ||
changes.push({link : link, changeType : changeType}); | ||
}, | ||
linkConnectionSymbol = '👉 '; | ||
recordLinkChange = function(link, changeType) { | ||
changes.push({ | ||
link: link, | ||
changeType: changeType | ||
}); | ||
}, | ||
linkConnectionSymbol = '👉 '; | ||
var graphPart = { | ||
var graphPart = { | ||
/** | ||
* Adds node to the graph. If node with given id already exists in the graph | ||
* its data is extended with whatever comes in 'data' argument. | ||
* | ||
* @param nodeId the node's identifier. A string or number is preferred. | ||
* note: Node id should not contain 'linkConnectionSymbol'. This will break link identifiers | ||
* @param [data] additional data for the node being added. If node already | ||
* exists its data object is augmented with the new one. | ||
* | ||
* @return {node} The newly added node or node with given id if it already exists. | ||
*/ | ||
addNode : function (nodeId, data) { | ||
if (typeof nodeId === 'undefined') { | ||
throw new Error('Invalid node identifier'); | ||
} | ||
/** | ||
* Adds node to the graph. If node with given id already exists in the graph | ||
* its data is extended with whatever comes in 'data' argument. | ||
* | ||
* @param nodeId the node's identifier. A string or number is preferred. | ||
* note: Node id should not contain 'linkConnectionSymbol'. This will break link identifiers | ||
* @param [data] additional data for the node being added. If node already | ||
* exists its data object is augmented with the new one. | ||
* | ||
* @return {node} The newly added node or node with given id if it already exists. | ||
*/ | ||
addNode: function(nodeId, data) { | ||
if (typeof nodeId === 'undefined') { | ||
throw new Error('Invalid node identifier'); | ||
} | ||
enterModification(); | ||
enterModification(); | ||
var node = this.getNode(nodeId); | ||
if (!node) { | ||
// TODO: Should I check for linkConnectionSymbol here? | ||
node = new Node(nodeId); | ||
nodesCount++; | ||
var node = this.getNode(nodeId); | ||
if (!node) { | ||
// TODO: Should I check for linkConnectionSymbol here? | ||
node = new Node(nodeId); | ||
nodesCount++; | ||
recordNodeChange(node, 'add'); | ||
} else { | ||
recordNodeChange(node, 'update'); | ||
} | ||
recordNodeChange(node, 'add'); | ||
} else { | ||
recordNodeChange(node, 'update'); | ||
} | ||
node.data = data; | ||
node.data = data; | ||
nodes[nodeId] = node; | ||
nodes[nodeId] = node; | ||
exitModification(this); | ||
return node; | ||
}, | ||
exitModification(this); | ||
return node; | ||
}, | ||
/** | ||
* Adds a link to the graph. The function always create a new | ||
* link between two nodes. If one of the nodes does not exists | ||
* a new node is created. | ||
* | ||
* @param fromId link start node id; | ||
* @param toId link end node id; | ||
* @param [data] additional data to be set on the new link; | ||
* | ||
* @return {link} The newly created link | ||
*/ | ||
addLink : function (fromId, toId, data) { | ||
enterModification(); | ||
/** | ||
* Adds a link to the graph. The function always create a new | ||
* link between two nodes. If one of the nodes does not exists | ||
* a new node is created. | ||
* | ||
* @param fromId link start node id; | ||
* @param toId link end node id; | ||
* @param [data] additional data to be set on the new link; | ||
* | ||
* @return {link} The newly created link | ||
*/ | ||
addLink: function(fromId, toId, data) { | ||
enterModification(); | ||
var fromNode = this.getNode(fromId) || this.addNode(fromId); | ||
var toNode = this.getNode(toId) || this.addNode(toId); | ||
var fromNode = this.getNode(fromId) || this.addNode(fromId); | ||
var toNode = this.getNode(toId) || this.addNode(toId); | ||
var linkId = fromId.toString() + linkConnectionSymbol + toId.toString(); | ||
var isMultiEdge = multiEdges.hasOwnProperty(linkId); | ||
if (isMultiEdge || this.hasLink(fromId, toId)) { | ||
if (!isMultiEdge) { | ||
multiEdges[linkId] = 0; | ||
} | ||
linkId += '@' + (++multiEdges[linkId]); | ||
} | ||
var linkId = fromId.toString() + linkConnectionSymbol + toId.toString(); | ||
var isMultiEdge = multiEdges.hasOwnProperty(linkId); | ||
if (isMultiEdge || this.hasLink(fromId, toId)) { | ||
if (!isMultiEdge) { | ||
multiEdges[linkId] = 0; | ||
} | ||
linkId += '@' + (++multiEdges[linkId]); | ||
} | ||
var link = new Link(fromId, toId, data, linkId); | ||
var link = new Link(fromId, toId, data, linkId); | ||
links.push(link); | ||
links.push(link); | ||
// TODO: this is not cool. On large graphs potentially would consume more memory. | ||
fromNode.links.push(link); | ||
toNode.links.push(link); | ||
// TODO: this is not cool. On large graphs potentially would consume more memory. | ||
fromNode.links.push(link); | ||
toNode.links.push(link); | ||
recordLinkChange(link, 'add'); | ||
recordLinkChange(link, 'add'); | ||
exitModification(this); | ||
exitModification(this); | ||
return link; | ||
}, | ||
return link; | ||
}, | ||
/** | ||
* Removes link from the graph. If link does not exist does nothing. | ||
* | ||
* @param link - object returned by addLink() or getLinks() methods. | ||
* | ||
* @returns true if link was removed; false otherwise. | ||
*/ | ||
removeLink : function (link) { | ||
if (!link) { return false; } | ||
var idx = indexOfElementInArray(link, links); | ||
if (idx < 0) { return false; } | ||
/** | ||
* Removes link from the graph. If link does not exist does nothing. | ||
* | ||
* @param link - object returned by addLink() or getLinks() methods. | ||
* | ||
* @returns true if link was removed; false otherwise. | ||
*/ | ||
removeLink: function(link) { | ||
if (!link) { | ||
return false; | ||
} | ||
var idx = indexOfElementInArray(link, links); | ||
if (idx < 0) { | ||
return false; | ||
} | ||
enterModification(); | ||
enterModification(); | ||
links.splice(idx, 1); | ||
links.splice(idx, 1); | ||
var fromNode = this.getNode(link.fromId); | ||
var toNode = this.getNode(link.toId); | ||
var fromNode = this.getNode(link.fromId); | ||
var toNode = this.getNode(link.toId); | ||
if (fromNode) { | ||
idx = indexOfElementInArray(link, fromNode.links); | ||
if (idx >= 0) { | ||
fromNode.links.splice(idx, 1); | ||
} | ||
} | ||
if (fromNode) { | ||
idx = indexOfElementInArray(link, fromNode.links); | ||
if (idx >= 0) { | ||
fromNode.links.splice(idx, 1); | ||
} | ||
} | ||
if (toNode) { | ||
idx = indexOfElementInArray(link, toNode.links); | ||
if (idx >= 0) { | ||
toNode.links.splice(idx, 1); | ||
} | ||
} | ||
if (toNode) { | ||
idx = indexOfElementInArray(link, toNode.links); | ||
if (idx >= 0) { | ||
toNode.links.splice(idx, 1); | ||
} | ||
} | ||
recordLinkChange(link, 'remove'); | ||
recordLinkChange(link, 'remove'); | ||
exitModification(this); | ||
exitModification(this); | ||
return true; | ||
}, | ||
return true; | ||
}, | ||
/** | ||
* Removes node with given id from the graph. If node does not exist in the graph | ||
* does nothing. | ||
* | ||
* @param nodeId node's identifier passed to addNode() function. | ||
* | ||
* @returns true if node was removed; false otherwise. | ||
*/ | ||
removeNode: function (nodeId) { | ||
var node = this.getNode(nodeId); | ||
if (!node) { return false; } | ||
/** | ||
* Removes node with given id from the graph. If node does not exist in the graph | ||
* does nothing. | ||
* | ||
* @param nodeId node's identifier passed to addNode() function. | ||
* | ||
* @returns true if node was removed; false otherwise. | ||
*/ | ||
removeNode: function(nodeId) { | ||
var node = this.getNode(nodeId); | ||
if (!node) { | ||
return false; | ||
} | ||
enterModification(); | ||
enterModification(); | ||
while (node.links.length) { | ||
var link = node.links[0]; | ||
this.removeLink(link); | ||
} | ||
while (node.links.length) { | ||
var link = node.links[0]; | ||
this.removeLink(link); | ||
} | ||
delete nodes[nodeId]; | ||
nodesCount--; | ||
delete nodes[nodeId]; | ||
nodesCount--; | ||
recordNodeChange(node, 'remove'); | ||
recordNodeChange(node, 'remove'); | ||
exitModification(this); | ||
exitModification(this); | ||
return true; | ||
}, | ||
return true; | ||
}, | ||
/** | ||
* Gets node with given identifier. If node does not exist undefined value is returned. | ||
* | ||
* @param nodeId requested node identifier; | ||
* | ||
* @return {node} in with requested identifier or undefined if no such node exists. | ||
*/ | ||
getNode : function (nodeId) { | ||
return nodes[nodeId]; | ||
}, | ||
/** | ||
* Gets node with given identifier. If node does not exist undefined value is returned. | ||
* | ||
* @param nodeId requested node identifier; | ||
* | ||
* @return {node} in with requested identifier or undefined if no such node exists. | ||
*/ | ||
getNode: function(nodeId) { | ||
return nodes[nodeId]; | ||
}, | ||
/** | ||
* Gets number of nodes in this graph. | ||
* | ||
* @return number of nodes in the graph. | ||
*/ | ||
getNodesCount : function () { | ||
return nodesCount; | ||
}, | ||
/** | ||
* Gets number of nodes in this graph. | ||
* | ||
* @return number of nodes in the graph. | ||
*/ | ||
getNodesCount: function() { | ||
return nodesCount; | ||
}, | ||
/** | ||
* Gets total number of links in the graph. | ||
*/ | ||
getLinksCount : function () { | ||
return links.length; | ||
}, | ||
/** | ||
* Gets total number of links in the graph. | ||
*/ | ||
getLinksCount: function() { | ||
return links.length; | ||
}, | ||
/** | ||
* Gets all links (inbound and outbound) from the node with given id. | ||
* If node with given id is not found null is returned. | ||
* | ||
* @param nodeId requested node identifier. | ||
* | ||
* @return Array of links from and to requested node if such node exists; | ||
* otherwise null is returned. | ||
*/ | ||
getLinks : function (nodeId) { | ||
var node = this.getNode(nodeId); | ||
return node ? node.links : null; | ||
}, | ||
/** | ||
* Gets all links (inbound and outbound) from the node with given id. | ||
* If node with given id is not found null is returned. | ||
* | ||
* @param nodeId requested node identifier. | ||
* | ||
* @return Array of links from and to requested node if such node exists; | ||
* otherwise null is returned. | ||
*/ | ||
getLinks: function(nodeId) { | ||
var node = this.getNode(nodeId); | ||
return node ? node.links : null; | ||
}, | ||
/** | ||
* Invokes callback on each node of the graph. | ||
* | ||
* @param {Function(node)} callback Function to be invoked. The function | ||
* is passed one argument: visited node. | ||
*/ | ||
forEachNode : function (callback) { | ||
if (typeof callback !== 'function') { | ||
return; | ||
} | ||
var node; | ||
/** | ||
* Invokes callback on each node of the graph. | ||
* | ||
* @param {Function(node)} callback Function to be invoked. The function | ||
* is passed one argument: visited node. | ||
*/ | ||
forEachNode: createNodeIterator(), | ||
for (node in nodes) { | ||
if (callback(nodes[node])) { | ||
return; // client doesn't want to proceed. return. | ||
} | ||
/** | ||
* Invokes callback on every linked (adjacent) node to the given one. | ||
* | ||
* @param nodeId Identifier of the requested node. | ||
* @param {Function(node, link)} callback Function to be called on all linked nodes. | ||
* The function is passed two parameters: adjacent node and link object itself. | ||
* @param oriented if true graph treated as oriented. | ||
*/ | ||
forEachLinkedNode: function(nodeId, callback, oriented) { | ||
var node = this.getNode(nodeId), | ||
i, | ||
link, | ||
linkedNodeId; | ||
if (node && node.links && typeof callback === 'function') { | ||
// Extraced orientation check out of the loop to increase performance | ||
if (oriented) { | ||
for (i = 0; i < node.links.length; ++i) { | ||
link = node.links[i]; | ||
if (link.fromId === nodeId) { | ||
callback(nodes[link.toId], link); | ||
} | ||
}, | ||
} | ||
} else { | ||
for (i = 0; i < node.links.length; ++i) { | ||
link = node.links[i]; | ||
linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; | ||
/** | ||
* Invokes callback on every linked (adjacent) node to the given one. | ||
* | ||
* @param nodeId Identifier of the requested node. | ||
* @param {Function(node, link)} callback Function to be called on all linked nodes. | ||
* The function is passed two parameters: adjacent node and link object itself. | ||
* @param oriented if true graph treated as oriented. | ||
*/ | ||
forEachLinkedNode : function (nodeId, callback, oriented) { | ||
var node = this.getNode(nodeId), | ||
i, | ||
link, | ||
linkedNodeId; | ||
callback(nodes[linkedNodeId], link); | ||
} | ||
} | ||
} | ||
}, | ||
if (node && node.links && typeof callback === 'function') { | ||
// Extraced orientation check out of the loop to increase performance | ||
if (oriented) { | ||
for (i = 0; i < node.links.length; ++i) { | ||
link = node.links[i]; | ||
if (link.fromId === nodeId) { | ||
callback(nodes[link.toId], link); | ||
} | ||
} | ||
} else { | ||
for (i = 0; i < node.links.length; ++i) { | ||
link = node.links[i]; | ||
linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId; | ||
/** | ||
* Enumerates all links in the graph | ||
* | ||
* @param {Function(link)} callback Function to be called on all links in the graph. | ||
* The function is passed one parameter: graph's link object. | ||
* | ||
* Link object contains at least the following fields: | ||
* fromId - node id where link starts; | ||
* toId - node id where link ends, | ||
* data - additional data passed to graph.addLink() method. | ||
*/ | ||
forEachLink: function(callback) { | ||
var i, length; | ||
if (typeof callback === 'function') { | ||
for (i = 0, length = links.length; i < length; ++i) { | ||
callback(links[i]); | ||
} | ||
} | ||
}, | ||
callback(nodes[linkedNodeId], link); | ||
} | ||
} | ||
} | ||
}, | ||
/** | ||
* Suspend all notifications about graph changes until | ||
* endUpdate is called. | ||
*/ | ||
beginUpdate: function() { | ||
enterModification(); | ||
}, | ||
/** | ||
* Enumerates all links in the graph | ||
* | ||
* @param {Function(link)} callback Function to be called on all links in the graph. | ||
* The function is passed one parameter: graph's link object. | ||
* | ||
* Link object contains at least the following fields: | ||
* fromId - node id where link starts; | ||
* toId - node id where link ends, | ||
* data - additional data passed to graph.addLink() method. | ||
*/ | ||
forEachLink : function (callback) { | ||
var i, length; | ||
if (typeof callback === 'function') { | ||
for (i = 0, length = links.length; i < length; ++i) { | ||
callback(links[i]); | ||
} | ||
} | ||
}, | ||
/** | ||
* Resumes all notifications about graph changes and fires | ||
* graph 'changed' event in case there are any pending changes. | ||
*/ | ||
endUpdate: function() { | ||
exitModification(this); | ||
}, | ||
/** | ||
* Suspend all notifications about graph changes until | ||
* endUpdate is called. | ||
*/ | ||
beginUpdate : function () { | ||
enterModification(); | ||
}, | ||
/** | ||
* Removes all nodes and links from the graph. | ||
*/ | ||
clear: function() { | ||
var that = this; | ||
that.beginUpdate(); | ||
that.forEachNode(function(node) { | ||
that.removeNode(node.id); | ||
}); | ||
that.endUpdate(); | ||
}, | ||
/** | ||
* Resumes all notifications about graph changes and fires | ||
* graph 'changed' event in case there are any pending changes. | ||
*/ | ||
endUpdate : function () { | ||
exitModification(this); | ||
}, | ||
/** | ||
* Detects whether there is a link between two nodes. | ||
* Operation complexity is O(n) where n - number of links of a node. | ||
* | ||
* @returns link if there is one. null otherwise. | ||
*/ | ||
hasLink: function(fromNodeId, toNodeId) { | ||
// TODO: Use adjacency matrix to speed up this operation. | ||
var node = this.getNode(fromNodeId), | ||
i; | ||
if (!node) { | ||
return null; | ||
} | ||
/** | ||
* Removes all nodes and links from the graph. | ||
*/ | ||
clear : function () { | ||
var that = this; | ||
that.beginUpdate(); | ||
that.forEachNode(function (node) { that.removeNode(node.id); }); | ||
that.endUpdate(); | ||
}, | ||
for (i = 0; i < node.links.length; ++i) { | ||
var link = node.links[i]; | ||
if (link.fromId === fromNodeId && link.toId === toNodeId) { | ||
return link; | ||
} | ||
} | ||
/** | ||
* Detects whether there is a link between two nodes. | ||
* Operation complexity is O(n) where n - number of links of a node. | ||
* | ||
* @returns link if there is one. null otherwise. | ||
*/ | ||
hasLink : function (fromNodeId, toNodeId) { | ||
// TODO: Use adjacency matrix to speed up this operation. | ||
var node = this.getNode(fromNodeId), | ||
i; | ||
if (!node) { | ||
return null; | ||
} | ||
return null; // no link. | ||
} | ||
}; | ||
for (i = 0; i < node.links.length; ++i) { | ||
var link = node.links[i]; | ||
if (link.fromId === fromNodeId && link.toId === toNodeId) { | ||
return link; | ||
} | ||
} | ||
// Let graph fire events before we return it to the caller. | ||
var eventify = require('ngraph.events'); | ||
eventify(graphPart); | ||
return null; // no link. | ||
} | ||
}; | ||
return graphPart; | ||
// Let graph fire events before we return it to the caller. | ||
var eventify = require('ngraph.events'); | ||
eventify(graphPart); | ||
function createNodeIterator() { | ||
// Object.keys iterator is 1.3x faster than `for in` loop. | ||
// See `https://github.com/anvaka/ngraph.graph/tree/bench-for-in-vs-obj-keys` | ||
// branch for perf test | ||
return Object.keys ? objectKeysIterator : forInIterator; | ||
} | ||
return graphPart; | ||
function objectKeysIterator(callback) { | ||
if (typeof callback !== 'function') { | ||
return; | ||
} | ||
var keys = Object.keys(nodes); | ||
for (var i = 0; i < keys.length; ++i) { | ||
if (callback(nodes[keys[i]])) { | ||
return; // client doesn't want to proceed. return. | ||
} | ||
} | ||
} | ||
function forInIterator(callback) { | ||
if (typeof callback !== 'function') { | ||
return; | ||
} | ||
var node; | ||
for (node in nodes) { | ||
if (callback(nodes[node])) { | ||
return; // client doesn't want to proceed. return. | ||
} | ||
} | ||
} | ||
}; | ||
@@ -385,16 +420,16 @@ | ||
function indexOfElementInArray(element, array) { | ||
if (array.indexOf) { | ||
return array.indexOf(element); | ||
} | ||
if (array.indexOf) { | ||
return array.indexOf(element); | ||
} | ||
var len = array.length, | ||
i; | ||
var len = array.length, | ||
i; | ||
for (i = 0; i < len; i += 1) { | ||
if (array[i] === element) { | ||
return i; | ||
} | ||
for (i = 0; i < len; i += 1) { | ||
if (array[i] === element) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
return -1; | ||
} | ||
@@ -406,5 +441,5 @@ | ||
function Node(id) { | ||
this.id = id; | ||
this.links = []; | ||
this.data = null; | ||
this.id = id; | ||
this.links = []; | ||
this.data = null; | ||
} | ||
@@ -417,6 +452,6 @@ | ||
function Link(fromId, toId, data, id) { | ||
this.fromId = fromId; | ||
this.toId = toId; | ||
this.data = data; | ||
this.id = id; | ||
this.fromId = fromId; | ||
this.toId = toId; | ||
this.data = data; | ||
this.id = id; | ||
} |
{ | ||
"name": "ngraph.graph", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "Base graph structure in ngraph.*", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
517
24037