|
import * as util from '../../util'; |
|
import * as math from '../../math'; |
|
|
|
let defaults = { |
|
fit: true, |
|
padding: 30, |
|
boundingBox: undefined, |
|
avoidOverlap: true, |
|
avoidOverlapPadding: 10, |
|
nodeDimensionsIncludeLabels: false, |
|
spacingFactor: undefined, |
|
condense: false, |
|
rows: undefined, |
|
cols: undefined, |
|
position: function( node ){}, |
|
sort: undefined, |
|
animate: false, |
|
animationDuration: 500, |
|
animationEasing: undefined, |
|
animateFilter: function ( node, i ){ return true; }, |
|
ready: undefined, |
|
stop: undefined, |
|
transform: function (node, position ){ return position; } |
|
}; |
|
|
|
function GridLayout( options ){ |
|
this.options = util.extend( {}, defaults, options ); |
|
} |
|
|
|
GridLayout.prototype.run = function(){ |
|
let params = this.options; |
|
let options = params; |
|
|
|
let cy = params.cy; |
|
let eles = options.eles; |
|
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() |
|
} ); |
|
|
|
if( bb.h === 0 || bb.w === 0 ){ |
|
eles.nodes().layoutPositions( this, options, function( ele ){ |
|
return { x: bb.x1, y: bb.y1 }; |
|
} ); |
|
|
|
} else { |
|
|
|
|
|
let cells = nodes.size(); |
|
let splits = Math.sqrt( cells * bb.h / bb.w ); |
|
let rows = Math.round( splits ); |
|
let cols = Math.round( bb.w / bb.h * splits ); |
|
|
|
let small = function( val ){ |
|
if( val == null ){ |
|
return Math.min( rows, cols ); |
|
} else { |
|
let min = Math.min( rows, cols ); |
|
if( min == rows ){ |
|
rows = val; |
|
} else { |
|
cols = val; |
|
} |
|
} |
|
}; |
|
|
|
let large = function( val ){ |
|
if( val == null ){ |
|
return Math.max( rows, cols ); |
|
} else { |
|
let max = Math.max( rows, cols ); |
|
if( max == rows ){ |
|
rows = val; |
|
} else { |
|
cols = val; |
|
} |
|
} |
|
}; |
|
|
|
let oRows = options.rows; |
|
let oCols = options.cols != null ? options.cols : options.columns; |
|
|
|
|
|
if( oRows != null && oCols != null ){ |
|
rows = oRows; |
|
cols = oCols; |
|
} else if( oRows != null && oCols == null ){ |
|
rows = oRows; |
|
cols = Math.ceil( cells / rows ); |
|
} else if( oRows == null && oCols != null ){ |
|
cols = oCols; |
|
rows = Math.ceil( cells / cols ); |
|
} |
|
|
|
|
|
|
|
|
|
else if( cols * rows > cells ){ |
|
let sm = small(); |
|
let lg = large(); |
|
|
|
|
|
if( (sm - 1) * lg >= cells ){ |
|
small( sm - 1 ); |
|
} else if( (lg - 1) * sm >= cells ){ |
|
large( lg - 1 ); |
|
} |
|
} else { |
|
|
|
|
|
while( cols * rows < cells ){ |
|
let sm = small(); |
|
let lg = large(); |
|
|
|
|
|
if( (lg + 1) * sm >= cells ){ |
|
large( lg + 1 ); |
|
} else { |
|
small( sm + 1 ); |
|
} |
|
} |
|
} |
|
|
|
let cellWidth = bb.w / cols; |
|
let cellHeight = bb.h / rows; |
|
|
|
if( options.condense ){ |
|
cellWidth = 0; |
|
cellHeight = 0; |
|
} |
|
|
|
if( options.avoidOverlap ){ |
|
for( let i = 0; i < nodes.length; i++ ){ |
|
let node = nodes[ i ]; |
|
let pos = node._private.position; |
|
|
|
if( pos.x == null || pos.y == null ){ |
|
pos.x = 0; |
|
pos.y = 0; |
|
} |
|
|
|
let nbb = node.layoutDimensions( options ); |
|
let p = options.avoidOverlapPadding; |
|
|
|
let w = nbb.w + p; |
|
let h = nbb.h + p; |
|
|
|
cellWidth = Math.max( cellWidth, w ); |
|
cellHeight = Math.max( cellHeight, h ); |
|
} |
|
} |
|
|
|
let cellUsed = {}; |
|
|
|
let used = function( row, col ){ |
|
return cellUsed[ 'c-' + row + '-' + col ] ? true : false; |
|
}; |
|
|
|
let use = function( row, col ){ |
|
cellUsed[ 'c-' + row + '-' + col ] = true; |
|
}; |
|
|
|
|
|
let row = 0; |
|
let col = 0; |
|
let moveToNextCell = function(){ |
|
col++; |
|
if( col >= cols ){ |
|
col = 0; |
|
row++; |
|
} |
|
}; |
|
|
|
|
|
let id2manPos = {}; |
|
for( let i = 0; i < nodes.length; i++ ){ |
|
let node = nodes[ i ]; |
|
let rcPos = options.position( node ); |
|
|
|
if( rcPos && (rcPos.row !== undefined || rcPos.col !== undefined) ){ |
|
let pos = { |
|
row: rcPos.row, |
|
col: rcPos.col |
|
}; |
|
|
|
if( pos.col === undefined ){ |
|
pos.col = 0; |
|
|
|
while( used( pos.row, pos.col ) ){ |
|
pos.col++; |
|
} |
|
} else if( pos.row === undefined ){ |
|
pos.row = 0; |
|
|
|
while( used( pos.row, pos.col ) ){ |
|
pos.row++; |
|
} |
|
} |
|
|
|
id2manPos[ node.id() ] = pos; |
|
use( pos.row, pos.col ); |
|
} |
|
} |
|
|
|
let getPos = function( element, i ){ |
|
let x, y; |
|
|
|
if( element.locked() || element.isParent() ){ |
|
return false; |
|
} |
|
|
|
|
|
let rcPos = id2manPos[ element.id() ]; |
|
if( rcPos ){ |
|
x = rcPos.col * cellWidth + cellWidth / 2 + bb.x1; |
|
y = rcPos.row * cellHeight + cellHeight / 2 + bb.y1; |
|
|
|
} else { |
|
|
|
while( used( row, col ) ){ |
|
moveToNextCell(); |
|
} |
|
|
|
x = col * cellWidth + cellWidth / 2 + bb.x1; |
|
y = row * cellHeight + cellHeight / 2 + bb.y1; |
|
use( row, col ); |
|
|
|
moveToNextCell(); |
|
} |
|
|
|
return { x: x, y: y }; |
|
|
|
}; |
|
|
|
nodes.layoutPositions( this, options, getPos ); |
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
export default GridLayout; |
|
|