|
import * as util from '../../util'; |
|
import * as math from '../../math'; |
|
|
|
let defaults = { |
|
fit: true, |
|
padding: 30, |
|
startAngle: 3 / 2 * Math.PI, |
|
sweep: undefined, |
|
clockwise: true, |
|
equidistant: false, |
|
minNodeSpacing: 10, |
|
boundingBox: undefined, |
|
avoidOverlap: true, |
|
nodeDimensionsIncludeLabels: false, |
|
height: undefined, |
|
width: undefined, |
|
spacingFactor: undefined, |
|
concentric: function( node ){ |
|
return node.degree(); |
|
}, |
|
levelWidth: function( nodes ){ |
|
return nodes.maxDegree() / 4; |
|
}, |
|
animate: false, |
|
animationDuration: 500, |
|
animationEasing: undefined, |
|
animateFilter: function ( node, i ){ return true; }, |
|
ready: undefined, |
|
stop: undefined, |
|
transform: function (node, position ){ return position; } |
|
}; |
|
|
|
function ConcentricLayout( options ){ |
|
this.options = util.extend( {}, defaults, options ); |
|
} |
|
|
|
ConcentricLayout.prototype.run = function(){ |
|
let params = this.options; |
|
let options = params; |
|
|
|
let clockwise = options.counterclockwise !== undefined ? !options.counterclockwise : options.clockwise; |
|
|
|
let cy = params.cy; |
|
|
|
let eles = options.eles; |
|
let nodes = eles.nodes().not( ':parent' ); |
|
|
|
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 nodeValues = []; |
|
let maxNodeSize = 0; |
|
|
|
for( let i = 0; i < nodes.length; i++ ){ |
|
let node = nodes[ i ]; |
|
let value; |
|
|
|
|
|
value = options.concentric( node ); |
|
nodeValues.push( { |
|
value: value, |
|
node: node |
|
} ); |
|
|
|
|
|
node._private.scratch.concentric = value; |
|
} |
|
|
|
|
|
nodes.updateStyle(); |
|
|
|
|
|
for( let i = 0; i < nodes.length; i++ ){ |
|
let node = nodes[ i ]; |
|
let nbb = node.layoutDimensions( options ); |
|
|
|
maxNodeSize = Math.max( maxNodeSize, nbb.w, nbb.h ); |
|
} |
|
|
|
|
|
nodeValues.sort( function( a, b ){ |
|
return b.value - a.value; |
|
} ); |
|
|
|
let levelWidth = options.levelWidth( nodes ); |
|
|
|
|
|
let levels = [ [] ]; |
|
let currentLevel = levels[0]; |
|
for( let i = 0; i < nodeValues.length; i++ ){ |
|
let val = nodeValues[ i ]; |
|
|
|
if( currentLevel.length > 0 ){ |
|
let diff = Math.abs( currentLevel[0].value - val.value ); |
|
|
|
if( diff >= levelWidth ){ |
|
currentLevel = []; |
|
levels.push( currentLevel ); |
|
} |
|
} |
|
|
|
currentLevel.push( val ); |
|
} |
|
|
|
|
|
|
|
let minDist = maxNodeSize + options.minNodeSpacing; |
|
|
|
if( !options.avoidOverlap ){ |
|
let firstLvlHasMulti = levels.length > 0 && levels[0].length > 1; |
|
let maxR = ( Math.min( bb.w, bb.h ) / 2 - minDist ); |
|
let rStep = maxR / ( levels.length + firstLvlHasMulti ? 1 : 0 ); |
|
|
|
minDist = Math.min( minDist, rStep ); |
|
} |
|
|
|
|
|
let r = 0; |
|
for( let i = 0; i < levels.length; i++ ){ |
|
let level = levels[ i ]; |
|
let sweep = options.sweep === undefined ? 2 * Math.PI - 2 * Math.PI / level.length : options.sweep; |
|
let dTheta = level.dTheta = sweep / ( Math.max( 1, level.length - 1 ) ); |
|
|
|
|
|
if( level.length > 1 && options.avoidOverlap ){ |
|
let dcos = Math.cos( dTheta ) - Math.cos( 0 ); |
|
let dsin = Math.sin( dTheta ) - Math.sin( 0 ); |
|
let rMin = Math.sqrt( minDist * minDist / ( dcos * dcos + dsin * dsin ) ); |
|
|
|
r = Math.max( rMin, r ); |
|
} |
|
|
|
level.r = r; |
|
|
|
r += minDist; |
|
} |
|
|
|
if( options.equidistant ){ |
|
let rDeltaMax = 0; |
|
let r = 0; |
|
|
|
for( let i = 0; i < levels.length; i++ ){ |
|
let level = levels[ i ]; |
|
let rDelta = level.r - r; |
|
|
|
rDeltaMax = Math.max( rDeltaMax, rDelta ); |
|
} |
|
|
|
r = 0; |
|
for( let i = 0; i < levels.length; i++ ){ |
|
let level = levels[ i ]; |
|
|
|
if( i === 0 ){ |
|
r = level.r; |
|
} |
|
|
|
level.r = r; |
|
|
|
r += rDeltaMax; |
|
} |
|
} |
|
|
|
|
|
let pos = {}; |
|
for( let i = 0; i < levels.length; i++ ){ |
|
let level = levels[ i ]; |
|
let dTheta = level.dTheta; |
|
let r = level.r; |
|
|
|
for( let j = 0; j < level.length; j++ ){ |
|
let val = level[ j ]; |
|
let theta = options.startAngle + (clockwise ? 1 : -1) * dTheta * j; |
|
|
|
let p = { |
|
x: center.x + r * Math.cos( theta ), |
|
y: center.y + r * Math.sin( theta ) |
|
}; |
|
|
|
pos[ val.node.id() ] = p; |
|
} |
|
} |
|
|
|
|
|
eles.nodes().layoutPositions( this, options, function( ele ){ |
|
let id = ele.id(); |
|
|
|
return pos[ id ]; |
|
} ); |
|
|
|
return this; |
|
}; |
|
|
|
export default ConcentricLayout; |
|
|