Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ngraph.graph

Package Overview
Dependencies
Maintainers
1
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ngraph.graph - npm Package Compare versions

Comparing version 0.0.5 to 0.0.6

test/iteration.js

497

index.js

@@ -12,3 +12,7 @@ /**

*/
module.exports = function() {
module.exports = createGraph;
var eventify = require('ngraph.events');
function createGraph() {
// Graph structure is maintained as dictionary of nodes

@@ -27,3 +31,12 @@ // and array of links. Each node has 'links' property which

// Accumlates all changes made during graph updates.
forEachNode = createNodeIterator(),
linkConnectionSymbol = '👉 ',
// Our graph API provides means to listen to graph changes. Users can subscribe
// to be notified about changes in the graph by using `on` method. However
// in some cases they don't use it. To avoid unnecessary memory consumption
// we will not record graph changes until we have at least one subscriber.
// Code below supports this optimization.
//
// Accumulates all changes made during graph updates.
// Each change element contains:

@@ -34,37 +47,9 @@ // changeType - one of the strings: 'add', 'remove' or 'update';

changes = [],
recordLinkChange = noop,
recordNodeChange = noop,
enterModification = noop,
exitModification = noop;
fireGraphChanged = function(graph) {
graph.fire('changed', changes);
},
// 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;
}
},
recordNodeChange = function(node, changeType) {
changes.push({
node: node,
changeType: changeType
});
},
recordLinkChange = function(link, changeType) {
changes.push({
link: link,
changeType: changeType
});
},
linkConnectionSymbol = '👉 ';
// this is our public API:
var graphPart = {
/**

@@ -81,28 +66,4 @@ * Adds node to the graph. If node with given id already exists in the graph

*/
addNode: function(nodeId, data) {
if (typeof nodeId === 'undefined') {
throw new Error('Invalid node identifier');
}
addNode: addNode,
enterModification();
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');
}
node.data = data;
nodes[nodeId] = node;
exitModification(this);
return node;
},
/**

@@ -119,32 +80,4 @@ * Adds a link to the graph. The function always create a new

*/
addLink: function(fromId, toId, data) {
enterModification();
addLink: addLink,
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 link = new Link(fromId, toId, data, linkId);
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');
exitModification(this);
return link;
},
/**

@@ -157,39 +90,4 @@ * Removes link from the graph. If link does not exist does nothing.

*/
removeLink: function(link) {
if (!link) {
return false;
}
var idx = indexOfElementInArray(link, links);
if (idx < 0) {
return false;
}
removeLink: removeLink,
enterModification();
links.splice(idx, 1);
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 (toNode) {
idx = indexOfElementInArray(link, toNode.links);
if (idx >= 0) {
toNode.links.splice(idx, 1);
}
}
recordLinkChange(link, 'remove');
exitModification(this);
return true;
},
/**

@@ -203,25 +101,4 @@ * Removes node with given id from the graph. If node does not exist in the graph

*/
removeNode: function(nodeId) {
var node = this.getNode(nodeId);
if (!node) {
return false;
}
removeNode: removeNode,
enterModification();
while (node.links.length) {
var link = node.links[0];
this.removeLink(link);
}
delete nodes[nodeId];
nodesCount--;
recordNodeChange(node, 'remove');
exitModification(this);
return true;
},
/**

@@ -234,5 +111,3 @@ * Gets node with given identifier. If node does not exist undefined value is returned.

*/
getNode: function(nodeId) {
return nodes[nodeId];
},
getNode: getNode,

@@ -264,6 +139,3 @@ /**

*/
getLinks: function(nodeId) {
var node = this.getNode(nodeId);
return node ? node.links : null;
},
getLinks: getLinks,

@@ -276,3 +148,3 @@ /**

*/
forEachNode: createNodeIterator(),
forEachNode: forEachNode,

@@ -287,28 +159,4 @@ /**

*/
forEachLinkedNode: function(nodeId, callback, oriented) {
var node = this.getNode(nodeId),
i,
link,
linkedNodeId;
forEachLinkedNode: forEachLinkedNode,
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;
callback(nodes[linkedNodeId], link);
}
}
}
},
/**

@@ -325,10 +173,3 @@ * Enumerates all links in the graph

*/
forEachLink: function(callback) {
var i, length;
if (typeof callback === 'function') {
for (i = 0, length = links.length; i < length; ++i) {
callback(links[i]);
}
}
},
forEachLink: forEachLink,

