cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
106 lines (83 loc) • 3.88 kB
JavaScript
import * as util from '../../util';
import * as math from '../../math';
import * as is from '../../is';
let defaults = {
fit: true, // whether to fit the viewport to the graph
padding: 30, // the padding on fit
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
avoidOverlap: true, // prevents node overlap, may overflow boundingBox and radius if not enough space
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
spacingFactor: undefined, // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up
radius: undefined, // the radius of the circle
startAngle: 3 / 2 * Math.PI, // where nodes start in radians
sweep: undefined, // how many radians should be between the first and last node (defaults to full circle)
clockwise: true, // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false)
sort: undefined, // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') }
animate: false, // whether to transition the node positions
animationDuration: 500, // duration of animation in ms if enabled
animationEasing: undefined, // easing of animation if enabled
animateFilter: function ( node, i ){ return true; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts
ready: undefined, // callback on layoutready
stop: undefined, // callback on layoutstop
transform: function (node, position ){ return position; } // transform a given node position. Useful for changing flow direction in discrete layouts
};
function CircleLayout( options ){
this.options = util.extend( {}, defaults, options );
}
CircleLayout.prototype.run = function(){
let params = this.options;
let options = params;
let cy = params.cy;
let eles = options.eles;
let clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise;
let nodes = eles.nodes().not( ':parent' );
if( options.sort ){
nodes = nodes.sort( options.sort );
}
let bb = math.makeBoundingBox( options.boundingBox ? options.boundingBox : {
x1: 0, y1: 0, w: cy.width(), h: cy.height()
} );
let center = {
x: bb.x1 + bb.w / 2,
y: bb.y1 + bb.h / 2
};
let sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / nodes.length : options.sweep;
let dTheta = sweep / ( Math.max( 1, nodes.length - 1 ) );
let r;
let minDistance = 0;
for( let i = 0; i < nodes.length; i++ ){
let n = nodes[ i ];
let nbb = n.layoutDimensions( options );
let w = nbb.w;
let h = nbb.h;
minDistance = Math.max( minDistance, w, h );
}
if( is.number( options.radius ) ){
r = options.radius;
} else if( nodes.length <= 1 ){
r = 0;
} else {
r = Math.min( bb.h, bb.w ) / 2 - minDistance;
}
// calculate the radius
if( nodes.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap)
minDistance *= 1.75; // just to have some nice spacing
let dcos = Math.cos( dTheta ) - Math.cos( 0 );
let dsin = Math.sin( dTheta ) - Math.sin( 0 );
let rMin = Math.sqrt( minDistance * minDistance / ( dcos * dcos + dsin * dsin ) ); // s.t. no nodes overlapping
r = Math.max( rMin, r );
}
let getPos = function( ele, i ){
let theta = options.startAngle + i * dTheta * ( clockwise ? 1 : -1 );
let rx = r * Math.cos( theta );
let ry = r * Math.sin( theta );
let pos = {
x: center.x + rx,
y: center.y + ry
};
return pos;
};
eles.nodes().layoutPositions( this, options, getPos );
return this; // chaining
};
export default CircleLayout;