cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
846 lines (664 loc) • 25.7 kB
JavaScript
import * as util from '../util';
import * as is from '../is';
import Promise from '../promise';
const styfn = {};
// keys for style blocks, e.g. ttfftt
const TRUE = 't';
const FALSE = 'f';
// (potentially expensive calculation)
// apply the style to the element based on
// - its bypass
// - what selectors match it
styfn.apply = function( eles ){
let self = this;
let _p = self._private;
let cy = _p.cy;
let updatedEles = cy.collection();
for( let ie = 0; ie < eles.length; ie++ ){
let ele = eles[ ie ];
let cxtMeta = self.getContextMeta( ele );
if( cxtMeta.empty ){
continue;
}
let cxtStyle = self.getContextStyle( cxtMeta );
let app = self.applyContextStyle( cxtMeta, cxtStyle, ele );
if( ele._private.appliedInitStyle ){
self.updateTransitions( ele, app.diffProps );
} else {
ele._private.appliedInitStyle = true;
}
let hintsDiff = self.updateStyleHints( ele );
if( hintsDiff ){
updatedEles.push( ele );
}
} // for elements
return updatedEles;
};
styfn.getPropertiesDiff = function( oldCxtKey, newCxtKey ){
let self = this;
let cache = self._private.propDiffs = self._private.propDiffs || {};
let dualCxtKey = oldCxtKey + '-' + newCxtKey;
let cachedVal = cache[ dualCxtKey ];
if( cachedVal ){
return cachedVal;
}
let diffProps = [];
let addedProp = {};
for( let i = 0; i < self.length; i++ ){
let cxt = self[ i ];
let oldHasCxt = oldCxtKey[ i ] === TRUE;
let newHasCxt = newCxtKey[ i ] === TRUE;
let cxtHasDiffed = oldHasCxt !== newHasCxt;
let cxtHasMappedProps = cxt.mappedProperties.length > 0;
if( cxtHasDiffed || ( newHasCxt && cxtHasMappedProps )){
let props;
if( cxtHasDiffed && cxtHasMappedProps ){
props = cxt.properties; // suffices b/c mappedProperties is a subset of properties
} else if( cxtHasDiffed ){
props = cxt.properties; // need to check them all
} else if( cxtHasMappedProps ){
props = cxt.mappedProperties; // only need to check mapped
}
for( let j = 0; j < props.length; j++ ){
let prop = props[ j ];
let name = prop.name;
// if a later context overrides this property, then the fact that this context has switched/diffed doesn't matter
// (semi expensive check since it makes this function O(n^2) on context length, but worth it since overall result
// is cached)
let laterCxtOverrides = false;
for( let k = i + 1; k < self.length; k++ ){
let laterCxt = self[ k ];
let hasLaterCxt = newCxtKey[ k ] === TRUE;
if( !hasLaterCxt ){ continue; } // can't override unless the context is active
laterCxtOverrides = laterCxt.properties[ prop.name ] != null;
if( laterCxtOverrides ){ break; } // exit early as long as one later context overrides
}
if( !addedProp[ name ] && !laterCxtOverrides ){
addedProp[ name ] = true;
diffProps.push( name );
}
} // for props
} // if
} // for contexts
cache[ dualCxtKey ] = diffProps;
return diffProps;
};
styfn.getContextMeta = function( ele ){
let self = this;
let cxtKey = '';
let diffProps;
let prevKey = ele._private.styleCxtKey || '';
// get the cxt key
for( let i = 0; i < self.length; i++ ){
let context = self[ i ];
let contextSelectorMatches = context.selector && context.selector.matches( ele ); // NB: context.selector may be null for 'core'
if( contextSelectorMatches ){
cxtKey += TRUE;
} else {
cxtKey += FALSE;
}
} // for context
diffProps = self.getPropertiesDiff( prevKey, cxtKey );
ele._private.styleCxtKey = cxtKey;
return {
key: cxtKey,
diffPropNames: diffProps,
empty: diffProps.length === 0
};
};
// gets a computed ele style object based on matched contexts
styfn.getContextStyle = function( cxtMeta ){
let cxtKey = cxtMeta.key;
let self = this;
let cxtStyles = this._private.contextStyles = this._private.contextStyles || {};
// if already computed style, returned cached copy
if( cxtStyles[ cxtKey ] ){ return cxtStyles[ cxtKey ]; }
let style = {
_private: {
key: cxtKey
}
};
for( let i = 0; i < self.length; i++ ){
let cxt = self[ i ];
let hasCxt = cxtKey[ i ] === TRUE;
if( !hasCxt ){ continue; }
for( let j = 0; j < cxt.properties.length; j++ ){
let prop = cxt.properties[ j ];
style[ prop.name ] = prop;
}
}
cxtStyles[ cxtKey ] = style;
return style;
};
styfn.applyContextStyle = function( cxtMeta, cxtStyle, ele ){
let self = this;
let diffProps = cxtMeta.diffPropNames;
let retDiffProps = {};
let types = self.types;
for( let i = 0; i < diffProps.length; i++ ){
let diffPropName = diffProps[ i ];
let cxtProp = cxtStyle[ diffPropName ];
let eleProp = ele.pstyle( diffPropName );
if( !cxtProp ){ // no context prop means delete
if( !eleProp ){
continue; // no existing prop means nothing needs to be removed
// nb affects initial application on mapped values like control-point-distances
} else if( eleProp.bypass ){
cxtProp = { name: diffPropName, deleteBypassed: true };
} else {
cxtProp = { name: diffPropName, delete: true };
}
}
// save cycles when the context prop doesn't need to be applied
if( eleProp === cxtProp ){ continue; }
// save cycles when a mapped context prop doesn't need to be applied
if(
cxtProp.mapped === types.fn // context prop is function mapper
&& eleProp != null // some props can be null even by default (e.g. a prop that overrides another one)
&& eleProp.mapping != null // ele prop is a concrete value from from a mapper
&& eleProp.mapping.value === cxtProp.value // the current prop on the ele is a flat prop value for the function mapper
){ // NB don't write to cxtProp, as it's shared among eles (stored in stylesheet)
let mapping = eleProp.mapping; // can write to mapping, as it's a per-ele copy
let fnValue = mapping.fnValue = cxtProp.value( ele ); // temporarily cache the value in case of a miss
if( fnValue === mapping.prevFnValue ){ continue; }
}
let retDiffProp = retDiffProps[ diffPropName ] = {
prev: eleProp
};
self.applyParsedProperty( ele, cxtProp );
retDiffProp.next = ele.pstyle( diffPropName );
if( retDiffProp.next && retDiffProp.next.bypass ){
retDiffProp.next = retDiffProp.next.bypassed;
}
}
return {
diffProps: retDiffProps
};
};
styfn.updateStyleHints = function(ele){
let _p = ele._private;
let self = this;
let propNames = self.propertyGroupNames;
let propGrKeys = self.propertyGroupKeys;
let propHash = ( ele, propNames, seedKey ) => self.getPropertiesHash( ele, propNames, seedKey );
let oldStyleKey = _p.styleKey;
if( ele.removed() ){ return false; }
let isNode = _p.group === 'nodes';
// get the style key hashes per prop group
// but lazily -- only use non-default prop values to reduce the number of hashes
//
let overriddenStyles = ele._private.style;
propNames = Object.keys( overriddenStyles );
for( let i = 0; i < propGrKeys.length; i++ ){
let grKey = propGrKeys[i];
_p.styleKeys[ grKey ] = [ util.DEFAULT_HASH_SEED, util.DEFAULT_HASH_SEED_ALT ];
}
let updateGrKey1 = (val, grKey) => _p.styleKeys[ grKey ][0] = util.hashInt( val, _p.styleKeys[ grKey ][0] );
let updateGrKey2 = (val, grKey) => _p.styleKeys[ grKey ][1] = util.hashIntAlt( val, _p.styleKeys[ grKey ][1] );
let updateGrKey = (val, grKey) => {
updateGrKey1(val, grKey);
updateGrKey2(val, grKey);
};
let updateGrKeyWStr = (strVal, grKey) => {
for( let j = 0; j < strVal.length; j++ ){
let ch = strVal.charCodeAt(j);
updateGrKey1(ch, grKey);
updateGrKey2(ch, grKey);
}
};
// - hashing works on 32 bit ints b/c we use bitwise ops
// - small numbers get cut off (e.g. 0.123 is seen as 0 by the hashing function)
// - raise up small numbers so more significant digits are seen by hashing
// - make small numbers larger than a normal value to avoid collisions
// - works in practice and it's relatively cheap
let N = 2000000000;
let cleanNum = val => (-128 < val && val < 128) && Math.floor(val) !== val ? N - ((val * 1024) | 0) : val;
for( let i = 0; i < propNames.length; i++ ){
let name = propNames[i];
let parsedProp = overriddenStyles[ name ];
if( parsedProp == null ){ continue; }
let propInfo = this.properties[name];
let type = propInfo.type;
let grKey = propInfo.groupKey;
let normalizedNumberVal;
if( propInfo.hashOverride != null ){
normalizedNumberVal = propInfo.hashOverride(ele, parsedProp);
} else if( parsedProp.pfValue != null ){
normalizedNumberVal = parsedProp.pfValue;
}
// might not be a number if it allows enums
let numberVal = propInfo.enums == null ? parsedProp.value : null;
let haveNormNum = normalizedNumberVal != null;
let haveUnitedNum = numberVal != null;
let haveNum = haveNormNum || haveUnitedNum;
let units = parsedProp.units;
// numbers are cheaper to hash than strings
// 1 hash op vs n hash ops (for length n string)
if( type.number && haveNum && !type.multiple ){
let v = haveNormNum ? normalizedNumberVal : numberVal;
updateGrKey(cleanNum(v), grKey);
if( !haveNormNum && units != null ){
updateGrKeyWStr(units, grKey);
}
} else {
updateGrKeyWStr(parsedProp.strValue, grKey);
}
}
// overall style key
//
let hash = [ util.DEFAULT_HASH_SEED, util.DEFAULT_HASH_SEED_ALT ];
for( let i = 0; i < propGrKeys.length; i++ ){
let grKey = propGrKeys[i];
let grHash = _p.styleKeys[ grKey ];
hash[0] = util.hashInt( grHash[0], hash[0] );
hash[1] = util.hashIntAlt( grHash[1], hash[1] );
}
_p.styleKey = util.combineHashes(hash[0], hash[1]);
// label dims
//
let sk = _p.styleKeys;
_p.labelDimsKey = util.combineHashesArray(sk.labelDimensions);
let labelKeys = propHash( ele, ['label'], sk.labelDimensions );
_p.labelKey = util.combineHashesArray(labelKeys);
_p.labelStyleKey = util.combineHashesArray(util.hashArrays(sk.commonLabel, labelKeys));
if( !isNode ){
let sourceLabelKeys = propHash( ele, ['source-label'], sk.labelDimensions );
_p.sourceLabelKey = util.combineHashesArray(sourceLabelKeys);
_p.sourceLabelStyleKey = util.combineHashesArray(util.hashArrays(sk.commonLabel, sourceLabelKeys));
let targetLabelKeys = propHash( ele, ['target-label'], sk.labelDimensions );
_p.targetLabelKey = util.combineHashesArray(targetLabelKeys);
_p.targetLabelStyleKey = util.combineHashesArray(util.hashArrays(sk.commonLabel, targetLabelKeys));
}
// node
//
if( isNode ){
let { nodeBody, nodeBorder, backgroundImage, compound, pie } = _p.styleKeys;
let nodeKeys = [ nodeBody, nodeBorder, backgroundImage, compound, pie ].filter(k => k != null).reduce(util.hashArrays, [
util.DEFAULT_HASH_SEED,
util.DEFAULT_HASH_SEED_ALT
]);
_p.nodeKey = util.combineHashesArray(nodeKeys);
_p.hasPie = pie != null && pie[0] !== util.DEFAULT_HASH_SEED && pie[1] !== util.DEFAULT_HASH_SEED_ALT;
}
return oldStyleKey !== _p.styleKey;
};
styfn.clearStyleHints = function(ele){
let _p = ele._private;
_p.styleCxtKey = '';
_p.styleKeys = {};
_p.styleKey = null;
_p.labelKey = null;
_p.labelStyleKey = null;
_p.sourceLabelKey = null;
_p.sourceLabelStyleKey = null;
_p.targetLabelKey = null;
_p.targetLabelStyleKey = null;
_p.nodeKey = null;
_p.hasPie = null;
};
// apply a property to the style (for internal use)
// returns whether application was successful
//
// now, this function flattens the property, and here's how:
//
// for parsedProp:{ bypass: true, deleteBypass: true }
// no property is generated, instead the bypass property in the
// element's style is replaced by what's pointed to by the `bypassed`
// field in the bypass property (i.e. restoring the property the
// bypass was overriding)
//
// for parsedProp:{ mapped: truthy }
// the generated flattenedProp:{ mapping: prop }
//
// for parsedProp:{ bypass: true }
// the generated flattenedProp:{ bypassed: parsedProp }
styfn.applyParsedProperty = function( ele, parsedProp ){
let self = this;
let prop = parsedProp;
let style = ele._private.style;
let flatProp;
let types = self.types;
let type = self.properties[ prop.name ].type;
let propIsBypass = prop.bypass;
let origProp = style[ prop.name ];
let origPropIsBypass = origProp && origProp.bypass;
let _p = ele._private;
let flatPropMapping = 'mapping';
let getVal = p => {
if( p == null ){
return null;
} else if( p.pfValue != null ){
return p.pfValue;
} else {
return p.value;
}
};
let checkTriggers = () => {
let fromVal = getVal(origProp);
let toVal = getVal(prop);
self.checkTriggers( ele, prop.name, fromVal, toVal );
};
if( prop && prop.name.substr(0, 3) === 'pie' ){
util.warn('The pie style properties are deprecated. Create charts using background images instead.');
}
// edge sanity checks to prevent the client from making serious mistakes
if(
parsedProp.name === 'curve-style'
&& ele.isEdge()
&& (
( // loops must be bundled beziers
parsedProp.value !== 'bezier'
&& ele.isLoop()
) || ( // edges connected to compound nodes can not be haystacks
parsedProp.value === 'haystack'
&& ( ele.source().isParent() || ele.target().isParent() )
)
)
){
prop = parsedProp = this.parse( parsedProp.name, 'bezier', propIsBypass );
}
if( prop.delete ){ // delete the property and use the default value on falsey value
style[ prop.name ] = undefined;
checkTriggers();
return true;
}
if( prop.deleteBypassed ){ // delete the property that the
if( !origProp ){
checkTriggers();
return true; // can't delete if no prop
} else if( origProp.bypass ){ // delete bypassed
origProp.bypassed = undefined;
checkTriggers();
return true;
} else {
return false; // we're unsuccessful deleting the bypassed
}
}
// check if we need to delete the current bypass
if( prop.deleteBypass ){ // then this property is just here to indicate we need to delete
if( !origProp ){
checkTriggers();
return true; // property is already not defined
} else if( origProp.bypass ){ // then replace the bypass property with the original
// because the bypassed property was already applied (and therefore parsed), we can just replace it (no reapplying necessary)
style[ prop.name ] = origProp.bypassed;
checkTriggers();
return true;
} else {
return false; // we're unsuccessful deleting the bypass
}
}
let printMappingErr = function(){
util.warn( 'Do not assign mappings to elements without corresponding data (i.e. ele `' + ele.id() + '` has no mapping for property `' + prop.name + '` with data field `' + prop.field + '`); try a `[' + prop.field + ']` selector to limit scope to elements with `' + prop.field + '` defined' );
};
// put the property in the style objects
switch( prop.mapped ){ // flatten the property if mapped
case types.mapData: {
// flatten the field (e.g. data.foo.bar)
let fields = prop.field.split( '.' );
let fieldVal = _p.data;
for( let i = 0; i < fields.length && fieldVal; i++ ){
let field = fields[ i ];
fieldVal = fieldVal[ field ];
}
if( fieldVal == null ){
printMappingErr();
return false;
}
let percent;
if( !is.number( fieldVal ) ){ // then don't apply and fall back on the existing style
util.warn('Do not use continuous mappers without specifying numeric data (i.e. `' + prop.field + ': ' + fieldVal + '` for `' + ele.id() + '` is non-numeric)');
return false;
} else {
let fieldWidth = prop.fieldMax - prop.fieldMin;
if( fieldWidth === 0 ){ // safety check -- not strictly necessary as no props of zero range should be passed here
percent = 0;
} else {
percent = (fieldVal - prop.fieldMin) / fieldWidth;
}
}
// make sure to bound percent value
if( percent < 0 ){
percent = 0;
} else if( percent > 1 ){
percent = 1;
}
if( type.color ){
let r1 = prop.valueMin[0];
let r2 = prop.valueMax[0];
let g1 = prop.valueMin[1];
let g2 = prop.valueMax[1];
let b1 = prop.valueMin[2];
let b2 = prop.valueMax[2];
let a1 = prop.valueMin[3] == null ? 1 : prop.valueMin[3];
let a2 = prop.valueMax[3] == null ? 1 : prop.valueMax[3];
let clr = [
Math.round( r1 + (r2 - r1) * percent ),
Math.round( g1 + (g2 - g1) * percent ),
Math.round( b1 + (b2 - b1) * percent ),
Math.round( a1 + (a2 - a1) * percent )
];
flatProp = { // colours are simple, so just create the flat property instead of expensive string parsing
bypass: prop.bypass, // we're a bypass if the mapping property is a bypass
name: prop.name,
value: clr,
strValue: 'rgb(' + clr[0] + ', ' + clr[1] + ', ' + clr[2] + ')'
};
} else if( type.number ){
let calcValue = prop.valueMin + (prop.valueMax - prop.valueMin) * percent;
flatProp = this.parse( prop.name, calcValue, prop.bypass, flatPropMapping );
} else {
return false; // can only map to colours and numbers
}
if( !flatProp ){ // if we can't flatten the property, then don't apply the property and fall back on the existing style
printMappingErr();
return false;
}
flatProp.mapping = prop; // keep a reference to the mapping
prop = flatProp; // the flattened (mapped) property is the one we want
break;
}
// direct mapping
case types.data: {
// flatten the field (e.g. data.foo.bar)
let fields = prop.field.split( '.' );
let fieldVal = _p.data;
for( let i = 0; i < fields.length && fieldVal; i++ ){
let field = fields[ i ];
fieldVal = fieldVal[ field ];
}
if( fieldVal != null ){
flatProp = this.parse( prop.name, fieldVal, prop.bypass, flatPropMapping );
}
if( !flatProp ){ // if we can't flatten the property, then don't apply and fall back on the existing style
printMappingErr();
return false;
}
flatProp.mapping = prop; // keep a reference to the mapping
prop = flatProp; // the flattened (mapped) property is the one we want
break;
}
case types.fn: {
let fn = prop.value;
let fnRetVal = prop.fnValue != null ? prop.fnValue : fn( ele ); // check for cached value before calling function
prop.prevFnValue = fnRetVal;
if( fnRetVal == null ){
util.warn('Custom function mappers may not return null (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is null)');
return false;
}
flatProp = this.parse( prop.name, fnRetVal, prop.bypass, flatPropMapping );
if( !flatProp ){
util.warn('Custom function mappers may not return invalid values for the property type (i.e. `' + prop.name + '` for ele `' + ele.id() + '` is invalid)');
return false;
}
flatProp.mapping = util.copy( prop ); // keep a reference to the mapping
prop = flatProp; // the flattened (mapped) property is the one we want
break;
}
case undefined:
break; // just set the property
default:
return false; // not a valid mapping
}
// if the property is a bypass property, then link the resultant property to the original one
if( propIsBypass ){
if( origPropIsBypass ){ // then this bypass overrides the existing one
prop.bypassed = origProp.bypassed; // steal bypassed prop from old bypass
} else { // then link the orig prop to the new bypass
prop.bypassed = origProp;
}
style[ prop.name ] = prop; // and set
} else { // prop is not bypass
if( origPropIsBypass ){ // then keep the orig prop (since it's a bypass) and link to the new prop
origProp.bypassed = prop;
} else { // then just replace the old prop with the new one
style[ prop.name ] = prop;
}
}
checkTriggers();
return true;
};
styfn.cleanElements = function( eles, keepBypasses ){
for( let i = 0; i < eles.length; i++ ){
let ele = eles[i];
this.clearStyleHints(ele);
ele.dirtyCompoundBoundsCache();
ele.dirtyBoundingBoxCache();
if( !keepBypasses ){
ele._private.style = {};
} else {
let style = ele._private.style;
let propNames = Object.keys(style);
for( let j = 0; j < propNames.length; j++ ){
let propName = propNames[j];
let eleProp = style[ propName ];
if( eleProp != null ){
if( eleProp.bypass ){
eleProp.bypassed = null;
} else {
style[ propName ] = null;
}
}
}
}
}
};
// updates the visual style for all elements (useful for manual style modification after init)
styfn.update = function(){
let cy = this._private.cy;
let eles = cy.mutableElements();
eles.updateStyle();
};
// diffProps : { name => { prev, next } }
styfn.updateTransitions = function( ele, diffProps ){
let self = this;
let _p = ele._private;
let props = ele.pstyle( 'transition-property' ).value;
let duration = ele.pstyle( 'transition-duration' ).pfValue;
let delay = ele.pstyle( 'transition-delay' ).pfValue;
if( props.length > 0 && duration > 0 ){
let style = {};
// build up the style to animate towards
let anyPrev = false;
for( let i = 0; i < props.length; i++ ){
let prop = props[ i ];
let styProp = ele.pstyle( prop );
let diffProp = diffProps[ prop ];
if( !diffProp ){ continue; }
let prevProp = diffProp.prev;
let fromProp = prevProp;
let toProp = diffProp.next != null ? diffProp.next : styProp;
let diff = false;
let initVal;
let initDt = 0.000001; // delta time % value for initVal (allows animating out of init zero opacity)
if( !fromProp ){ continue; }
// consider px values
if( is.number( fromProp.pfValue ) && is.number( toProp.pfValue ) ){
diff = toProp.pfValue - fromProp.pfValue; // nonzero is truthy
initVal = fromProp.pfValue + initDt * diff;
// consider numerical values
} else if( is.number( fromProp.value ) && is.number( toProp.value ) ){
diff = toProp.value - fromProp.value; // nonzero is truthy
initVal = fromProp.value + initDt * diff;
// consider colour values
} else if( is.array( fromProp.value ) && is.array( toProp.value ) ){
diff = fromProp.value[0] !== toProp.value[0]
|| fromProp.value[1] !== toProp.value[1]
|| fromProp.value[2] !== toProp.value[2]
;
initVal = fromProp.strValue;
}
// the previous value is good for an animation only if it's different
if( diff ){
style[ prop ] = toProp.strValue; // to val
this.applyBypass( ele, prop, initVal ); // from val
anyPrev = true;
}
} // end if props allow ani
// can't transition if there's nothing previous to transition from
if( !anyPrev ){ return; }
_p.transitioning = true;
( new Promise(function( resolve ){
if( delay > 0 ){
ele.delayAnimation( delay ).play().promise().then( resolve );
} else {
resolve();
}
}) ).then(function(){
return ele.animation( {
style: style,
duration: duration,
easing: ele.pstyle( 'transition-timing-function' ).value,
queue: false
} ).play().promise();
}).then(function(){
// if( !isBypass ){
self.removeBypasses( ele, props );
ele.emitAndNotify('style');
// }
_p.transitioning = false;
});
} else if( _p.transitioning ){
this.removeBypasses( ele, props );
ele.emitAndNotify('style');
_p.transitioning = false;
}
};
styfn.checkTrigger = function( ele, name, fromValue, toValue, getTrigger, onTrigger ){
let prop = this.properties[ name ];
let triggerCheck = getTrigger( prop );
if( triggerCheck != null && triggerCheck( fromValue, toValue ) ){
onTrigger(prop);
}
};
styfn.checkZOrderTrigger = function( ele, name, fromValue, toValue ){
this.checkTrigger( ele, name, fromValue, toValue, prop => prop.triggersZOrder, () => {
this._private.cy.notify('zorder', ele);
});
};
styfn.checkBoundsTrigger = function( ele, name, fromValue, toValue ){
this.checkTrigger( ele, name, fromValue, toValue, prop => prop.triggersBounds, prop => {
ele.dirtyCompoundBoundsCache();
ele.dirtyBoundingBoxCache();
// if the prop change makes the bb of pll bezier edges invalid,
// then dirty the pll edge bb cache as well
if( // only for beziers -- so performance of other edges isn't affected
prop.triggersBoundsOfParallelBeziers
&& ( ( name === 'curve-style' && (fromValue === 'bezier' || toValue === 'bezier') )
|| ( name === 'display' && (fromValue === 'none' || toValue === 'none') )
)
){
ele.parallelEdges().forEach(pllEdge => {
if( pllEdge.isBundledBezier() ){
pllEdge.dirtyBoundingBoxCache();
}
});
}
} );
};
styfn.checkTriggers = function( ele, name, fromValue, toValue ){
ele.dirtyStyleCache();
this.checkZOrderTrigger( ele, name, fromValue, toValue );
this.checkBoundsTrigger( ele, name, fromValue, toValue );
};
export default styfn;