@@ -339,5 +180,3 @@ /**

*/
beginUpdate: function() {
enterModification();
},
beginUpdate: enterModification,

@@ -348,5 +187,3 @@ /**

*/
endUpdate: function() {
exitModification(this);
},
endUpdate: exitModification,

@@ -356,10 +193,3 @@ /**

*/
clear: function() {
var that = this;
that.beginUpdate();
that.forEachNode(function(node) {
that.removeNode(node.id);
});
that.endUpdate();
},
clear: clear,

@@ -372,27 +202,250 @@ /**

*/
hasLink: function(fromNodeId, toNodeId) {
// TODO: Use adjacency matrix to speed up this operation.
var node = this.getNode(fromNodeId),
i;
if (!node) {
return null;
hasLink: hasLink
};
// this will add `on()` and `fire()` methods.
eventify(graphPart);
monitorSubscribers();
return graphPart;
function monitorSubscribers() {
var realOn = graphPart.on;
// replace real `on` with our temporary on, which will trigger change
// modification monitoring:
graphPart.on = on;
function on() {
// now it's time to start tracking stuff:
enterModification = enterModificationReal;
exitModification = exitModificationReal;
recordLinkChange = recordLinkChangeReal;
recordNodeChange = recordNodeChangeReal;
// this will replace current `on` method with real pub/sub from `eventify`.
graphPart.on = realOn;
// delegate to real `on` handler:
return realOn.apply(graphPart, arguments);
}
}
function recordLinkChangeReal(link, changeType) {
changes.push({
link: link,
changeType: changeType
});
}
function recordNodeChangeReal(node, changeType) {
changes.push({
node: node,
changeType: changeType
});
}
function addNode(nodeId, data) {
if (nodeId === undefined) {
throw new Error('Invalid node identifier');
}
enterModification();
var node = getNode(nodeId);
if (!node) {
// TODO: Should I check for linkConnectionSymbol here?
node = new Node(nodeId);
nodesCount++;
recordNodeChange(node, 'add');
} else {
recordNodeChange(node, 'update');
}
node.data = data;
nodes[nodeId] = node;
exitModification();
return node;
}
function getNode(nodeId) {
return nodes[nodeId];
}
function removeNode(nodeId) {
var node = getNode(nodeId);
if (!node) {
return false;
}
enterModification();
while (node.links.length) {
var link = node.links[0];
removeLink(link);
}
delete nodes[nodeId];
nodesCount--;
recordNodeChange(node, 'remove');
exitModification();
return true;
}
function addLink(fromId, toId, data) {
enterModification();
var fromNode = getNode(fromId) || addNode(fromId);
var toNode = getNode(toId) || addNode(toId);
var linkId = fromId.toString() + linkConnectionSymbol + toId.toString();
var isMultiEdge = multiEdges.hasOwnProperty(linkId);
if (isMultiEdge || hasLink(fromId, toId)) {
if (!isMultiEdge) {
multiEdges[linkId] = 0;
}
linkId += '@' + (++multiEdges[linkId]);
}
for (i = 0; i < node.links.length; ++i) {
var link = node.links[i];
if (link.fromId === fromNodeId && link.toId === toNodeId) {
return link;
}
var link = new Link(fromId, toId, data, linkId);
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');
exitModification();
return link;
}
function getLinks(nodeId) {
var node = getNode(nodeId);
return node ? node.links : null;
}
function removeLink(link) {
if (!link) {
return false;
}
var idx = indexOfElementInArray(link, links);
if (idx < 0) {
return false;
}
enterModification();
links.splice(idx, 1);
var fromNode = getNode(link.fromId);
var toNode = getNode(link.toId);
if (fromNode) {
idx = indexOfElementInArray(link, fromNode.links);
if (idx >= 0) {
fromNode.links.splice(idx, 1);
}
}
return null; // no link.
if (toNode) {
idx = indexOfElementInArray(link, toNode.links);
if (idx >= 0) {
toNode.links.splice(idx, 1);
}
}
};
// Let graph fire events before we return it to the caller.
var eventify = require('ngraph.events');
eventify(graphPart);
recordLinkChange(link, 'remove');
return graphPart;
exitModification();
return true;
}
function hasLink(fromNodeId, toNodeId) {
// TODO: Use adjacency matrix to speed up this operation.
var node = getNode(fromNodeId),
i;
if (!node) {
return null;
}
for (i = 0; i < node.links.length; ++i) {
var link = node.links[i];
if (link.fromId === fromNodeId && link.toId === toNodeId) {
return link;
}
}
return null; // no link.
}
function clear() {
enterModification();
forEachNode(function(node) {
removeNode(node.id);
});
exitModification();
}
function forEachLink(callback) {
var i, length;
if (typeof callback === 'function') {
for (i = 0, length = links.length; i < length; ++i) {
callback(links[i]);
}
}
}
function forEachLinkedNode(nodeId, callback, oriented) {
var node = getNode(nodeId),
i,
link,
linkedNodeId;
if (node && node.links && typeof callback === 'function') {
// Extracted 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;
callback(nodes[linkedNodeId], link);
}
}
}
}
// we will not fire anything until users of this library explicitly call `on()`
// method.
function noop() {}
// Enter, Exit modification allows bulk graph updates without firing events.
function enterModificationReal() {
suspendEvents += 1;
}
function exitModificationReal() {
suspendEvents -= 1;
if (suspendEvents === 0 && changes.length > 0) {
graphPart.fire('changed', changes);
changes.length = 0;
}
}
function createNodeIterator() {

@@ -413,3 +466,3 @@ // Object.keys iterator is 1.3x faster than `for in` loop.

if (callback(nodes[keys[i]])) {
return; // client doesn't want to proceed. return.
return; // client doesn't want to proceed. Return.
}

@@ -427,7 +480,7 @@ }

