Graph database Analysis of the Steam Network
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

504 lines
14 KiB

;(function(undefined) {
'use strict';
if (typeof sigma === 'undefined')
throw 'sigma is not declared';
// Initialize package:
sigma.utils.pkg('sigma.plugins');
// Add custom graph methods:
/**
* This methods returns an array of nodes that are adjacent to a node.
*
* @param {string} id The node id.
* @return {array} The array of adjacent nodes.
*/
if (!sigma.classes.graph.hasMethod('adjacentNodes'))
sigma.classes.graph.addMethod('adjacentNodes', function(id) {
if (typeof id !== 'string')
throw 'adjacentNodes: the node id must be a string.';
var target,
nodes = [];
for(target in this.allNeighborsIndex[id]) {
nodes.push(this.nodesIndex[target]);
}
return nodes;
});
/**
* This methods returns an array of edges that are adjacent to a node.
*
* @param {string} id The node id.
* @return {array} The array of adjacent edges.
*/
if (!sigma.classes.graph.hasMethod('adjacentEdges'))
sigma.classes.graph.addMethod('adjacentEdges', function(id) {
if (typeof id !== 'string')
throw 'adjacentEdges: the node id must be a string.';
var a = this.allNeighborsIndex[id],
eid,
target,
edges = [];
for(target in a) {
for(eid in a[target]) {
edges.push(a[target][eid]);
}
}
return edges;
});
/**
* Sigma Filter
* =============================
*
* @author Sébastien Heymann <seb@linkurio.us> (Linkurious)
* @version 0.1
*/
var _g = undefined,
_s = undefined,
_chain = [], // chain of wrapped filters
_keysIndex = Object.create(null),
Processors = {}; // available predicate processors
/**
* Library of processors
* ------------------
*/
/**
*
* @param {function} fn The predicate.
*/
Processors.nodes = function nodes(fn) {
var n = _g.nodes(),
ln = n.length,
e = _g.edges(),
le = e.length;
// hide node, or keep former value
while(ln--)
n[ln].hidden = !fn.call(_g, n[ln]) || n[ln].hidden;
while(le--)
if (_g.nodes(e[le].source).hidden || _g.nodes(e[le].target).hidden)
e[le].hidden = true;
};
/**
*
* @param {function} fn The predicate.
*/
Processors.edges = function edges(fn) {
var e = _g.edges(),
le = e.length;
// hide edge, or keep former value
while(le--)
e[le].hidden = !fn.call(_g, e[le]) || e[le].hidden;
};
/**
*
* @param {string} id The center node.
*/
Processors.neighbors = function neighbors(id) {
var n = _g.nodes(),
ln = n.length,
e = _g.edges(),
le = e.length,
neighbors = _g.adjacentNodes(id),
nn = neighbors.length,
no = {};
while(nn--)
no[neighbors[nn].id] = true;
while(ln--)
if (n[ln].id !== id && !(n[ln].id in no))
n[ln].hidden = true;
while(le--)
if (_g.nodes(e[le].source).hidden || _g.nodes(e[le].target).hidden)
e[le].hidden = true;
};
/**
* This function adds a filter to the chain of filters.
*
* @param {function} fn The filter (i.e. predicate processor).
* @param {function} p The predicate.
* @param {?string} key The key to identify the filter.
*/
function register(fn, p, key) {
if (key != undefined && typeof key !== 'string')
throw 'The filter key "'+ key.toString() +'" must be a string.';
if (key != undefined && !key.length)
throw 'The filter key must be a non-empty string.';
if (typeof fn !== 'function')
throw 'The predicate of key "'+ key +'" must be a function.';
if ('undo' === key)
throw '"undo" is a reserved key.';
if (_keysIndex[key])
throw 'The filter "' + key + '" already exists.';
if (key)
_keysIndex[key] = true;
_chain.push({
'key': key,
'processor': fn,
'predicate': p
});
};
/**
* This function removes a set of filters from the chain.
*
* @param {object} o The filter keys.
*/
function unregister (o) {
_chain = _chain.filter(function(a) {
return !(a.key in o);
});
for(var key in o)
delete _keysIndex[key];
};
/**
* Filter Object
* ------------------
* @param {sigma} s The related sigma instance.
*/
function Filter(s) {
_s = s;
_g = s.graph;
};
/**
* This method is used to filter the nodes. The method must be called with
* the predicate, which is a function that takes a node as argument and
* returns a boolean. It may take an identifier as argument to undo the
* filter later. The method wraps the predicate into an anonymous function
* that looks through each node in the graph. When executed, the anonymous
* function hides the nodes that fail a truth test (predicate). The method
* adds the anonymous function to the chain of filters. The filter is not
* executed until the apply() method is called.
*
* > var filter = new sigma.plugins.filter(s);
* > filter.nodesBy(function(n) {
* > return this.degree(n.id) > 0;
* > }, 'degreeNotNull');
*
* @param {function} fn The filter predicate.
* @param {?string} key The key to identify the filter.
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.nodesBy = function(fn, key) {
// Wrap the predicate to be applied on the graph and add it to the chain.
register(Processors.nodes, fn, key);
return this;
};
/**
* This method is used to filter the edges. The method must be called with
* the predicate, which is a function that takes a node as argument and
* returns a boolean. It may take an identifier as argument to undo the
* filter later. The method wraps the predicate into an anonymous function
* that looks through each edge in the graph. When executed, the anonymous
* function hides the edges that fail a truth test (predicate). The method
* adds the anonymous function to the chain of filters. The filter is not
* executed until the apply() method is called.
*
* > var filter = new sigma.plugins.filter(s);
* > filter.edgesBy(function(e) {
* > return e.size > 1;
* > }, 'edgeSize');
*
* @param {function} fn The filter predicate.
* @param {?string} key The key to identify the filter.
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.edgesBy = function(fn, key) {
// Wrap the predicate to be applied on the graph and add it to the chain.
register(Processors.edges, fn, key);
return this;
};
/**
* This method is used to filter the nodes which are not direct connections
* of a given node. The method must be called with the node identifier. It
* may take an identifier as argument to undo the filter later. The filter
* is not executed until the apply() method is called.
*
* > var filter = new sigma.plugins.filter(s);
* > filter.neighborsOf('n0');
*
* @param {string} id The node id.
* @param {?string} key The key to identify the filter.
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.neighborsOf = function(id, key) {
if (typeof id !== 'string')
throw 'The node id "'+ id.toString() +'" must be a string.';
if (!id.length)
throw 'The node id must be a non-empty string.';
// Wrap the predicate to be applied on the graph and add it to the chain.
register(Processors.neighbors, id, key);
return this;
};
/**
* This method is used to execute the chain of filters and to refresh the
* display.
*
* > var filter = new sigma.plugins.filter(s);
* > filter
* > .nodesBy(function(n) {
* > return this.degree(n.id) > 0;
* > }, 'degreeNotNull')
* > .apply();
*
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.apply = function() {
for (var i = 0, len = _chain.length; i < len; ++i) {
_chain[i].processor(_chain[i].predicate);
};
if (_chain[0] && 'undo' === _chain[0].key) {
_chain.shift();
}
_s.refresh();
return this;
};
/**
* This method undoes one or several filters, depending on how it is called.
*
* To undo all filters, call "undo" without argument. To undo a specific
* filter, call it with the key of the filter. To undo multiple filters, call
* it with an array of keys or multiple arguments, and it will undo each
* filter, in the same order. The undo is not executed until the apply()
* method is called. For instance:
*
* > var filter = new sigma.plugins.filter(s);
* > filter
* > .nodesBy(function(n) {
* > return this.degree(n.id) > 0;
* > }, 'degreeNotNull');
* > .edgesBy(function(e) {
* > return e.size > 1;
* > }, 'edgeSize')
* > .undo();
*
* Other examples:
* > filter.undo();
* > filter.undo('myfilter');
* > filter.undo(['myfilter1', 'myfilter2']);
* > filter.undo('myfilter1', 'myfilter2');
*
* @param {?(string|array|*string))} v Eventually one key, an array of keys.
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.undo = function(v) {
var q = Object.create(null),
la = arguments.length;
// find removable filters
if (la === 1) {
if (Object.prototype.toString.call(v) === '[object Array]')
for (var i = 0, len = v.length; i < len; i++)
q[v[i]] = true;
else // 1 filter key
q[v] = true;
} else if (la > 1) {
for (var i = 0; i < la; i++)
q[arguments[i]] = true;
}
else
this.clear();
unregister(q);
function processor() {
var n = _g.nodes(),
ln = n.length,
e = _g.edges(),
le = e.length;
while(ln--)
n[ln].hidden = false;
while(le--)
e[le].hidden = false;
};
_chain.unshift({
'key': 'undo',
'processor': processor
});
return this;
};
// fast deep copy function
function deepCopy(o) {
var copy = Object.create(null);
for (var i in o) {
if (typeof o[i] === "object" && o[i] !== null) {
copy[i] = deepCopy(o[i]);
}
else if (typeof o[i] === "function" && o[i] !== null) {
// clone function:
eval(" copy[i] = " + o[i].toString());
//copy[i] = o[i].bind(_g);
}
else
copy[i] = o[i];
}
return copy;
};
function cloneChain(chain) {
// Clone the array of filters:
var copy = chain.slice(0);
for (var i = 0, len = copy.length; i < len; i++) {
copy[i] = deepCopy(copy[i]);
if (typeof copy[i].processor === "function")
copy[i].processor = 'filter.processors.' + copy[i].processor.name;
};
return copy;
}
/**
* This method is used to empty the chain of filters.
* Prefer the undo() method to reset filters.
*
* > var filter = new sigma.plugins.filter(s);
* > filter.clear();
*
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.clear = function() {
_chain.length = 0; // clear the array
_keysIndex = Object.create(null);
return this;
};
/**
* This method clones the filter chain and return the copy.
*
* > var filter = new sigma.plugins.filter(s);
* > var chain = filter.export();
*
* @return {object} The cloned chain of filters.
*/
Filter.prototype.export = function() {
var c = cloneChain(_chain);
return c;
};
/**
* This method sets the chain of filters with the specified chain.
*
* > var filter = new sigma.plugins.filter(s);
* > var chain = [
* > {
* > key: 'my-filter',
* > predicate: function(n) {...},
* > processor: 'filter.processors.nodes'
* > }, ...
* > ];
* > filter.import(chain);
*
* @param {array} chain The chain of filters.
* @return {sigma.plugins.filter} Returns the instance.
*/
Filter.prototype.import = function(chain) {
if (chain === undefined)
throw 'Wrong arguments.';
if (Object.prototype.toString.call(chain) !== '[object Array]')
throw 'The chain" must be an array.';
var copy = cloneChain(chain);
for (var i = 0, len = copy.length; i < len; i++) {
if (copy[i].predicate === undefined || copy[i].processor === undefined)
throw 'Wrong arguments.';
if (copy[i].key != undefined && typeof copy[i].key !== 'string')
throw 'The filter key "'+ copy[i].key.toString() +'" must be a string.';
if (typeof copy[i].predicate !== 'function')
throw 'The predicate of key "'+ copy[i].key +'" must be a function.';
if (typeof copy[i].processor !== 'string')
throw 'The processor of key "'+ copy[i].key +'" must be a string.';
// Replace the processor name by the corresponding function:
switch(copy[i].processor) {
case 'filter.processors.nodes':
copy[i].processor = Processors.nodes;
break;
case 'filter.processors.edges':
copy[i].processor = Processors.edges;
break;
case 'filter.processors.neighbors':
copy[i].processor = Processors.neighbors;
break;
default:
throw 'Unknown processor ' + copy[i].processor;
}
};
_chain = copy;
return this;
};
/**
* Interface
* ------------------
*
* > var filter = new sigma.plugins.filter(s);
*/
var filter = null;
/**
* @param {sigma} s The related sigma instance.
*/
sigma.plugins.filter = function(s) {
// Create filter if undefined
if (!filter) {
filter = new Filter(s);
}
return filter;
};
}).call(this);