;(function(undefined) {
|
|
'use strict';
|
|
|
|
var _methods = Object.create(null),
|
|
_indexes = Object.create(null),
|
|
_initBindings = Object.create(null),
|
|
_methodBindings = Object.create(null),
|
|
_methodBeforeBindings = Object.create(null),
|
|
_defaultSettings = {
|
|
immutable: true,
|
|
clone: true
|
|
},
|
|
_defaultSettingsFunction = function(key) {
|
|
return _defaultSettings[key];
|
|
};
|
|
|
|
/**
|
|
* The graph constructor. It initializes the data and the indexes, and binds
|
|
* the custom indexes and methods to its own scope.
|
|
*
|
|
* Recognized parameters:
|
|
* **********************
|
|
* Here is the exhaustive list of every accepted parameters in the settings
|
|
* object:
|
|
*
|
|
* {boolean} clone Indicates if the data have to be cloned in methods
|
|
* to add nodes or edges.
|
|
* {boolean} immutable Indicates if nodes "id" values and edges "id",
|
|
* "source" and "target" values must be set as
|
|
* immutable.
|
|
*
|
|
* @param {?configurable} settings Eventually a settings function.
|
|
* @return {graph} The new graph instance.
|
|
*/
|
|
var graph = function(settings) {
|
|
var k,
|
|
fn,
|
|
data;
|
|
|
|
/**
|
|
* DATA:
|
|
* *****
|
|
* Every data that is callable from graph methods are stored in this "data"
|
|
* object. This object will be served as context for all these methods,
|
|
* and it is possible to add other type of data in it.
|
|
*/
|
|
data = {
|
|
/**
|
|
* SETTINGS FUNCTION:
|
|
* ******************
|
|
*/
|
|
settings: settings || _defaultSettingsFunction,
|
|
|
|
/**
|
|
* MAIN DATA:
|
|
* **********
|
|
*/
|
|
nodesArray: [],
|
|
edgesArray: [],
|
|
|
|
/**
|
|
* GLOBAL INDEXES:
|
|
* ***************
|
|
* These indexes just index data by ids.
|
|
*/
|
|
nodesIndex: Object.create(null),
|
|
edgesIndex: Object.create(null),
|
|
|
|
/**
|
|
* LOCAL INDEXES:
|
|
* **************
|
|
* These indexes refer from node to nodes. Each key is an id, and each
|
|
* value is the array of the ids of related nodes.
|
|
*/
|
|
inNeighborsIndex: Object.create(null),
|
|
outNeighborsIndex: Object.create(null),
|
|
allNeighborsIndex: Object.create(null),
|
|
|
|
inNeighborsCount: Object.create(null),
|
|
outNeighborsCount: Object.create(null),
|
|
allNeighborsCount: Object.create(null)
|
|
};
|
|
|
|
// Execute bindings:
|
|
for (k in _initBindings)
|
|
_initBindings[k].call(data);
|
|
|
|
// Add methods to both the scope and the data objects:
|
|
for (k in _methods) {
|
|
fn = __bindGraphMethod(k, data, _methods[k]);
|
|
this[k] = fn;
|
|
data[k] = fn;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* A custom tool to bind methods such that function that are bound to it will
|
|
* be executed anytime the method is called.
|
|
*
|
|
* @param {string} methodName The name of the method to bind.
|
|
* @param {object} scope The scope where the method must be executed.
|
|
* @param {function} fn The method itself.
|
|
* @return {function} The new method.
|
|
*/
|
|
function __bindGraphMethod(methodName, scope, fn) {
|
|
var result = function() {
|
|
var k,
|
|
res;
|
|
|
|
// Execute "before" bound functions:
|
|
for (k in _methodBeforeBindings[methodName])
|
|
_methodBeforeBindings[methodName][k].apply(scope, arguments);
|
|
|
|
// Apply the method:
|
|
res = fn.apply(scope, arguments);
|
|
|
|
// Execute bound functions:
|
|
for (k in _methodBindings[methodName])
|
|
_methodBindings[methodName][k].apply(scope, arguments);
|
|
|
|
// Return res:
|
|
return res;
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This custom tool function removes every pair key/value from an hash. The
|
|
* goal is to avoid creating a new object while some other references are
|
|
* still hanging in some scopes...
|
|
*
|
|
* @param {object} obj The object to empty.
|
|
* @return {object} The empty object.
|
|
*/
|
|
function __emptyObject(obj) {
|
|
var k;
|
|
|
|
for (k in obj)
|
|
if (!('hasOwnProperty' in obj) || obj.hasOwnProperty(k))
|
|
delete obj[k];
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This global method adds a method that will be bound to the futurly created
|
|
* graph instances.
|
|
*
|
|
* Since these methods will be bound to their scope when the instances are
|
|
* created, it does not use the prototype. Because of that, methods have to
|
|
* be added before instances are created to make them available.
|
|
*
|
|
* Here is an example:
|
|
*
|
|
* > graph.addMethod('getNodesCount', function() {
|
|
* > return this.nodesArray.length;
|
|
* > });
|
|
* >
|
|
* > var myGraph = new graph();
|
|
* > console.log(myGraph.getNodesCount()); // outputs 0
|
|
*
|
|
* @param {string} methodName The name of the method.
|
|
* @param {function} fn The method itself.
|
|
* @return {object} The global graph constructor.
|
|
*/
|
|
graph.addMethod = function(methodName, fn) {
|
|
if (
|
|
typeof methodName !== 'string' ||
|
|
typeof fn !== 'function' ||
|
|
arguments.length !== 2
|
|
)
|
|
throw 'addMethod: Wrong arguments.';
|
|
|
|
if (_methods[methodName] || graph[methodName])
|
|
throw 'The method "' + methodName + '" already exists.';
|
|
|
|
_methods[methodName] = fn;
|
|
_methodBindings[methodName] = Object.create(null);
|
|
_methodBeforeBindings[methodName] = Object.create(null);
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* This global method returns true if the method has already been added, and
|
|
* false else.
|
|
*
|
|
* Here are some examples:
|
|
*
|
|
* > graph.hasMethod('addNode'); // returns true
|
|
* > graph.hasMethod('hasMethod'); // returns true
|
|
* > graph.hasMethod('unexistingMethod'); // returns false
|
|
*
|
|
* @param {string} methodName The name of the method.
|
|
* @return {boolean} The result.
|
|
*/
|
|
graph.hasMethod = function(methodName) {
|
|
return !!(_methods[methodName] || graph[methodName]);
|
|
};
|
|
|
|
/**
|
|
* This global methods attaches a function to a method. Anytime the specified
|
|
* method is called, the attached function is called right after, with the
|
|
* same arguments and in the same scope. The attached function is called
|
|
* right before if the last argument is true, unless the method is the graph
|
|
* constructor.
|
|
*
|
|
* To attach a function to the graph constructor, use 'constructor' as the
|
|
* method name (first argument).
|
|
*
|
|
* The main idea is to have a clean way to keep custom indexes up to date,
|
|
* for instance:
|
|
*
|
|
* > var timesAddNodeCalled = 0;
|
|
* > graph.attach('addNode', 'timesAddNodeCalledInc', function() {
|
|
* > timesAddNodeCalled++;
|
|
* > });
|
|
* >
|
|
* > var myGraph = new graph();
|
|
* > console.log(timesAddNodeCalled); // outputs 0
|
|
* >
|
|
* > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
|
|
* > console.log(timesAddNodeCalled); // outputs 2
|
|
*
|
|
* The idea for calling a function before is to provide pre-processors, for
|
|
* instance:
|
|
*
|
|
* > var colorPalette = { Person: '#C3CBE1', Place: '#9BDEBD' };
|
|
* > graph.attach('addNode', 'applyNodeColorPalette', function(n) {
|
|
* > n.color = colorPalette[n.category];
|
|
* > }, true);
|
|
* >
|
|
* > var myGraph = new graph();
|
|
* > myGraph.addNode({ id: 'n0', category: 'Person' });
|
|
* > console.log(myGraph.nodes('n0').color); // outputs '#C3CBE1'
|
|
*
|
|
* @param {string} methodName The name of the related method or
|
|
* "constructor".
|
|
* @param {string} key The key to identify the function to attach.
|
|
* @param {function} fn The function to bind.
|
|
* @param {boolean} before If true the function is called right before.
|
|
* @return {object} The global graph constructor.
|
|
*/
|
|
graph.attach = function(methodName, key, fn, before) {
|
|
if (
|
|
typeof methodName !== 'string' ||
|
|
typeof key !== 'string' ||
|
|
typeof fn !== 'function' ||
|
|
arguments.length < 3 ||
|
|
arguments.length > 4
|
|
)
|
|
throw 'attach: Wrong arguments.';
|
|
|
|
var bindings;
|
|
|
|
if (methodName === 'constructor')
|
|
bindings = _initBindings;
|
|
else {
|
|
if (before) {
|
|
if (!_methodBeforeBindings[methodName])
|
|
throw 'The method "' + methodName + '" does not exist.';
|
|
|
|
bindings = _methodBeforeBindings[methodName];
|
|
}
|
|
else {
|
|
if (!_methodBindings[methodName])
|
|
throw 'The method "' + methodName + '" does not exist.';
|
|
|
|
bindings = _methodBindings[methodName];
|
|
}
|
|
}
|
|
|
|
if (bindings[key])
|
|
throw 'A function "' + key + '" is already attached ' +
|
|
'to the method "' + methodName + '".';
|
|
|
|
bindings[key] = fn;
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Alias of attach(methodName, key, fn, true).
|
|
*/
|
|
graph.attachBefore = function(methodName, key, fn) {
|
|
return this.attach(methodName, key, fn, true);
|
|
};
|
|
|
|
/**
|
|
* This methods is just an helper to deal with custom indexes. It takes as
|
|
* arguments the name of the index and an object containing all the different
|
|
* functions to bind to the methods.
|
|
*
|
|
* Here is a basic example, that creates an index to keep the number of nodes
|
|
* in the current graph. It also adds a method to provide a getter on that
|
|
* new index:
|
|
*
|
|
* > sigma.classes.graph.addIndex('nodesCount', {
|
|
* > constructor: function() {
|
|
* > this.nodesCount = 0;
|
|
* > },
|
|
* > addNode: function() {
|
|
* > this.nodesCount++;
|
|
* > },
|
|
* > dropNode: function() {
|
|
* > this.nodesCount--;
|
|
* > }
|
|
* > });
|
|
* >
|
|
* > sigma.classes.graph.addMethod('getNodesCount', function() {
|
|
* > return this.nodesCount;
|
|
* > });
|
|
* >
|
|
* > var myGraph = new sigma.classes.graph();
|
|
* > console.log(myGraph.getNodesCount()); // outputs 0
|
|
* >
|
|
* > myGraph.addNode({ id: '1' }).addNode({ id: '2' });
|
|
* > console.log(myGraph.getNodesCount()); // outputs 2
|
|
*
|
|
* @param {string} name The name of the index.
|
|
* @param {object} bindings The object containing the functions to bind.
|
|
* @return {object} The global graph constructor.
|
|
*/
|
|
graph.addIndex = function(name, bindings) {
|
|
if (
|
|
typeof name !== 'string' ||
|
|
Object(bindings) !== bindings ||
|
|
arguments.length !== 2
|
|
)
|
|
throw 'addIndex: Wrong arguments.';
|
|
|
|
if (_indexes[name])
|
|
throw 'The index "' + name + '" already exists.';
|
|
|
|
var k;
|
|
|
|
// Store the bindings:
|
|
_indexes[name] = bindings;
|
|
|
|
// Attach the bindings:
|
|
for (k in bindings)
|
|
if (typeof bindings[k] !== 'function')
|
|
throw 'The bindings must be functions.';
|
|
else
|
|
graph.attach(k, name, bindings[k]);
|
|
|
|
return this;
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This method adds a node to the graph. The node must be an object, with a
|
|
* string under the key "id". Except for this, it is possible to add any
|
|
* other attribute, that will be preserved all along the manipulations.
|
|
*
|
|
* If the graph option "clone" has a truthy value, the node will be cloned
|
|
* when added to the graph. Also, if the graph option "immutable" has a
|
|
* truthy value, its id will be defined as immutable.
|
|
*
|
|
* @param {object} node The node to add.
|
|
* @return {object} The graph instance.
|
|
*/
|
|
graph.addMethod('addNode', function(node) {
|
|
// Check that the node is an object and has an id:
|
|
if (Object(node) !== node || arguments.length !== 1)
|
|
throw 'addNode: Wrong arguments.';
|
|
|
|
if (typeof node.id !== 'string' && typeof node.id !== 'number')
|
|
throw 'The node must have a string or number id.';
|
|
|
|
if (this.nodesIndex[node.id])
|
|
throw 'The node "' + node.id + '" already exists.';
|
|
|
|
var k,
|
|
id = node.id,
|
|
validNode = Object.create(null);
|
|
|
|
// Check the "clone" option:
|
|
if (this.settings('clone')) {
|
|
for (k in node)
|
|
if (k !== 'id')
|
|
validNode[k] = node[k];
|
|
} else
|
|
validNode = node;
|
|
|
|
// Check the "immutable" option:
|
|
if (this.settings('immutable'))
|
|
Object.defineProperty(validNode, 'id', {
|
|
value: id,
|
|
enumerable: true
|
|
});
|
|
else
|
|
validNode.id = id;
|
|
|
|
// Add empty containers for edges indexes:
|
|
this.inNeighborsIndex[id] = Object.create(null);
|
|
this.outNeighborsIndex[id] = Object.create(null);
|
|
this.allNeighborsIndex[id] = Object.create(null);
|
|
|
|
this.inNeighborsCount[id] = 0;
|
|
this.outNeighborsCount[id] = 0;
|
|
this.allNeighborsCount[id] = 0;
|
|
|
|
// Add the node to indexes:
|
|
this.nodesArray.push(validNode);
|
|
this.nodesIndex[validNode.id] = validNode;
|
|
|
|
// Return the current instance:
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* This method adds an edge to the graph. The edge must be an object, with a
|
|
* string under the key "id", and strings under the keys "source" and
|
|
* "target" that design existing nodes. Except for this, it is possible to
|
|
* add any other attribute, that will be preserved all along the
|
|
* manipulations.
|
|
*
|
|
* If the graph option "clone" has a truthy value, the edge will be cloned
|
|
* when added to the graph. Also, if the graph option "immutable" has a
|
|
* truthy value, its id, source and target will be defined as immutable.
|
|
*
|
|
* @param {object} edge The edge to add.
|
|
* @return {object} The graph instance.
|
|
*/
|
|
graph.addMethod('addEdge', function(edge) {
|
|
// Check that the edge is an object and has an id:
|
|
if (Object(edge) !== edge || arguments.length !== 1)
|
|
throw 'addEdge: Wrong arguments.';
|
|
|
|
if (typeof edge.id !== 'string' && typeof edge.id !== 'number')
|
|
throw 'The edge must have a string or number id.';
|
|
|
|
if ((typeof edge.source !== 'string' && typeof edge.source !== 'number') ||
|
|
!this.nodesIndex[edge.source])
|
|
throw 'The edge source must have an existing node id.';
|
|
|
|
if ((typeof edge.target !== 'string' && typeof edge.target !== 'number') ||
|
|
!this.nodesIndex[edge.target])
|
|
throw 'The edge target must have an existing node id.';
|
|
|
|
if (this.edgesIndex[edge.id])
|
|
throw 'The edge "' + edge.id + '" already exists.';
|
|
|
|
var k,
|
|
validEdge = Object.create(null);
|
|
|
|
// Check the "clone" option:
|
|
if (this.settings('clone')) {
|
|
for (k in edge)
|
|
if (k !== 'id' && k !== 'source' && k !== 'target')
|
|
validEdge[k] = edge[k];
|
|
} else
|
|
validEdge = edge;
|
|
|
|
// Check the "immutable" option:
|
|
if (this.settings('immutable')) {
|
|
Object.defineProperty(validEdge, 'id', {
|
|
value: edge.id,
|
|
enumerable: true
|
|
});
|
|
|
|
Object.defineProperty(validEdge, 'source', {
|
|
value: edge.source,
|
|
enumerable: true
|
|
});
|
|
|
|
Object.defineProperty(validEdge, 'target', {
|
|
value: edge.target,
|
|
enumerable: true
|
|
});
|
|
} else {
|
|
validEdge.id = edge.id;
|
|
validEdge.source = edge.source;
|
|
validEdge.target = edge.target;
|
|
}
|
|
|
|
// Add the edge to indexes:
|
|
this.edgesArray.push(validEdge);
|
|
this.edgesIndex[validEdge.id] = validEdge;
|
|
|
|
if (!this.inNeighborsIndex[validEdge.target][validEdge.source])
|
|
this.inNeighborsIndex[validEdge.target][validEdge.source] =
|
|
Object.create(null);
|
|
this.inNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
|
|
validEdge;
|
|
|
|
if (!this.outNeighborsIndex[validEdge.source][validEdge.target])
|
|
this.outNeighborsIndex[validEdge.source][validEdge.target] =
|
|
Object.create(null);
|
|
this.outNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
|
|
validEdge;
|
|
|
|
if (!this.allNeighborsIndex[validEdge.source][validEdge.target])
|
|
this.allNeighborsIndex[validEdge.source][validEdge.target] =
|
|
Object.create(null);
|
|
this.allNeighborsIndex[validEdge.source][validEdge.target][validEdge.id] =
|
|
validEdge;
|
|
|
|
if (validEdge.target !== validEdge.source) {
|
|
if (!this.allNeighborsIndex[validEdge.target][validEdge.source])
|
|
this.allNeighborsIndex[validEdge.target][validEdge.source] =
|
|
Object.create(null);
|
|
this.allNeighborsIndex[validEdge.target][validEdge.source][validEdge.id] =
|
|
validEdge;
|
|
}
|
|
|
|
// Keep counts up to date:
|
|
this.inNeighborsCount[validEdge.target]++;
|
|
this.outNeighborsCount[validEdge.source]++;
|
|
this.allNeighborsCount[validEdge.target]++;
|
|
this.allNeighborsCount[validEdge.source]++;
|
|
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* This method drops a node from the graph. It also removes each edge that is
|
|
* bound to it, through the dropEdge method. An error is thrown if the node
|
|
* does not exist.
|
|
*
|
|
* @param {string} id The node id.
|
|
* @return {object} The graph instance.
|
|
*/
|
|
graph.addMethod('dropNode', function(id) {
|
|
// Check that the arguments are valid:
|
|
if ((typeof id !== 'string' && typeof id !== 'number') ||
|
|
arguments.length !== 1)
|
|
throw 'dropNode: Wrong arguments.';
|
|
|
|
if (!this.nodesIndex[id])
|
|
throw 'The node "' + id + '" does not exist.';
|
|
|
|
var i, k, l;
|
|
|
|
// Remove the node from indexes:
|
|
delete this.nodesIndex[id];
|
|
for (i = 0, l = this.nodesArray.length; i < l; i++)
|
|
if (this.nodesArray[i].id === id) {
|
|
this.nodesArray.splice(i, 1);
|
|
break;
|
|
}
|
|
|
|
// Remove related edges:
|
|
for (i = this.edgesArray.length - 1; i >= 0; i--)
|
|
if (this.edgesArray[i].source === id || this.edgesArray[i].target === id)
|
|
this.dropEdge(this.edgesArray[i].id);
|
|
|
|
// Remove related edge indexes:
|
|
delete this.inNeighborsIndex[id];
|
|
delete this.outNeighborsIndex[id];
|
|
delete this.allNeighborsIndex[id];
|
|
|
|
delete this.inNeighborsCount[id];
|
|
delete this.outNeighborsCount[id];
|
|
delete this.allNeighborsCount[id];
|
|
|
|
for (k in this.nodesIndex) {
|
|
delete this.inNeighborsIndex[k][id];
|
|
delete this.outNeighborsIndex[k][id];
|
|
delete this.allNeighborsIndex[k][id];
|
|
}
|
|
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* This method drops an edge from the graph. An error is thrown if the edge
|
|
* does not exist.
|
|
*
|
|
* @param {string} id The edge id.
|
|
* @return {object} The graph instance.
|
|
*/
|
|
graph.addMethod('dropEdge', function(id) {
|
|
// Check that the arguments are valid:
|
|
if ((typeof id !== 'string' && typeof id !== 'number') ||
|
|
arguments.length !== 1)
|
|
throw 'dropEdge: Wrong arguments.';
|
|
|
|
if (!this.edgesIndex[id])
|
|
throw 'The edge "' + id + '" does not exist.';
|
|
|
|
var i, l, edge;
|
|
|
|
// Remove the edge from indexes:
|
|
edge = this.edgesIndex[id];
|
|
delete this.edgesIndex[id];
|
|
for (i = 0, l = this.edgesArray.length; i < l; i++)
|
|
if (this.edgesArray[i].id === id) {
|
|
this.edgesArray.splice(i, 1);
|
|
break;
|
|
}
|
|
|
|
delete this.inNeighborsIndex[edge.target][edge.source][edge.id];
|
|
if (!Object.keys(this.inNeighborsIndex[edge.target][edge.source]).length)
|
|
delete this.inNeighborsIndex[edge.target][edge.source];
|
|
|
|
delete this.outNeighborsIndex[edge.source][edge.target][edge.id];
|
|
if (!Object.keys(this.outNeighborsIndex[edge.source][edge.target]).length)
|
|
delete this.outNeighborsIndex[edge.source][edge.target];
|
|
|
|
delete this.allNeighborsIndex[edge.source][edge.target][edge.id];
|
|
if (!Object.keys(this.allNeighborsIndex[edge.source][edge.target]).length)
|
|
delete this.allNeighborsIndex[edge.source][edge.target];
|
|
|
|
if (edge.target !== edge.source) {
|
|
delete this.allNeighborsIndex[edge.target][edge.source][edge.id];
|
|
if (!Object.keys(this.allNeighborsIndex[edge.target][edge.source]).length)
|
|
delete this.allNeighborsIndex[edge.target][edge.source];
|
|
}
|
|
|
|
this.inNeighborsCount[edge.target]--;
|
|
this.outNeighborsCount[edge.source]--;
|
|
this.allNeighborsCount[edge.source]--;
|
|
this.allNeighborsCount[edge.target]--;
|
|
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* This method destroys the current instance. It basically empties each index
|
|
* and methods attached to the graph.
|
|
*/
|
|
graph.addMethod('kill', function() {
|
|
// Delete arrays:
|
|
this.nodesArray.length = 0;
|
|
this.edgesArray.length = 0;
|
|
delete this.nodesArray;
|
|
delete this.edgesArray;
|
|
|
|
// Delete indexes:
|
|
delete this.nodesIndex;
|
|
delete this.edgesIndex;
|
|
delete this.inNeighborsIndex;
|
|
delete this.outNeighborsIndex;
|
|
delete this.allNeighborsIndex;
|
|
delete this.inNeighborsCount;
|
|
delete this.outNeighborsCount;
|
|
delete this.allNeighborsCount;
|
|
});
|
|
|
|
/**
|
|
* This method empties the nodes and edges arrays, as well as the different
|
|
* indexes.
|
|
*
|
|
* @return {object} The graph instance.
|
|
*/
|
|
graph.addMethod('clear', function() {
|
|
this.nodesArray.length = 0;
|
|
this.edgesArray.length = 0;
|
|
|
|
// Due to GC issues, I prefer not to create new object. These objects are
|
|
// only available from the methods and attached functions, but still, it is
|
|
// better to prevent ghost references to unrelevant data...
|
|
__emptyObject(this.nodesIndex);
|
|
__emptyObject(this.edgesIndex);
|
|
__emptyObject(this.nodesIndex);
|
|
__emptyObject(this.inNeighborsIndex);
|
|
__emptyObject(this.outNeighborsIndex);
|
|
__emptyObject(this.allNeighborsIndex);
|
|
__emptyObject(this.inNeighborsCount);
|
|
__emptyObject(this.outNeighborsCount);
|
|
__emptyObject(this.allNeighborsCount);
|
|
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* This method reads an object and adds the nodes and edges, through the
|
|
* proper methods "addNode" and "addEdge".
|
|
*
|
|
* Here is an example:
|
|
*
|
|
* > var myGraph = new graph();
|
|
* > myGraph.read({
|
|
* > nodes: [
|
|
* > { id: 'n0' },
|
|
* > { id: 'n1' }
|
|
* > ],
|
|
* > edges: [
|
|
* > {
|
|
* > id: 'e0',
|
|
* > source: 'n0',
|
|
* > target: 'n1'
|
|
* > }
|
|
* > ]
|
|
* > });
|
|
* >
|
|
* > console.log(
|
|
* > myGraph.nodes().length,
|
|
* > myGraph.edges().length
|
|
* > ); // outputs 2 1
|
|
*
|
|
* @param {object} g The graph object.
|
|
* @return {object} The graph instance.
|
|
*/
|
|
graph.addMethod('read', function(g) {
|
|
var i,
|
|
a,
|
|
l;
|
|
|
|
a = g.nodes || [];
|
|
for (i = 0, l = a.length; i < l; i++)
|
|
this.addNode(a[i]);
|
|
|
|
a = g.edges || [];
|
|
for (i = 0, l = a.length; i < l; i++)
|
|
this.addEdge(a[i]);
|
|
|
|
return this;
|
|
});
|
|
|
|
/**
|
|
* This methods returns one or several nodes, depending on how it is called.
|
|
*
|
|
* To get the array of nodes, call "nodes" without argument. To get a
|
|
* specific node, call it with the id of the node. The get multiple node,
|
|
* call it with an array of ids, and it will return the array of nodes, in
|
|
* the same order.
|
|
*
|
|
* @param {?(string|array)} v Eventually one id, an array of ids.
|
|
* @return {object|array} The related node or array of nodes.
|
|
*/
|
|
graph.addMethod('nodes', function(v) {
|
|
// Clone the array of nodes and return it:
|
|
if (!arguments.length)
|
|
return this.nodesArray.slice(0);
|
|
|
|
// Return the related node:
|
|
if (arguments.length === 1 &&
|
|
(typeof v === 'string' || typeof v === 'number'))
|
|
return this.nodesIndex[v];
|
|
|
|
// Return an array of the related node:
|
|
if (
|
|
arguments.length === 1 &&
|
|
Object.prototype.toString.call(v) === '[object Array]'
|
|
) {
|
|
var i,
|
|
l,
|
|
a = [];
|
|
|
|
for (i = 0, l = v.length; i < l; i++)
|
|
if (typeof v[i] === 'string' || typeof v[i] === 'number')
|
|
a.push(this.nodesIndex[v[i]]);
|
|
else
|
|
throw 'nodes: Wrong arguments.';
|
|
|
|
return a;
|
|
}
|
|
|
|
throw 'nodes: Wrong arguments.';
|
|
});
|
|
|
|
/**
|
|
* This methods returns the degree of one or several nodes, depending on how
|
|
* it is called. It is also possible to get incoming or outcoming degrees
|
|
* instead by specifying 'in' or 'out' as a second argument.
|
|
*
|
|
* @param {string|array} v One id, an array of ids.
|
|
* @param {?string} which Which degree is required. Values are 'in',
|
|
* 'out', and by default the normal degree.
|
|
* @return {number|array} The related degree or array of degrees.
|
|
*/
|
|
graph.addMethod('degree', function(v, which) {
|
|
// Check which degree is required:
|
|
which = {
|
|
'in': this.inNeighborsCount,
|
|
'out': this.outNeighborsCount
|
|
}[which || ''] || this.allNeighborsCount;
|
|
|
|
// Return the related node:
|
|
if (typeof v === 'string' || typeof v === 'number')
|
|
return which[v];
|
|
|
|
// Return an array of the related node:
|
|
if (Object.prototype.toString.call(v) === '[object Array]') {
|
|
var i,
|
|
l,
|
|
a = [];
|
|
|
|
for (i = 0, l = v.length; i < l; i++)
|
|
if (typeof v[i] === 'string' || typeof v[i] === 'number')
|
|
a.push(which[v[i]]);
|
|
else
|
|
throw 'degree: Wrong arguments.';
|
|
|
|
return a;
|
|
}
|
|
|
|
throw 'degree: Wrong arguments.';
|
|
});
|
|
|
|
/**
|
|
* This methods returns one or several edges, depending on how it is called.
|
|
*
|
|
* To get the array of edges, call "edges" without argument. To get a
|
|
* specific edge, call it with the id of the edge. The get multiple edge,
|
|
* call it with an array of ids, and it will return the array of edges, in
|
|
* the same order.
|
|
*
|
|
* @param {?(string|array)} v Eventually one id, an array of ids.
|
|
* @return {object|array} The related edge or array of edges.
|
|
*/
|
|
graph.addMethod('edges', function(v) {
|
|
// Clone the array of edges and return it:
|
|
if (!arguments.length)
|
|
return this.edgesArray.slice(0);
|
|
|
|
// Return the related edge:
|
|
if (arguments.length === 1 &&
|
|
(typeof v === 'string' || typeof v === 'number'))
|
|
return this.edgesIndex[v];
|
|
|
|
// Return an array of the related edge:
|
|
if (
|
|
arguments.length === 1 &&
|
|
Object.prototype.toString.call(v) === '[object Array]'
|
|
) {
|
|
var i,
|
|
l,
|
|
a = [];
|
|
|
|
for (i = 0, l = v.length; i < l; i++)
|
|
if (typeof v[i] === 'string' || typeof v[i] === 'number')
|
|
a.push(this.edgesIndex[v[i]]);
|
|
else
|
|
throw 'edges: Wrong arguments.';
|
|
|
|
return a;
|
|
}
|
|
|
|
throw 'edges: Wrong arguments.';
|
|
});
|
|
|
|
|
|
/**
|
|
* EXPORT:
|
|
* *******
|
|
*/
|
|
if (typeof sigma !== 'undefined') {
|
|
sigma.classes = sigma.classes || Object.create(null);
|
|
sigma.classes.graph = graph;
|
|
} else if (typeof exports !== 'undefined') {
|
|
if (typeof module !== 'undefined' && module.exports)
|
|
exports = module.exports = graph;
|
|
exports.graph = graph;
|
|
} else
|
|
this.graph = graph;
|
|
}).call(this);
|