|
|
- ;(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);
|