|
import * as is from '../../is'; |
|
import { assignBoundingBox, expandBoundingBoxSides, clearBoundingBox, expandBoundingBox, makeBoundingBox, copyBoundingBox, shiftBoundingBox, updateBoundingBox } from '../../math'; |
|
import { defaults, getPrefixedProperty, hashIntsArray } from '../../util'; |
|
|
|
let fn, elesfn; |
|
|
|
fn = elesfn = {}; |
|
|
|
elesfn.renderedBoundingBox = function( options ){ |
|
let bb = this.boundingBox( options ); |
|
let cy = this.cy(); |
|
let zoom = cy.zoom(); |
|
let pan = cy.pan(); |
|
|
|
let x1 = bb.x1 * zoom + pan.x; |
|
let x2 = bb.x2 * zoom + pan.x; |
|
let y1 = bb.y1 * zoom + pan.y; |
|
let y2 = bb.y2 * zoom + pan.y; |
|
|
|
return { |
|
x1: x1, |
|
x2: x2, |
|
y1: y1, |
|
y2: y2, |
|
w: x2 - x1, |
|
h: y2 - y1 |
|
}; |
|
}; |
|
|
|
elesfn.dirtyCompoundBoundsCache = function(silent = false){ |
|
let cy = this.cy(); |
|
|
|
if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return this; } |
|
|
|
this.forEachUp( ele => { |
|
if( ele.isParent() ){ |
|
let _p = ele._private; |
|
|
|
_p.compoundBoundsClean = false; |
|
_p.bbCache = null; |
|
|
|
if(!silent){ |
|
ele.emitAndNotify('bounds'); |
|
} |
|
} |
|
} ); |
|
|
|
return this; |
|
}; |
|
|
|
elesfn.updateCompoundBounds = function(force = false){ |
|
let cy = this.cy(); |
|
|
|
|
|
if( !cy.styleEnabled() || !cy.hasCompoundNodes() ){ return this; } |
|
|
|
|
|
if( !force && cy.batching() ){ return this; } |
|
|
|
function update( parent ){ |
|
if( !parent.isParent() ){ return; } |
|
|
|
let _p = parent._private; |
|
let children = parent.children(); |
|
let includeLabels = parent.pstyle( 'compound-sizing-wrt-labels' ).value === 'include'; |
|
|
|
let min = { |
|
width: { |
|
val: parent.pstyle( 'min-width' ).pfValue, |
|
left: parent.pstyle( 'min-width-bias-left' ), |
|
right: parent.pstyle( 'min-width-bias-right' ) |
|
}, |
|
height: { |
|
val: parent.pstyle( 'min-height' ).pfValue, |
|
top: parent.pstyle( 'min-height-bias-top' ), |
|
bottom: parent.pstyle( 'min-height-bias-bottom' ) |
|
} |
|
}; |
|
|
|
let bb = children.boundingBox( { |
|
includeLabels: includeLabels, |
|
includeOverlays: false, |
|
|
|
|
|
|
|
useCache: false |
|
} ); |
|
let pos = _p.position; |
|
|
|
|
|
if( bb.w === 0 || bb.h === 0 ){ |
|
bb = { |
|
w: parent.pstyle('width').pfValue, |
|
h: parent.pstyle('height').pfValue |
|
}; |
|
|
|
bb.x1 = pos.x - bb.w/2; |
|
bb.x2 = pos.x + bb.w/2; |
|
bb.y1 = pos.y - bb.h/2; |
|
bb.y2 = pos.y + bb.h/2; |
|
} |
|
|
|
function computeBiasValues( propDiff, propBias, propBiasComplement ){ |
|
let biasDiff = 0; |
|
let biasComplementDiff = 0; |
|
let biasTotal = propBias + propBiasComplement; |
|
|
|
if( propDiff > 0 && biasTotal > 0 ){ |
|
biasDiff = ( propBias / biasTotal ) * propDiff; |
|
biasComplementDiff = ( propBiasComplement / biasTotal ) * propDiff; |
|
} |
|
return { |
|
biasDiff: biasDiff, |
|
biasComplementDiff: biasComplementDiff |
|
}; |
|
} |
|
|
|
function computePaddingValues( width, height, paddingObject, relativeTo ) { |
|
|
|
if(paddingObject.units === '%') { |
|
switch(relativeTo) { |
|
case 'width': |
|
return width > 0 ? paddingObject.pfValue * width : 0; |
|
case 'height': |
|
return height > 0 ? paddingObject.pfValue * height : 0; |
|
case 'average': |
|
return ( width > 0 ) && ( height > 0 ) ? paddingObject.pfValue * ( width + height ) / 2 : 0; |
|
case 'min': |
|
return ( width > 0 ) && ( height > 0 ) ? ( ( width > height ) ? paddingObject.pfValue * height : paddingObject.pfValue * width ) : 0; |
|
case 'max': |
|
return ( width > 0 ) && ( height > 0 ) ? ( ( width > height ) ? paddingObject.pfValue * width : paddingObject.pfValue * height ) : 0; |
|
default: |
|
return 0; |
|
} |
|
} else if(paddingObject.units === 'px') { |
|
return paddingObject.pfValue; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
let leftVal = min.width.left.value; |
|
if( min.width.left.units === 'px' && min.width.val > 0 ){ |
|
leftVal = ( leftVal * 100 ) / min.width.val; |
|
} |
|
let rightVal = min.width.right.value; |
|
if( min.width.right.units === 'px' && min.width.val > 0 ){ |
|
rightVal = ( rightVal * 100 ) / min.width.val; |
|
} |
|
|
|
let topVal = min.height.top.value; |
|
if( min.height.top.units === 'px' && min.height.val > 0 ){ |
|
topVal = ( topVal * 100 ) / min.height.val; |
|
} |
|
|
|
let bottomVal = min.height.bottom.value; |
|
if( min.height.bottom.units === 'px' && min.height.val > 0 ){ |
|
bottomVal = ( bottomVal * 100 ) / min.height.val; |
|
} |
|
|
|
let widthBiasDiffs = computeBiasValues( min.width.val - bb.w, leftVal, rightVal ); |
|
let diffLeft = widthBiasDiffs.biasDiff; |
|
let diffRight = widthBiasDiffs.biasComplementDiff; |
|
|
|
let heightBiasDiffs = computeBiasValues( min.height.val - bb.h, topVal, bottomVal ); |
|
let diffTop = heightBiasDiffs.biasDiff; |
|
let diffBottom = heightBiasDiffs.biasComplementDiff; |
|
|
|
_p.autoPadding = computePaddingValues( bb.w, bb.h, parent.pstyle( 'padding' ), parent.pstyle( 'padding-relative-to' ).value ); |
|
|
|
_p.autoWidth = Math.max(bb.w, min.width.val); |
|
pos.x = (- diffLeft + bb.x1 + bb.x2 + diffRight) / 2; |
|
|
|
_p.autoHeight = Math.max(bb.h, min.height.val); |
|
pos.y = (- diffTop + bb.y1 + bb.y2 + diffBottom) / 2; |
|
} |
|
|
|
for( let i = 0; i < this.length; i++ ){ |
|
let ele = this[i]; |
|
let _p = ele._private; |
|
|
|
if( !_p.compoundBoundsClean || force ){ |
|
update( ele ); |
|
|
|
if( !cy.batching() ){ |
|
_p.compoundBoundsClean = true; |
|
} |
|
} |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
let noninf = function( x ){ |
|
if( x === Infinity || x === -Infinity ){ |
|
return 0; |
|
} |
|
|
|
return x; |
|
}; |
|
|
|
let updateBounds = function( b, x1, y1, x2, y2 ){ |
|
|
|
if( x2 - x1 === 0 || y2 - y1 === 0 ){ return; } |
|
|
|
|
|
if( x1 == null || y1 == null || x2 == null || y2 == null ){ return; } |
|
|
|
b.x1 = x1 < b.x1 ? x1 : b.x1; |
|
b.x2 = x2 > b.x2 ? x2 : b.x2; |
|
b.y1 = y1 < b.y1 ? y1 : b.y1; |
|
b.y2 = y2 > b.y2 ? y2 : b.y2; |
|
b.w = b.x2 - b.x1; |
|
b.h = b.y2 - b.y1; |
|
}; |
|
|
|
let updateBoundsFromBox = function( b, b2 ){ |
|
if( b2 == null ){ return b; } |
|
|
|
return updateBounds( b, b2.x1, b2.y1, b2.x2, b2.y2 ); |
|
}; |
|
|
|
let prefixedProperty = function( obj, field, prefix ){ |
|
return getPrefixedProperty( obj, field, prefix ); |
|
}; |
|
|
|
let updateBoundsFromArrow = function( bounds, ele, prefix ){ |
|
if( ele.cy().headless() ){ return; } |
|
|
|
let _p = ele._private; |
|
let rstyle = _p.rstyle; |
|
let halfArW = rstyle.arrowWidth / 2; |
|
let arrowType = ele.pstyle( prefix + '-arrow-shape' ).value; |
|
let x; |
|
let y; |
|
|
|
if( arrowType !== 'none' ){ |
|
if( prefix === 'source' ){ |
|
x = rstyle.srcX; |
|
y = rstyle.srcY; |
|
} else if( prefix === 'target' ){ |
|
x = rstyle.tgtX; |
|
y = rstyle.tgtY; |
|
} else { |
|
x = rstyle.midX; |
|
y = rstyle.midY; |
|
} |
|
|
|
|
|
let bbs = _p.arrowBounds = _p.arrowBounds || {}; |
|
let bb = bbs[prefix] = bbs[prefix] || {}; |
|
bb.x1 = x - halfArW; |
|
bb.y1 = y - halfArW; |
|
bb.x2 = x + halfArW; |
|
bb.y2 = y + halfArW; |
|
bb.w = bb.x2 - bb.x1; |
|
bb.h = bb.y2 - bb.y1; |
|
expandBoundingBox(bb, 1); |
|
|
|
updateBounds( bounds, bb.x1, bb.y1, bb.x2, bb.y2 ); |
|
} |
|
}; |
|
|
|
let updateBoundsFromLabel = function( bounds, ele, prefix ){ |
|
if( ele.cy().headless() ){ return; } |
|
|
|
let prefixDash; |
|
|
|
if( prefix ){ |
|
prefixDash = prefix + '-'; |
|
} else { |
|
prefixDash = ''; |
|
} |
|
|
|
let _p = ele._private; |
|
let rstyle = _p.rstyle; |
|
let label = ele.pstyle( prefixDash + 'label' ).strValue; |
|
|
|
if( label ){ |
|
let halign = ele.pstyle( 'text-halign' ); |
|
let valign = ele.pstyle( 'text-valign' ); |
|
let labelWidth = prefixedProperty( rstyle, 'labelWidth', prefix ); |
|
let labelHeight = prefixedProperty( rstyle, 'labelHeight', prefix ); |
|
let labelX = prefixedProperty( rstyle, 'labelX', prefix ); |
|
let labelY = prefixedProperty( rstyle, 'labelY', prefix ); |
|
let marginX = ele.pstyle( prefixDash + 'text-margin-x' ).pfValue; |
|
let marginY = ele.pstyle( prefixDash + 'text-margin-y' ).pfValue; |
|
let isEdge = ele.isEdge(); |
|
let rotation = ele.pstyle( prefixDash + 'text-rotation' ); |
|
let outlineWidth = ele.pstyle( 'text-outline-width' ).pfValue; |
|
let borderWidth = ele.pstyle( 'text-border-width' ).pfValue; |
|
let halfBorderWidth = borderWidth / 2; |
|
let padding = ele.pstyle( 'text-background-padding' ).pfValue; |
|
let marginOfError = 2; |
|
|
|
let lh = labelHeight; |
|
let lw = labelWidth; |
|
let lw_2 = lw / 2; |
|
let lh_2 = lh / 2; |
|
let lx1, lx2, ly1, ly2; |
|
|
|
if( isEdge ){ |
|
lx1 = labelX - lw_2; |
|
lx2 = labelX + lw_2; |
|
ly1 = labelY - lh_2; |
|
ly2 = labelY + lh_2; |
|
} else { |
|
switch( halign.value ){ |
|
case 'left': |
|
lx1 = labelX - lw; |
|
lx2 = labelX; |
|
break; |
|
|
|
case 'center': |
|
lx1 = labelX - lw_2; |
|
lx2 = labelX + lw_2; |
|
break; |
|
|
|
case 'right': |
|
lx1 = labelX; |
|
lx2 = labelX + lw; |
|
break; |
|
} |
|
|
|
switch( valign.value ){ |
|
case 'top': |
|
ly1 = labelY - lh; |
|
ly2 = labelY; |
|
break; |
|
|
|
case 'center': |
|
ly1 = labelY - lh_2; |
|
ly2 = labelY + lh_2; |
|
break; |
|
|
|
case 'bottom': |
|
ly1 = labelY; |
|
ly2 = labelY + lh; |
|
break; |
|
} |
|
} |
|
|
|
|
|
lx1 += marginX - Math.max( outlineWidth, halfBorderWidth ) - padding - marginOfError; |
|
lx2 += marginX + Math.max( outlineWidth, halfBorderWidth ) + padding + marginOfError; |
|
ly1 += marginY - Math.max( outlineWidth, halfBorderWidth ) - padding - marginOfError; |
|
ly2 += marginY + Math.max( outlineWidth, halfBorderWidth ) + padding + marginOfError; |
|
|
|
|
|
let bbPrefix = prefix || 'main'; |
|
let bbs = _p.labelBounds; |
|
let bb = bbs[bbPrefix] = bbs[bbPrefix] || {}; |
|
bb.x1 = lx1; |
|
bb.y1 = ly1; |
|
bb.x2 = lx2; |
|
bb.y2 = ly2; |
|
bb.w = lx2 - lx1; |
|
bb.h = ly2 - ly1; |
|
|
|
let isAutorotate = ( isEdge && rotation.strValue === 'autorotate' ); |
|
let isPfValue = ( rotation.pfValue != null && rotation.pfValue !== 0 ); |
|
|
|
if( isAutorotate || isPfValue ){ |
|
let theta = isAutorotate ? prefixedProperty( _p.rstyle, 'labelAngle', prefix ) : rotation.pfValue; |
|
let cos = Math.cos( theta ); |
|
let sin = Math.sin( theta ); |
|
|
|
|
|
let xo = (lx1 + lx2)/2; |
|
let yo = (ly1 + ly2)/2; |
|
|
|
if( !isEdge ){ |
|
switch( halign.value ){ |
|
case 'left': |
|
xo = lx2; |
|
break; |
|
|
|
case 'right': |
|
xo = lx1; |
|
break; |
|
} |
|
|
|
switch( valign.value ){ |
|
case 'top': |
|
yo = ly2; |
|
break; |
|
|
|
case 'bottom': |
|
yo = ly1; |
|
break; |
|
} |
|
} |
|
|
|
let rotate = function( x, y ){ |
|
x = x - xo; |
|
y = y - yo; |
|
|
|
return { |
|
x: x * cos - y * sin + xo, |
|
y: x * sin + y * cos + yo |
|
}; |
|
}; |
|
|
|
let px1y1 = rotate( lx1, ly1 ); |
|
let px1y2 = rotate( lx1, ly2 ); |
|
let px2y1 = rotate( lx2, ly1 ); |
|
let px2y2 = rotate( lx2, ly2 ); |
|
|
|
lx1 = Math.min( px1y1.x, px1y2.x, px2y1.x, px2y2.x ); |
|
lx2 = Math.max( px1y1.x, px1y2.x, px2y1.x, px2y2.x ); |
|
ly1 = Math.min( px1y1.y, px1y2.y, px2y1.y, px2y2.y ); |
|
ly2 = Math.max( px1y1.y, px1y2.y, px2y1.y, px2y2.y ); |
|
} |
|
|
|
let bbPrefixRot = bbPrefix + 'Rot'; |
|
let bbRot = bbs[bbPrefixRot] = bbs[bbPrefixRot] || {}; |
|
bbRot.x1 = lx1; |
|
bbRot.y1 = ly1; |
|
bbRot.x2 = lx2; |
|
bbRot.y2 = ly2; |
|
bbRot.w = lx2 - lx1; |
|
bbRot.h = ly2 - ly1; |
|
|
|
updateBounds( bounds, lx1, ly1, lx2, ly2 ); |
|
updateBounds( _p.labelBounds.all, lx1, ly1, lx2, ly2 ); |
|
} |
|
|
|
return bounds; |
|
}; |
|
|
|
let updateBoundsFromOutline = function( bounds, ele ){ |
|
if( ele.cy().headless() ){ return; } |
|
|
|
let outlineOpacity = ele.pstyle('outline-opacity').value; |
|
let outlineWidth = ele.pstyle('outline-width').value; |
|
|
|
if (outlineOpacity > 0 && outlineWidth > 0) { |
|
let outlineOffset = ele.pstyle('outline-offset').value; |
|
let nodeShape = ele.pstyle( 'shape' ).value; |
|
|
|
let outlineSize = outlineWidth + outlineOffset; |
|
let scaleX = (bounds.w + outlineSize * 2) / bounds.w; |
|
let scaleY = (bounds.h + outlineSize * 2) / bounds.h; |
|
let xOffset = 0; |
|
let yOffset = 0; |
|
|
|
if (["diamond", "pentagon", "round-triangle"].includes(nodeShape)) { |
|
scaleX = (bounds.w + outlineSize * 2.4) / bounds.w; |
|
yOffset = -outlineSize/3.6; |
|
} else if (["concave-hexagon", "rhomboid", "right-rhomboid"].includes(nodeShape)) { |
|
scaleX = (bounds.w + outlineSize * 2.4) / bounds.w; |
|
} else if (nodeShape === "star") { |
|
scaleX = (bounds.w + outlineSize * 2.8) / bounds.w; |
|
scaleY = (bounds.h + outlineSize * 2.6) / bounds.h; |
|
yOffset = -outlineSize / 3.8; |
|
} else if (nodeShape === "triangle") { |
|
scaleX = (bounds.w + outlineSize * 2.8) / bounds.w; |
|
scaleY = (bounds.h + outlineSize * 2.4) / bounds.h; |
|
yOffset = -outlineSize/1.4; |
|
} else if (nodeShape === "vee") { |
|
scaleX = (bounds.w + outlineSize * 4.4) / bounds.w; |
|
scaleY = (bounds.h + outlineSize * 3.8) / bounds.h; |
|
yOffset = -outlineSize * .5; |
|
} |
|
|
|
let hDelta = (bounds.h * scaleY) - bounds.h; |
|
let wDelta = (bounds.w * scaleX) - bounds.w; |
|
expandBoundingBoxSides(bounds, [Math.ceil(hDelta/2), Math.ceil(wDelta/2)]); |
|
|
|
if (xOffset != 0 || yOffset !== 0) { |
|
let oBounds = shiftBoundingBox(bounds, xOffset, yOffset); |
|
updateBoundingBox(bounds, oBounds); |
|
} |
|
} |
|
}; |
|
|
|
|
|
let boundingBoxImpl = function( ele, options ){ |
|
let cy = ele._private.cy; |
|
let styleEnabled = cy.styleEnabled(); |
|
let headless = cy.headless(); |
|
|
|
let bounds = makeBoundingBox(); |
|
|
|
let _p = ele._private; |
|
let isNode = ele.isNode(); |
|
let isEdge = ele.isEdge(); |
|
let ex1, ex2, ey1, ey2; |
|
let x, y; |
|
let rstyle = _p.rstyle; |
|
let manualExpansion = isNode && styleEnabled ? ele.pstyle('bounds-expansion').pfValue : [0]; |
|
|
|
|
|
|
|
let isDisplayed = ele => ele.pstyle('display').value !== 'none'; |
|
|
|
let displayed = ( |
|
!styleEnabled |
|
|| ( |
|
isDisplayed(ele) |
|
|
|
|
|
&& ( !isEdge || ( isDisplayed(ele.source()) && isDisplayed(ele.target()) ) ) |
|
) |
|
); |
|
|
|
if( displayed ){ |
|
let overlayOpacity = 0; |
|
let overlayPadding = 0; |
|
|
|
if( styleEnabled && options.includeOverlays ){ |
|
overlayOpacity = ele.pstyle( 'overlay-opacity' ).value; |
|
|
|
if( overlayOpacity !== 0 ){ |
|
overlayPadding = ele.pstyle( 'overlay-padding' ).value; |
|
} |
|
} |
|
|
|
let underlayOpacity = 0; |
|
let underlayPadding = 0; |
|
|
|
if( styleEnabled && options.includeUnderlays ){ |
|
underlayOpacity = ele.pstyle( 'underlay-opacity' ).value; |
|
|
|
if( underlayOpacity !== 0 ){ |
|
underlayPadding = ele.pstyle( 'underlay-padding' ).value; |
|
} |
|
} |
|
|
|
let padding = Math.max(overlayPadding, underlayPadding); |
|
|
|
let w = 0; |
|
let wHalf = 0; |
|
|
|
if( styleEnabled ){ |
|
w = ele.pstyle( 'width' ).pfValue; |
|
wHalf = w / 2; |
|
} |
|
|
|
if( isNode && options.includeNodes ){ |
|
let pos = ele.position(); |
|
x = pos.x; |
|
y = pos.y; |
|
let w = ele.outerWidth(); |
|
let halfW = w / 2; |
|
let h = ele.outerHeight(); |
|
let halfH = h / 2; |
|
|
|
|
|
|
|
|
|
ex1 = x - halfW; |
|
ex2 = x + halfW; |
|
ey1 = y - halfH; |
|
ey2 = y + halfH; |
|
|
|
updateBounds( bounds, ex1, ey1, ex2, ey2 ); |
|
|
|
if( styleEnabled && options.includeOutlines ){ |
|
updateBoundsFromOutline( bounds, ele ); |
|
} |
|
} else if( isEdge && options.includeEdges ){ |
|
|
|
if( styleEnabled && !headless ){ |
|
let curveStyle = ele.pstyle( 'curve-style').strValue; |
|
|
|
|
|
|
|
|
|
|
|
ex1 = Math.min( rstyle.srcX, rstyle.midX, rstyle.tgtX ); |
|
ex2 = Math.max( rstyle.srcX, rstyle.midX, rstyle.tgtX ); |
|
ey1 = Math.min( rstyle.srcY, rstyle.midY, rstyle.tgtY ); |
|
ey2 = Math.max( rstyle.srcY, rstyle.midY, rstyle.tgtY ); |
|
|
|
|
|
ex1 -= wHalf; |
|
ex2 += wHalf; |
|
ey1 -= wHalf; |
|
ey2 += wHalf; |
|
|
|
updateBounds( bounds, ex1, ey1, ex2, ey2 ); |
|
|
|
|
|
|
|
|
|
|
|
if( curveStyle === 'haystack' ){ |
|
let hpts = rstyle.haystackPts; |
|
|
|
if( hpts && hpts.length === 2 ){ |
|
ex1 = hpts[0].x; |
|
ey1 = hpts[0].y; |
|
ex2 = hpts[1].x; |
|
ey2 = hpts[1].y; |
|
|
|
if( ex1 > ex2 ){ |
|
let temp = ex1; |
|
ex1 = ex2; |
|
ex2 = temp; |
|
} |
|
|
|
if( ey1 > ey2 ){ |
|
let temp = ey1; |
|
ey1 = ey2; |
|
ey2 = temp; |
|
} |
|
|
|
updateBounds( bounds, ex1 - wHalf, ey1 - wHalf, ex2 + wHalf, ey2 + wHalf ); |
|
} |
|
|
|
} else if( |
|
curveStyle === 'bezier' || curveStyle === 'unbundled-bezier' |
|
|| curveStyle.endsWith('segments') || curveStyle.endsWith('taxi') |
|
){ |
|
let pts; |
|
|
|
switch( curveStyle ){ |
|
case 'bezier': |
|
case 'unbundled-bezier': |
|
pts = rstyle.bezierPts; |
|
break; |
|
case 'segments': |
|
case 'taxi': |
|
case 'round-segments': |
|
case 'round-taxi': |
|
pts = rstyle.linePts; |
|
break; |
|
} |
|
|
|
if( pts != null ){ |
|
for( let j = 0; j < pts.length; j++ ){ |
|
let pt = pts[ j ]; |
|
|
|
ex1 = pt.x - wHalf; |
|
ex2 = pt.x + wHalf; |
|
ey1 = pt.y - wHalf; |
|
ey2 = pt.y + wHalf; |
|
|
|
updateBounds( bounds, ex1, ey1, ex2, ey2 ); |
|
} |
|
} |
|
} |
|
} else { |
|
|
|
|
|
|
|
|
|
let n1 = ele.source(); |
|
let n1pos = n1.position(); |
|
|
|
let n2 = ele.target(); |
|
let n2pos = n2.position(); |
|
|
|
ex1 = n1pos.x; |
|
ex2 = n2pos.x; |
|
ey1 = n1pos.y; |
|
ey2 = n2pos.y; |
|
|
|
if( ex1 > ex2 ){ |
|
let temp = ex1; |
|
ex1 = ex2; |
|
ex2 = temp; |
|
} |
|
|
|
if( ey1 > ey2 ){ |
|
let temp = ey1; |
|
ey1 = ey2; |
|
ey2 = temp; |
|
} |
|
|
|
|
|
ex1 -= wHalf; |
|
ex2 += wHalf; |
|
ey1 -= wHalf; |
|
ey2 += wHalf; |
|
|
|
updateBounds( bounds, ex1, ey1, ex2, ey2 ); |
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
if( styleEnabled && options.includeEdges && isEdge ){ |
|
updateBoundsFromArrow( bounds, ele, 'mid-source', options ); |
|
updateBoundsFromArrow( bounds, ele, 'mid-target', options ); |
|
updateBoundsFromArrow( bounds, ele, 'source', options ); |
|
updateBoundsFromArrow( bounds, ele, 'target', options ); |
|
} |
|
|
|
|
|
|
|
|
|
if( styleEnabled ){ |
|
let ghost = ele.pstyle('ghost').value === 'yes'; |
|
|
|
if( ghost ){ |
|
let gx = ele.pstyle('ghost-offset-x').pfValue; |
|
let gy = ele.pstyle('ghost-offset-y').pfValue; |
|
|
|
updateBounds( bounds, bounds.x1 + gx, bounds.y1 + gy, bounds.x2 + gx, bounds.y2 + gy ); |
|
} |
|
} |
|
|
|
|
|
let bbBody = _p.bodyBounds = _p.bodyBounds || {}; |
|
assignBoundingBox(bbBody, bounds); |
|
expandBoundingBoxSides(bbBody, manualExpansion); |
|
expandBoundingBox(bbBody, 1); |
|
|
|
|
|
|
|
|
|
if( styleEnabled ){ |
|
ex1 = bounds.x1; |
|
ex2 = bounds.x2; |
|
ey1 = bounds.y1; |
|
ey2 = bounds.y2; |
|
|
|
updateBounds( bounds, ex1 - padding, ey1 - padding, ex2 + padding, ey2 + padding ); |
|
} |
|
|
|
|
|
let bbOverlay = _p.overlayBounds = _p.overlayBounds || {}; |
|
assignBoundingBox(bbOverlay, bounds); |
|
expandBoundingBoxSides(bbOverlay, manualExpansion); |
|
expandBoundingBox(bbOverlay, 1); |
|
|
|
|
|
|
|
|
|
let bbLabels = _p.labelBounds = _p.labelBounds || {}; |
|
|
|
if( bbLabels.all != null ){ |
|
clearBoundingBox(bbLabels.all); |
|
} else { |
|
bbLabels.all = makeBoundingBox(); |
|
} |
|
|
|
if( styleEnabled && options.includeLabels ){ |
|
if( options.includeMainLabels ){ |
|
updateBoundsFromLabel( bounds, ele, null, options ); |
|
} |
|
|
|
if( isEdge ){ |
|
if( options.includeSourceLabels ){ |
|
updateBoundsFromLabel( bounds, ele, 'source', options ); |
|
} |
|
|
|
if( options.includeTargetLabels ){ |
|
updateBoundsFromLabel( bounds, ele, 'target', options ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
bounds.x1 = noninf( bounds.x1 ); |
|
bounds.y1 = noninf( bounds.y1 ); |
|
bounds.x2 = noninf( bounds.x2 ); |
|
bounds.y2 = noninf( bounds.y2 ); |
|
bounds.w = noninf( bounds.x2 - bounds.x1 ); |
|
bounds.h = noninf( bounds.y2 - bounds.y1 ); |
|
|
|
if( bounds.w > 0 && bounds.h > 0 && displayed ){ |
|
expandBoundingBoxSides( bounds, manualExpansion ); |
|
|
|
|
|
expandBoundingBox( bounds, 1 ); |
|
} |
|
|
|
return bounds; |
|
}; |
|
|
|
let getKey = function( opts ){ |
|
let i = 0; |
|
let tf = val => (val ? 1 : 0) << i++; |
|
let key = 0; |
|
|
|
key += tf( opts.incudeNodes ); |
|
key += tf( opts.includeEdges ); |
|
key += tf( opts.includeLabels ); |
|
key += tf( opts.includeMainLabels ); |
|
key += tf( opts.includeSourceLabels ); |
|
key += tf( opts.includeTargetLabels ); |
|
key += tf( opts.includeOverlays ); |
|
key += tf( opts.includeOutlines ); |
|
|
|
return key; |
|
}; |
|
|
|
let getBoundingBoxPosKey = ele => { |
|
if( ele.isEdge() ){ |
|
let p1 = ele.source().position(); |
|
let p2 = ele.target().position(); |
|
let r = x => Math.round(x); |
|
|
|
return hashIntsArray([ r(p1.x), r(p1.y), r(p2.x), r(p2.y) ]); |
|
} else { |
|
return 0; |
|
} |
|
}; |
|
|
|
let cachedBoundingBoxImpl = function( ele, opts ){ |
|
let _p = ele._private; |
|
let bb; |
|
let isEdge = ele.isEdge(); |
|
let key = opts == null ? defBbOptsKey : getKey( opts ); |
|
let usingDefOpts = key === defBbOptsKey; |
|
let currPosKey = getBoundingBoxPosKey( ele ); |
|
let isPosKeySame = _p.bbCachePosKey === currPosKey; |
|
let useCache = opts.useCache && isPosKeySame; |
|
let isDirty = ele => ele._private.bbCache == null || ele._private.styleDirty; |
|
let needRecalc = !useCache || isDirty(ele) || (isEdge && isDirty(ele.source()) || isDirty(ele.target())); |
|
|
|
if( needRecalc ){ |
|
if( !isPosKeySame ){ |
|
ele.recalculateRenderedStyle(useCache); |
|
} |
|
|
|
bb = boundingBoxImpl( ele, defBbOpts ); |
|
|
|
_p.bbCache = bb; |
|
_p.bbCachePosKey = currPosKey; |
|
} else { |
|
bb = _p.bbCache; |
|
} |
|
|
|
|
|
if( !usingDefOpts ){ |
|
let isNode = ele.isNode(); |
|
|
|
bb = makeBoundingBox(); |
|
|
|
if( (opts.includeNodes && isNode) || (opts.includeEdges && !isNode) ){ |
|
if( opts.includeOverlays ){ |
|
updateBoundsFromBox(bb, _p.overlayBounds); |
|
} else { |
|
updateBoundsFromBox(bb, _p.bodyBounds); |
|
} |
|
} |
|
|
|
if( opts.includeLabels ){ |
|
if( opts.includeMainLabels && (!isEdge || (opts.includeSourceLabels && opts.includeTargetLabels)) ){ |
|
updateBoundsFromBox(bb, _p.labelBounds.all); |
|
} else { |
|
if( opts.includeMainLabels ){ |
|
updateBoundsFromBox(bb, _p.labelBounds.mainRot); |
|
} |
|
|
|
if( opts.includeSourceLabels ){ |
|
updateBoundsFromBox(bb, _p.labelBounds.sourceRot); |
|
} |
|
|
|
if( opts.includeTargetLabels ){ |
|
updateBoundsFromBox(bb, _p.labelBounds.targetRot); |
|
} |
|
} |
|
} |
|
|
|
bb.w = bb.x2 - bb.x1; |
|
bb.h = bb.y2 - bb.y1; |
|
} |
|
|
|
return bb; |
|
}; |
|
|
|
let defBbOpts = { |
|
includeNodes: true, |
|
includeEdges: true, |
|
includeLabels: true, |
|
includeMainLabels: true, |
|
includeSourceLabels: true, |
|
includeTargetLabels: true, |
|
includeOverlays: true, |
|
includeUnderlays: true, |
|
includeOutlines: true, |
|
useCache: true |
|
}; |
|
|
|
const defBbOptsKey = getKey( defBbOpts ); |
|
|
|
const filledBbOpts = defaults( defBbOpts ); |
|
|
|
elesfn.boundingBox = function( options ){ |
|
let bounds; |
|
|
|
|
|
|
|
|
|
if( this.length === 1 && this[0]._private.bbCache != null && !this[0]._private.styleDirty && (options === undefined || options.useCache === undefined || options.useCache === true) ){ |
|
if( options === undefined ){ |
|
options = defBbOpts; |
|
} else { |
|
options = filledBbOpts( options ); |
|
} |
|
|
|
bounds = cachedBoundingBoxImpl( this[0], options ); |
|
} else { |
|
bounds = makeBoundingBox(); |
|
|
|
options = options || defBbOpts; |
|
|
|
let opts = filledBbOpts( options ); |
|
|
|
let eles = this; |
|
let cy = eles.cy(); |
|
let styleEnabled = cy.styleEnabled(); |
|
|
|
if( styleEnabled ){ |
|
for( let i = 0; i < eles.length; i++ ){ |
|
let ele = eles[i]; |
|
let _p = ele._private; |
|
let currPosKey = getBoundingBoxPosKey( ele ); |
|
let isPosKeySame = _p.bbCachePosKey === currPosKey; |
|
let useCache = opts.useCache && isPosKeySame && !_p.styleDirty; |
|
|
|
ele.recalculateRenderedStyle( useCache ); |
|
} |
|
} |
|
|
|
this.updateCompoundBounds(!options.useCache); |
|
|
|
for( let i = 0; i < eles.length; i++ ){ |
|
let ele = eles[i]; |
|
|
|
updateBoundsFromBox( bounds, cachedBoundingBoxImpl( ele, opts ) ); |
|
} |
|
} |
|
|
|
bounds.x1 = noninf( bounds.x1 ); |
|
bounds.y1 = noninf( bounds.y1 ); |
|
bounds.x2 = noninf( bounds.x2 ); |
|
bounds.y2 = noninf( bounds.y2 ); |
|
bounds.w = noninf( bounds.x2 - bounds.x1 ); |
|
bounds.h = noninf( bounds.y2 - bounds.y1 ); |
|
|
|
return bounds; |
|
}; |
|
|
|
elesfn.dirtyBoundingBoxCache = function(){ |
|
for( let i = 0; i < this.length; i++ ){ |
|
let _p = this[i]._private; |
|
|
|
_p.bbCache = null; |
|
_p.bbCachePosKey = null; |
|
_p.bodyBounds = null; |
|
_p.overlayBounds = null; |
|
_p.labelBounds.all = null; |
|
_p.labelBounds.source = null; |
|
_p.labelBounds.target = null; |
|
_p.labelBounds.main = null; |
|
_p.labelBounds.sourceRot = null; |
|
_p.labelBounds.targetRot = null; |
|
_p.labelBounds.mainRot = null; |
|
_p.arrowBounds.source = null; |
|
_p.arrowBounds.target = null; |
|
_p.arrowBounds['mid-source'] = null; |
|
_p.arrowBounds['mid-target'] = null; |
|
} |
|
|
|
this.emitAndNotify('bounds'); |
|
|
|
return this; |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
elesfn.boundingBoxAt = function( fn ){ |
|
let nodes = this.nodes(); |
|
let cy = this.cy(); |
|
let hasCompoundNodes = cy.hasCompoundNodes(); |
|
let parents = cy.collection(); |
|
|
|
if( hasCompoundNodes ){ |
|
parents = nodes.filter(node => node.isParent()); |
|
nodes = nodes.not(parents); |
|
} |
|
|
|
if( is.plainObject( fn ) ){ |
|
let obj = fn; |
|
|
|
fn = function(){ return obj; }; |
|
} |
|
|
|
let storeOldPos = (node, i) => node._private.bbAtOldPos = fn(node, i); |
|
let getOldPos = (node) => node._private.bbAtOldPos; |
|
|
|
cy.startBatch(); |
|
|
|
( |
|
nodes |
|
.forEach(storeOldPos) |
|
.silentPositions(fn) |
|
); |
|
|
|
if( hasCompoundNodes ){ |
|
parents.dirtyCompoundBoundsCache(); |
|
parents.dirtyBoundingBoxCache(); |
|
parents.updateCompoundBounds(true); |
|
} |
|
|
|
let bb = copyBoundingBox( this.boundingBox({ useCache: false }) ); |
|
|
|
nodes.silentPositions(getOldPos); |
|
|
|
if( hasCompoundNodes ){ |
|
parents.dirtyCompoundBoundsCache(); |
|
parents.dirtyBoundingBoxCache(); |
|
parents.updateCompoundBounds(true); |
|
} |
|
|
|
cy.endBatch(); |
|
|
|
return bb; |
|
}; |
|
|
|
fn.boundingbox = fn.bb = fn.boundingBox; |
|
fn.renderedBoundingbox = fn.renderedBoundingBox; |
|
|
|
export default elesfn; |
|
|