File size: 6,377 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
import * as util from '../../util';
import * as math from '../../math';
let defaults = {
fit: true, // whether to fit the viewport to the graph
padding: 30, // the padding on fit
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)
equidistant: false, // whether levels have an equal radial distance betwen them, may cause bounding box overflow
minNodeSpacing: 10, // min spacing between outside of nodes (used for radius adjustment)
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
avoidOverlap: true, // prevents node overlap, may overflow boundingBox if not enough space
nodeDimensionsIncludeLabels: false, // Excludes the label when calculating node bounding boxes for the layout algorithm
height: undefined, // height of layout area (overrides container height)
width: undefined, // width of layout area (overrides container width)
spacingFactor: undefined, // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up
concentric: function( node ){ // returns numeric value for each node, placing higher nodes in levels towards the centre
return node.degree();
},
levelWidth: function( nodes ){ // the variation of concentric values in each level
return nodes.maxDegree() / 4;
},
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 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 = []; // { node, value }
let maxNodeSize = 0;
for( let i = 0; i < nodes.length; i++ ){
let node = nodes[ i ];
let value;
// calculate the node value
value = options.concentric( node );
nodeValues.push( {
value: value,
node: node
} );
// for style mapping
node._private.scratch.concentric = value;
}
// in case we used the `concentric` in style
nodes.updateStyle();
// calculate max size now based on potentially updated mappers
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 );
}
// sort node values in descreasing order
nodeValues.sort( function( a, b ){
return b.value - a.value;
} );
let levelWidth = options.levelWidth( nodes );
// put the values into levels
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 );
}
// create positions from levels
let minDist = maxNodeSize + options.minNodeSpacing; // min dist between nodes
if( !options.avoidOverlap ){ // then strictly constrain to bb
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 );
}
// find the metrics for each level
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 ) );
// calculate the radius
if( level.length > 1 && options.avoidOverlap ){ // but only if more than one node (can't overlap)
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 ) ); // s.t. no nodes overlapping
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;
}
}
// calculate the node positions
let pos = {}; // id => position
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;
}
}
// position the nodes
eles.nodes().layoutPositions( this, options, function( ele ){
let id = ele.id();
return pos[ id ];
} );
return this; // chaining
};
export default ConcentricLayout;
|