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