cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
606 lines (465 loc) • 13.3 kB
JavaScript
import * as is from '../is';
import window from '../window';
import * as math from '../math';
let defaultSelectionType = 'single';
let corefn = ({
autolock: function( bool ){
if( bool !== undefined ){
this._private.autolock = bool ? true : false;
} else {
return this._private.autolock;
}
return this; // chaining
},
autoungrabify: function( bool ){
if( bool !== undefined ){
this._private.autoungrabify = bool ? true : false;
} else {
return this._private.autoungrabify;
}
return this; // chaining
},
autounselectify: function( bool ){
if( bool !== undefined ){
this._private.autounselectify = bool ? true : false;
} else {
return this._private.autounselectify;
}
return this; // chaining
},
selectionType: function( selType ){
let _p = this._private;
if( _p.selectionType == null ){
_p.selectionType = defaultSelectionType;
}
if( selType !== undefined ){
if( selType === 'additive' || selType === 'single' ){
_p.selectionType = selType;
}
} else {
return _p.selectionType;
}
return this;
},
panningEnabled: function( bool ){
if( bool !== undefined ){
this._private.panningEnabled = bool ? true : false;
} else {
return this._private.panningEnabled;
}
return this; // chaining
},
userPanningEnabled: function( bool ){
if( bool !== undefined ){
this._private.userPanningEnabled = bool ? true : false;
} else {
return this._private.userPanningEnabled;
}
return this; // chaining
},
zoomingEnabled: function( bool ){
if( bool !== undefined ){
this._private.zoomingEnabled = bool ? true : false;
} else {
return this._private.zoomingEnabled;
}
return this; // chaining
},
userZoomingEnabled: function( bool ){
if( bool !== undefined ){
this._private.userZoomingEnabled = bool ? true : false;
} else {
return this._private.userZoomingEnabled;
}
return this; // chaining
},
boxSelectionEnabled: function( bool ){
if( bool !== undefined ){
this._private.boxSelectionEnabled = bool ? true : false;
} else {
return this._private.boxSelectionEnabled;
}
return this; // chaining
},
pan: function(){
let args = arguments;
let pan = this._private.pan;
let dim, val, dims, x, y;
switch( args.length ){
case 0: // .pan()
return pan;
case 1:
if( is.string( args[0] ) ){ // .pan('x')
dim = args[0];
return pan[ dim ];
} else if( is.plainObject( args[0] ) ){ // .pan({ x: 0, y: 100 })
if( !this._private.panningEnabled ){
return this;
}
dims = args[0];
x = dims.x;
y = dims.y;
if( is.number( x ) ){
pan.x = x;
}
if( is.number( y ) ){
pan.y = y;
}
this.emit( 'pan viewport' );
}
break;
case 2: // .pan('x', 100)
if( !this._private.panningEnabled ){
return this;
}
dim = args[0];
val = args[1];
if( (dim === 'x' || dim === 'y') && is.number( val ) ){
pan[ dim ] = val;
}
this.emit( 'pan viewport' );
break;
default:
break; // invalid
}
this.notify('viewport');
return this; // chaining
},
panBy: function( arg0, arg1 ){
let args = arguments;
let pan = this._private.pan;
let dim, val, dims, x, y;
if( !this._private.panningEnabled ){
return this;
}
switch( args.length ){
case 1:
if( is.plainObject( arg0 ) ){ // .panBy({ x: 0, y: 100 })
dims = args[0];
x = dims.x;
y = dims.y;
if( is.number( x ) ){
pan.x += x;
}
if( is.number( y ) ){
pan.y += y;
}
this.emit( 'pan viewport' );
}
break;
case 2: // .panBy('x', 100)
dim = arg0;
val = arg1;
if( (dim === 'x' || dim === 'y') && is.number( val ) ){
pan[ dim ] += val;
}
this.emit( 'pan viewport' );
break;
default:
break; // invalid
}
this.notify('viewport');
return this; // chaining
},
fit: function( elements, padding ){
let viewportState = this.getFitViewport( elements, padding );
if( viewportState ){
let _p = this._private;
_p.zoom = viewportState.zoom;
_p.pan = viewportState.pan;
this.emit( 'pan zoom viewport' );
this.notify('viewport');
}
return this; // chaining
},
getFitViewport: function( elements, padding ){
if( is.number( elements ) && padding === undefined ){ // elements is optional
padding = elements;
elements = undefined;
}
if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
return;
}
let bb;
if( is.string( elements ) ){
let sel = elements;
elements = this.$( sel );
} else if( is.boundingBox( elements ) ){ // assume bb
let bbe = elements;
bb = {
x1: bbe.x1,
y1: bbe.y1,
x2: bbe.x2,
y2: bbe.y2
};
bb.w = bb.x2 - bb.x1;
bb.h = bb.y2 - bb.y1;
} else if( !is.elementOrCollection( elements ) ){
elements = this.mutableElements();
}
if( is.elementOrCollection( elements ) && elements.empty() ){ return; } // can't fit to nothing
bb = bb || elements.boundingBox();
let w = this.width();
let h = this.height();
let zoom;
padding = is.number( padding ) ? padding : 0;
if( !isNaN( w ) && !isNaN( h ) && w > 0 && h > 0 && !isNaN( bb.w ) && !isNaN( bb.h ) && bb.w > 0 && bb.h > 0 ){
zoom = Math.min( (w - 2 * padding) / bb.w, (h - 2 * padding) / bb.h );
// crop zoom
zoom = zoom > this._private.maxZoom ? this._private.maxZoom : zoom;
zoom = zoom < this._private.minZoom ? this._private.minZoom : zoom;
let pan = { // now pan to middle
x: (w - zoom * ( bb.x1 + bb.x2 )) / 2,
y: (h - zoom * ( bb.y1 + bb.y2 )) / 2
};
return {
zoom: zoom,
pan: pan
};
}
return;
},
zoomRange: function( min, max ){
let _p = this._private;
if( max == null ){
let opts = min;
min = opts.min;
max = opts.max;
}
if( is.number( min ) && is.number( max ) && min <= max ){
_p.minZoom = min;
_p.maxZoom = max;
} else if( is.number( min ) && max === undefined && min <= _p.maxZoom ){
_p.minZoom = min;
} else if( is.number( max ) && min === undefined && max >= _p.minZoom ){
_p.maxZoom = max;
}
return this;
},
minZoom: function( zoom ){
if( zoom === undefined ){
return this._private.minZoom;
} else {
return this.zoomRange({ min: zoom });
}
},
maxZoom: function( zoom ){
if( zoom === undefined ){
return this._private.maxZoom;
} else {
return this.zoomRange({ max: zoom });
}
},
getZoomedViewport: function( params ){
let _p = this._private;
let currentPan = _p.pan;
let currentZoom = _p.zoom;
let pos; // in rendered px
let zoom;
let bail = false;
if( !_p.zoomingEnabled ){ // zooming disabled
bail = true;
}
if( is.number( params ) ){ // then set the zoom
zoom = params;
} else if( is.plainObject( params ) ){ // then zoom about a point
zoom = params.level;
if( params.position != null ){
pos = math.modelToRenderedPosition( params.position, currentZoom, currentPan );
} else if( params.renderedPosition != null ){
pos = params.renderedPosition;
}
if( pos != null && !_p.panningEnabled ){ // panning disabled
bail = true;
}
}
// crop zoom
zoom = zoom > _p.maxZoom ? _p.maxZoom : zoom;
zoom = zoom < _p.minZoom ? _p.minZoom : zoom;
// can't zoom with invalid params
if( bail || !is.number( zoom ) || zoom === currentZoom || ( pos != null && (!is.number( pos.x ) || !is.number( pos.y )) ) ){
return null;
}
if( pos != null ){ // set zoom about position
let pan1 = currentPan;
let zoom1 = currentZoom;
let zoom2 = zoom;
let pan2 = {
x: -zoom2 / zoom1 * (pos.x - pan1.x) + pos.x,
y: -zoom2 / zoom1 * (pos.y - pan1.y) + pos.y
};
return {
zoomed: true,
panned: true,
zoom: zoom2,
pan: pan2
};
} else { // just set the zoom
return {
zoomed: true,
panned: false,
zoom: zoom,
pan: currentPan
};
}
},
zoom: function( params ){
if( params === undefined ){ // get
return this._private.zoom;
} else { // set
let vp = this.getZoomedViewport( params );
let _p = this._private;
if( vp == null || !vp.zoomed ){ return this; }
_p.zoom = vp.zoom;
if( vp.panned ){
_p.pan.x = vp.pan.x;
_p.pan.y = vp.pan.y;
}
this.emit( 'zoom' + ( vp.panned ? ' pan' : '' ) + ' viewport' );
this.notify('viewport');
return this; // chaining
}
},
viewport: function( opts ){
let _p = this._private;
let zoomDefd = true;
let panDefd = true;
let events = []; // to trigger
let zoomFailed = false;
let panFailed = false;
if( !opts ){ return this; }
if( !is.number( opts.zoom ) ){ zoomDefd = false; }
if( !is.plainObject( opts.pan ) ){ panDefd = false; }
if( !zoomDefd && !panDefd ){ return this; }
if( zoomDefd ){
let z = opts.zoom;
if( z < _p.minZoom || z > _p.maxZoom || !_p.zoomingEnabled ){
zoomFailed = true;
} else {
_p.zoom = z;
events.push( 'zoom' );
}
}
if( panDefd && (!zoomFailed || !opts.cancelOnFailedZoom) && _p.panningEnabled ){
let p = opts.pan;
if( is.number( p.x ) ){
_p.pan.x = p.x;
panFailed = false;
}
if( is.number( p.y ) ){
_p.pan.y = p.y;
panFailed = false;
}
if( !panFailed ){
events.push( 'pan' );
}
}
if( events.length > 0 ){
events.push( 'viewport' );
this.emit( events.join( ' ' ) );
this.notify('viewport');
}
return this; // chaining
},
center: function( elements ){
let pan = this.getCenterPan( elements );
if( pan ){
this._private.pan = pan;
this.emit( 'pan viewport' );
this.notify('viewport');
}
return this; // chaining
},
getCenterPan: function( elements, zoom ){
if( !this._private.panningEnabled ){
return;
}
if( is.string( elements ) ){
let selector = elements;
elements = this.mutableElements().filter( selector );
} else if( !is.elementOrCollection( elements ) ){
elements = this.mutableElements();
}
if( elements.length === 0 ){ return; } // can't centre pan to nothing
let bb = elements.boundingBox();
let w = this.width();
let h = this.height();
zoom = zoom === undefined ? this._private.zoom : zoom;
let pan = { // middle
x: (w - zoom * ( bb.x1 + bb.x2 )) / 2,
y: (h - zoom * ( bb.y1 + bb.y2 )) / 2
};
return pan;
},
reset: function(){
if( !this._private.panningEnabled || !this._private.zoomingEnabled ){
return this;
}
this.viewport( {
pan: { x: 0, y: 0 },
zoom: 1
} );
return this; // chaining
},
invalidateSize: function(){
this._private.sizeCache = null;
},
size: function(){
let _p = this._private;
let container = _p.container;
return ( _p.sizeCache = _p.sizeCache || ( container ? (function(){
let style = window.getComputedStyle( container );
let val = function( name ){ return parseFloat( style.getPropertyValue( name ) ); };
return {
width: container.clientWidth - val('padding-left') - val('padding-right'),
height: container.clientHeight - val('padding-top') - val('padding-bottom')
};
})() : { // fallback if no container (not 0 b/c can be used for dividing etc)
width: 1,
height: 1
} ) );
},
width: function(){
return this.size().width;
},
height: function(){
return this.size().height;
},
extent: function(){
let pan = this._private.pan;
let zoom = this._private.zoom;
let rb = this.renderedExtent();
let b = {
x1: ( rb.x1 - pan.x ) / zoom,
x2: ( rb.x2 - pan.x ) / zoom,
y1: ( rb.y1 - pan.y ) / zoom,
y2: ( rb.y2 - pan.y ) / zoom
};
b.w = b.x2 - b.x1;
b.h = b.y2 - b.y1;
return b;
},
renderedExtent: function(){
let width = this.width();
let height = this.height();
return {
x1: 0,
y1: 0,
x2: width,
y2: height,
w: width,
h: height
};
},
multiClickDebounceTime: function ( int ){
if( int ) (this._private.multiClickDebounceTime = int);
else return this._private.multiClickDebounceTime;
return this; // chaining
}
});
// aliases
corefn.centre = corefn.center;
// backwards compatibility
corefn.autolockNodes = corefn.autolock;
corefn.autoungrabifyNodes = corefn.autoungrabify;
export default corefn;