if (callback(nodes[node])) {
return; // client doesn't want to proceed. return.
return; // client doesn't want to proceed. Return.
}
}
}
};
}

@@ -434,0 +487,0 @@ // need this for old browsers. Should this be a separate module?

{
"name": "ngraph.graph",
"version": "0.0.5",
"version": "0.0.6",
"description": "Base graph structure in ngraph.*",
"main": "index.js",
"scripts": {
"test": "tap test/*.js"
"test": "node node_modules/argg test/*.js",
"plato": "plato -d reports/plato index.js",
"cover": "istanbul cover --dir reports/coverage node_modules/argg test/*.js"
},

@@ -23,2 +25,5 @@ "repository": {

"devDependencies": {
"argg": "0.0.1",
"istanbul": "^0.3.5",
"plato": "^1.3.0",
"tap": "~0.4.4"

@@ -25,0 +30,0 @@ },

@@ -20,4 +20,4 @@ ngraph.graph

``` js
g.addNode('hello');
g.addNode('world');
g.addNode('hello');
g.addNode('world');
```

@@ -72,3 +72,3 @@

g.forEachLinkedNode('hello', function(linkedNode, link){
console.log("Connected node: ", linkedNode.id, linkedNode.data);
console.log("Connected node: ", linkedNode.id, linkedNode.data);
console.dir(link); // link object itself

@@ -99,3 +99,3 @@ });

g.forEachLinkedNode('hello', function(linkedNode, link){
g.removeLink(link);
g.removeLink(link);
});

@@ -102,0 +102,0 @@ ```

var test = require('tap').test,
createGraph = require('..');
createGraph = require('..');

@@ -10,3 +10,4 @@ test('add node adds node', function(t) {

t.equal(graph.getNodesCount(), 1, 'addNode failed');
t.equal(graph.getNodesCount(), 1, 'exactly one node');
t.equal(graph.getLinksCount(), 0, 'no links');
t.equal(graph.getNode(1), node, 'invalid node returned by addNode (or getNode)');

@@ -18,4 +19,47 @@ t.equal(node.data, customData, 'data was not set properly');

test('add nodeid same as a prototype property', function (t) {
test('hasLink checks links', function (t) {
var graph = createGraph();
graph.addLink(1, 2);
var link12 = graph.hasLink(1, 2);
t.ok(link12.fromId === 1 && link12.toId === 2, 'link is found');
// this is somewhat doubtful... has link will return null, but forEachLinkedNode
// will actually iterate over this link. Not sure how to have consistency here
// for now documenting behavior in the test:
var noLink = graph.hasLink(2, 1);
t.notOk(noLink, 'hasLink is always directional');
var obviousNo = graph.hasLink();
t.notOk(obviousNo, 'No links there');
t.end();
});
test('it throw if no node id is passed', function(t) {
var graph = createGraph();
t.throws(function() {
graph.addNode(); // no id, should throw
}, 'It throws on undefined node');
t.end();
});
test('it fires update event when node is updated', function(t) {
t.plan(3);
var graph = createGraph();
graph.addNode(1, 'hello');
graph.on('changed', checkChangedEvent);
graph.addNode(1, 'world');
t.end();
function checkChangedEvent(changes) {
var change = changes[0];
t.equals(change.node.id, 1);
t.equals(change.node.data, 'world');
t.equals(change.changeType, 'update');
}
});
test('it can add node with id similar to reserved prototype property', function(t) {
var graph = createGraph();
graph.addNode('constructor');

@@ -25,3 +69,3 @@ graph.addLink('watch', 'constructor');

var iterated = 0;
graph.forEachNode(function () {
graph.forEachNode(function() {
iterated += 1;

@@ -31,2 +75,3 @@ });

t.ok(graph.hasLink('watch', 'constructor'));
t.equals(graph.getLinksCount(), 1, 'one link');
t.equal(iterated, 2, 'has two nodes');

@@ -40,6 +85,7 @@ t.end();

var link = graph.addLink(1, 2),
firstNodeLinks = graph.getLinks(1),
secondNodeLinks = graph.getLinks(2);
firstNodeLinks = graph.getLinks(1),
secondNodeLinks = graph.getLinks(2);
t.equal(graph.getNodesCount(), 2, 'addLink failed');
t.equal(graph.getNodesCount(), 2, 'Two nodes');
t.equal(graph.getLinksCount(), 1, 'One link');
t.equal(firstNodeLinks.length, 1, 'number of links of the first node is wrong');

@@ -52,2 +98,19 @@ t.equal(secondNodeLinks.length, 1, 'number of links of the second node is wrong');

test('it can add multi-edges', function (t) {
t.plan(5);
var graph = createGraph();
graph.addLink(1, 2, 'first');
graph.addLink(1, 2, 'second');
graph.addLink(1, 2, 'third');
t.equal(graph.getLinksCount(), 3, 'three links!');
t.equal(graph.getNodesCount(), 2, 'Two nodes');
graph.forEachLinkedNode(1, function (otherNode, link) {
t.ok(link.data === 'first' || link.data === 'second' || link.data === 'third', 'Link is here');
});
t.end();
});
test('add one node fires changed event', function(t) {

@@ -59,5 +122,5 @@ t.plan(3);

graph.on('changed', function(changes) {
t.ok(changes && changes.length === 1, "Only one change should be recorded");
t.equal(changes[0].node.id, testNodeId, "Wrong node change notification");
t.equal(changes[0].changeType, 'add', "Add change type expected.");
t.ok(changes && changes.length === 1, "Only one change should be recorded");
t.equal(changes[0].node.id, testNodeId, "Wrong node change notification");
t.equal(changes[0].changeType, 'add', "Add change type expected.");
});

@@ -73,9 +136,10 @@

var graph = createGraph();
var fromId = 1, toId = 2;
var fromId = 1,
toId = 2;
graph.on('changed', function(changes) {
t.ok(changes && changes.length === 3, "Three change should be recorded: node, node and link");
t.equal(changes[2].link.fromId, fromId, "Wrong link from Id");
t.equal(changes[2].link.toId, toId, "Wrong link toId");
t.equal(changes[2].changeType, 'add', "Add change type expected.");
t.ok(changes && changes.length === 3, "Three change should be recorded: node, node and link");
t.equal(changes[2].link.fromId, fromId, "Wrong link from Id");
t.equal(changes[2].link.toId, toId, "Wrong link toId");
t.equal(changes[2].changeType, 'add', "Add change type expected.");
});

@@ -98,3 +162,3 @@

test('remove link removes it', function(t) {
t.plan(3);
t.plan(5);

@@ -104,16 +168,31 @@ var graph = createGraph();

graph.removeLink(link);
var linkIsRemoved = graph.removeLink(link);
t.equal(graph.getNodesCount(), 2, 'remove link should not remove nodes');
t.equal(graph.getLinksCount(), 0, 'No Links');
t.equal(graph.getLinks(1).length, 0, 'link should be removed from the first node');
t.equal(graph.getLinks(2).length, 0, 'link should be removed from the second node');
t.ok(linkIsRemoved, 'Link removal is successful');
graph.forEachLink(function(link){
test.ok(false, 'No links should be in graph');
graph.forEachLink(function(link) {
test.ok(false, 'No links should be in graph');
});
t.end();
});
test('remove link returns false if no link removed', function (t) {
var graph = createGraph();
var link = graph.addLink(1, 2);
var result = graph.removeLink('blah');
t.notOk(result, 'Link is not removed');
var alsoNo = graph.removeLink();
t.notOk(alsoNo, 'No link - no removal');
t.end();
});
test('remove isolated node fires changed event', function(t) {
t.plan(3);
t.plan(4);
var graph = createGraph();

@@ -123,8 +202,9 @@ graph.addNode(1);

graph.on('changed', function(changes) {
t.ok(changes && changes.length === 1, "One change should be recorded: node removed");
t.equal(changes[0].node.id, 1, "Wrong node Id");
t.equal(changes[0].changeType, 'remove', "'remove' change type expected.");
t.ok(changes && changes.length === 1, "One change should be recorded: node removed");
t.equal(changes[0].node.id, 1, "Wrong node Id");
t.equal(changes[0].changeType, 'remove', "'remove' change type expected.");
});
graph.removeNode(1);
var result = graph.removeNode(1);
t.ok(result, 'node is removed');
t.end();

@@ -139,5 +219,5 @@ });

graph.on('changed', function(changes) {
t.ok(changes && changes.length === 1, "One change should be recorded: link removed");
t.equal(changes[0].link, link, "Wrong link removed");
t.equal(changes[0].changeType, 'remove', "'remove' change type expected.");
t.ok(changes && changes.length === 1, "One change should be recorded: link removed");
t.equal(changes[0].link, link, "Wrong link removed");
t.equal(changes[0].changeType, 'remove', "'remove' change type expected.");
});

@@ -152,11 +232,11 @@

var graph = createGraph(),
link = graph.addLink(1, 2),
nodeIdToRemove = 1;
link = graph.addLink(1, 2),
nodeIdToRemove = 1;
graph.on('changed', function(changes) {
t.ok(changes && changes.length === 2, "Two changes should be recorded: link and node removed");
t.equal(changes[0].link, link, "Wrong link removed");
t.equal(changes[0].changeType, 'remove', "'remove' change type expected.");
t.equal(changes[1].node.id, nodeIdToRemove, "Wrong node removed");
t.equal(changes[1].changeType, 'remove', "'remove' change type expected.");
t.ok(changes && changes.length === 2, "Two changes should be recorded: link and node removed");
t.equal(changes[0].link, link, "Wrong link removed");
t.equal(changes[0].changeType, 'remove', "'remove' change type expected.");
t.equal(changes[1].node.id, nodeIdToRemove, "Wrong node removed");
t.equal(changes[1].changeType, 'remove', "'remove' change type expected.");
});

@@ -170,5 +250,5 @@

var graph = createGraph(),
link12 = graph.addLink(1, 2),
link13 = graph.addLink(1, 3),
nodeIdToRemove = 1;
link12 = graph.addLink(1, 2),
link13 = graph.addLink(1, 3),
nodeIdToRemove = 1;

@@ -182,5 +262,25 @@ graph.removeNode(1);

graph.forEachLink(function(link) {
test.ok(false, 'No links should be in graph');
test.ok(false, 'No links should be in graph');
});
t.end();
});
test('remove node returns false when no node removed', function (t) {
var graph = createGraph();
graph.addNode('hello');
var result = graph.removeNode('blah');
t.notOk(result, 'No "blah" node');
t.end();
});
test('clearGraph clears graph', function (t) {
var graph = createGraph();
graph.addNode('hello');
graph.addLink(1, 2);
graph.clear();
t.equal(graph.getNodesCount(), 0, 'No nodes');
t.equal(graph.getLinksCount(), 0, 'No links');
t.end();
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc