|
|
- ;(function(undefined) {
- 'use strict';
-
- if (typeof sigma === 'undefined')
- throw new Error('sigma is not declared');
-
- // Initialize package:
- sigma.utils.pkg('sigma.layout.noverlap');
-
- /**
- * Noverlap Layout
- * ===============================
- *
- * Author: @apitts / Andrew Pitts
- * Algorithm: @jacomyma / Mathieu Jacomy (originally contributed to Gephi and ported to sigma.js under the MIT license by @andpitts with permission)
- * Acknowledgement: @sheyman / Sébastien Heymann (some inspiration has been taken from other MIT licensed layout algorithms authored by @sheyman)
- * Version: 0.1
- */
-
- var settings = {
- speed: 3,
- scaleNodes: 1.2,
- nodeMargin: 5.0,
- gridSize: 20,
- permittedExpansion: 1.1,
- rendererIndex: 0,
- maxIterations: 500
- };
-
- var _instance = {};
-
- /**
- * Event emitter Object
- * ------------------
- */
- var _eventEmitter = {};
-
- /**
- * Noverlap Object
- * ------------------
- */
- function Noverlap() {
- var self = this;
-
- this.init = function (sigInst, options) {
- options = options || {};
-
- // Properties
- this.sigInst = sigInst;
- this.config = sigma.utils.extend(options, settings);
- this.easing = options.easing;
- this.duration = options.duration;
-
- if (options.nodes) {
- this.nodes = options.nodes;
- delete options.nodes;
- }
-
- if (!sigma.plugins || typeof sigma.plugins.animate === 'undefined') {
- throw new Error('sigma.plugins.animate is not declared');
- }
-
- // State
- this.running = false;
- };
-
- /**
- * Single layout iteration.
- */
- this.atomicGo = function () {
- if (!this.running || this.iterCount < 1) return false;
-
- var nodes = this.nodes || this.sigInst.graph.nodes(),
- nodesCount = nodes.length,
- i,
- n,
- n1,
- n2,
- xmin = Infinity,
- xmax = -Infinity,
- ymin = Infinity,
- ymax = -Infinity,
- xwidth,
- yheight,
- xcenter,
- ycenter,
- grid,
- row,
- col,
- minXBox,
- maxXBox,
- minYBox,
- maxYBox,
- adjacentNodes,
- subRow,
- subCol,
- nxmin,
- nxmax,
- nymin,
- nymax;
-
- this.iterCount--;
- this.running = false;
-
- for (i=0; i < nodesCount; i++) {
- n = nodes[i];
- n.dn.dx = 0;
- n.dn.dy = 0;
-
- //Find the min and max for both x and y across all nodes
- xmin = Math.min(xmin, n.dn_x - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
- xmax = Math.max(xmax, n.dn_x + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
- ymin = Math.min(ymin, n.dn_y - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
- ymax = Math.max(ymax, n.dn_y + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin) );
-
- }
-
- xwidth = xmax - xmin;
- yheight = ymax - ymin;
- xcenter = (xmin + xmax) / 2;
- ycenter = (ymin + ymax) / 2;
- xmin = xcenter - self.config.permittedExpansion*xwidth / 2;
- xmax = xcenter + self.config.permittedExpansion*xwidth / 2;
- ymin = ycenter - self.config.permittedExpansion*yheight / 2;
- ymax = ycenter + self.config.permittedExpansion*yheight / 2;
-
- grid = {}; //An object of objects where grid[row][col] is an array of node ids representing nodes that fall in that grid. Nodes can fall in more than one grid
-
- for(row = 0; row < self.config.gridSize; row++) {
- grid[row] = {};
- for(col = 0; col < self.config.gridSize; col++) {
- grid[row][col] = [];
- }
- }
-
- //Place nodes in grid
- for (i=0; i < nodesCount; i++) {
- n = nodes[i];
-
- nxmin = n.dn_x - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
- nxmax = n.dn_x + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
- nymin = n.dn_y - (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
- nymax = n.dn_y + (n.dn_size*self.config.scaleNodes + self.config.nodeMargin);
-
- minXBox = Math.floor(self.config.gridSize* (nxmin - xmin) / (xmax - xmin) );
- maxXBox = Math.floor(self.config.gridSize* (nxmax - xmin) / (xmax - xmin) );
- minYBox = Math.floor(self.config.gridSize* (nymin - ymin) / (ymax - ymin) );
- maxYBox = Math.floor(self.config.gridSize* (nymax - ymin) / (ymax - ymin) );
- for(col = minXBox; col <= maxXBox; col++) {
- for(row = minYBox; row <= maxYBox; row++) {
- grid[row][col].push(n.id);
- }
- }
- }
-
-
- adjacentNodes = {}; //An object that stores the node ids of adjacent nodes (either in same grid box or adjacent grid box) for all nodes
-
- for(row = 0; row < self.config.gridSize; row++) {
- for(col = 0; col < self.config.gridSize; col++) {
- grid[row][col].forEach(function(nodeId) {
- if(!adjacentNodes[nodeId]) {
- adjacentNodes[nodeId] = [];
- }
- for(subRow = Math.max(0, row - 1); subRow <= Math.min(row + 1, self.config.gridSize - 1); subRow++) {
- for(subCol = Math.max(0, col - 1); subCol <= Math.min(col + 1, self.config.gridSize - 1); subCol++) {
- grid[subRow][subCol].forEach(function(subNodeId) {
- if(subNodeId !== nodeId && adjacentNodes[nodeId].indexOf(subNodeId) === -1) {
- adjacentNodes[nodeId].push(subNodeId);
- }
- });
- }
- }
- });
- }
- }
-
- //If two nodes overlap then repulse them
- for (i=0; i < nodesCount; i++) {
- n1 = nodes[i];
- adjacentNodes[n1.id].forEach(function(nodeId) {
- var n2 = self.sigInst.graph.nodes(nodeId);
- var xDist = n2.dn_x - n1.dn_x;
- var yDist = n2.dn_y - n1.dn_y;
- var dist = Math.sqrt(xDist*xDist + yDist*yDist);
- var collision = (dist < ((n1.dn_size*self.config.scaleNodes + self.config.nodeMargin) + (n2.dn_size*self.config.scaleNodes + self.config.nodeMargin)));
- if(collision) {
- self.running = true;
- if(dist > 0) {
- n2.dn.dx += xDist / dist * (1 + n1.dn_size);
- n2.dn.dy += yDist / dist * (1 + n1.dn_size);
- } else {
- n2.dn.dx += xwidth * 0.01 * (0.5 - Math.random());
- n2.dn.dy += yheight * 0.01 * (0.5 - Math.random());
- }
- }
- });
- }
-
- for (i=0; i < nodesCount; i++) {
- n = nodes[i];
- if(!n.fixed) {
- n.dn_x = n.dn_x + n.dn.dx * 0.1 * self.config.speed;
- n.dn_y = n.dn_y + n.dn.dy * 0.1 * self.config.speed;
- }
- }
-
- if(this.running && this.iterCount < 1) {
- this.running = false;
- }
-
- return this.running;
- };
-
- this.go = function () {
- this.iterCount = this.config.maxIterations;
-
- while (this.running) {
- this.atomicGo();
- };
-
- this.stop();
- };
-
- this.start = function() {
- if (this.running) return;
-
- var nodes = this.sigInst.graph.nodes();
-
- var prefix = this.sigInst.renderers[self.config.rendererIndex].options.prefix;
-
- this.running = true;
-
- // Init nodes
- for (var i = 0; i < nodes.length; i++) {
- nodes[i].dn_x = nodes[i][prefix + 'x'];
- nodes[i].dn_y = nodes[i][prefix + 'y'];
- nodes[i].dn_size = nodes[i][prefix + 'size'];
- nodes[i].dn = {
- dx: 0,
- dy: 0
- };
- }
- _eventEmitter[self.sigInst.id].dispatchEvent('start');
- this.go();
- };
-
- this.stop = function() {
- var nodes = this.sigInst.graph.nodes();
-
- this.running = false;
-
- if (this.easing) {
- _eventEmitter[self.sigInst.id].dispatchEvent('interpolate');
- sigma.plugins.animate(
- self.sigInst,
- {
- x: 'dn_x',
- y: 'dn_y'
- },
- {
- easing: self.easing,
- onComplete: function() {
- self.sigInst.refresh();
- for (var i = 0; i < nodes.length; i++) {
- nodes[i].dn = null;
- nodes[i].dn_x = null;
- nodes[i].dn_y = null;
- }
- _eventEmitter[self.sigInst.id].dispatchEvent('stop');
- },
- duration: self.duration
- }
- );
- }
- else {
- // Apply changes
- for (var i = 0; i < nodes.length; i++) {
- nodes[i].x = nodes[i].dn_x;
- nodes[i].y = nodes[i].dn_y;
- }
-
- this.sigInst.refresh();
-
- for (var i = 0; i < nodes.length; i++) {
- nodes[i].dn = null;
- nodes[i].dn_x = null;
- nodes[i].dn_y = null;
- }
- _eventEmitter[self.sigInst.id].dispatchEvent('stop');
- }
- };
-
- this.kill = function() {
- this.sigInst = null;
- this.config = null;
- this.easing = null;
- };
- };
-
- /**
- * Interface
- * ----------
- */
-
- /**
- * Configure the layout algorithm.
-
- * Recognized options:
- * **********************
- * Here is the exhaustive list of every accepted parameter in the settings
- * object:
- *
- * {?number} speed A larger value increases the convergence speed at the cost of precision
- * {?number} scaleNodes The ratio to scale nodes by - a larger ratio will lead to more space around larger nodes
- * {?number} nodeMargin A fixed margin to apply around nodes regardless of size
- * {?number} maxIterations The maximum number of iterations to perform before the layout completes.
- * {?integer} gridSize The number of rows and columns to use when partioning nodes into a grid for efficient computation
- * {?number} permittedExpansion A permitted expansion factor to the overall size of the network applied at each iteration
- * {?integer} rendererIndex The index of the renderer to use for node co-ordinates. Defaults to zero.
- * {?(function|string)} easing Either the name of an easing in the sigma.utils.easings package or a function. If not specified, the
- * quadraticInOut easing from this package will be used instead.
- * {?number} duration The duration of the animation. If not specified, the "animationsTime" setting value of the sigma instance will be used instead.
- *
- *
- * @param {object} config The optional configuration object.
- *
- * @return {sigma.classes.dispatcher} Returns an event emitter.
- */
- sigma.prototype.configNoverlap = function(config) {
-
- var sigInst = this;
-
- if (!config) throw new Error('Missing argument: "config"');
-
- // Create instance if undefined
- if (!_instance[sigInst.id]) {
- _instance[sigInst.id] = new Noverlap();
-
- _eventEmitter[sigInst.id] = {};
- sigma.classes.dispatcher.extend(_eventEmitter[sigInst.id]);
-
- // Binding on kill to clear the references
- sigInst.bind('kill', function() {
- _instance[sigInst.id].kill();
- _instance[sigInst.id] = null;
- _eventEmitter[sigInst.id] = null;
- });
- }
-
- _instance[sigInst.id].init(sigInst, config);
-
- return _eventEmitter[sigInst.id];
- };
-
- /**
- * Start the layout algorithm. It will use the existing configuration if no
- * new configuration is passed.
-
- * Recognized options:
- * **********************
- * Here is the exhaustive list of every accepted parameter in the settings
- * object
- *
- * {?number} speed A larger value increases the convergence speed at the cost of precision
- * {?number} scaleNodes The ratio to scale nodes by - a larger ratio will lead to more space around larger nodes
- * {?number} nodeMargin A fixed margin to apply around nodes regardless of size
- * {?number} maxIterations The maximum number of iterations to perform before the layout completes.
- * {?integer} gridSize The number of rows and columns to use when partioning nodes into a grid for efficient computation
- * {?number} permittedExpansion A permitted expansion factor to the overall size of the network applied at each iteration
- * {?integer} rendererIndex The index of the renderer to use for node co-ordinates. Defaults to zero.
- * {?(function|string)} easing Either the name of an easing in the sigma.utils.easings package or a function. If not specified, the
- * quadraticInOut easing from this package will be used instead.
- * {?number} duration The duration of the animation. If not specified, the "animationsTime" setting value of the sigma instance will be used instead.
- *
- *
- *
- * @param {object} config The optional configuration object.
- *
- * @return {sigma.classes.dispatcher} Returns an event emitter.
- */
-
- sigma.prototype.startNoverlap = function(config) {
-
- var sigInst = this;
-
- if (config) {
- this.configNoverlap(sigInst, config);
- }
-
- _instance[sigInst.id].start();
-
- return _eventEmitter[sigInst.id];
- };
-
- /**
- * Returns true if the layout has started and is not completed.
- *
- * @return {boolean}
- */
- sigma.prototype.isNoverlapRunning = function() {
-
- var sigInst = this;
-
- return !!_instance[sigInst.id] && _instance[sigInst.id].running;
- };
-
- }).call(this);
|