|
import window from '../window'; |
|
import * as util from '../util'; |
|
import Collection from '../collection'; |
|
import * as is from '../is'; |
|
import Promise from '../promise'; |
|
|
|
import addRemove from './add-remove'; |
|
import animation from './animation'; |
|
import events from './events'; |
|
import exportFormat from './export'; |
|
import layout from './layout'; |
|
import notification from './notification'; |
|
import renderer from './renderer'; |
|
import search from './search'; |
|
import style from './style'; |
|
import viewport from './viewport'; |
|
import data from './data'; |
|
|
|
let Core = function( opts ){ |
|
let cy = this; |
|
|
|
opts = util.extend( {}, opts ); |
|
|
|
let container = opts.container; |
|
|
|
|
|
|
|
if( container && !is.htmlElement( container ) && is.htmlElement( container[0] ) ){ |
|
container = container[0]; |
|
} |
|
|
|
let reg = container ? container._cyreg : null; |
|
reg = reg || {}; |
|
|
|
if( reg && reg.cy ){ |
|
reg.cy.destroy(); |
|
|
|
reg = {}; |
|
} |
|
|
|
let readies = reg.readies = reg.readies || []; |
|
|
|
if( container ){ container._cyreg = reg; } |
|
reg.cy = cy; |
|
|
|
let head = window !== undefined && container !== undefined && !opts.headless; |
|
let options = opts; |
|
options.layout = util.extend( { name: head ? 'grid' : 'null' }, options.layout ); |
|
options.renderer = util.extend( { name: head ? 'canvas' : 'null' }, options.renderer ); |
|
|
|
let defVal = function( def, val, altVal ){ |
|
if( val !== undefined ){ |
|
return val; |
|
} else if( altVal !== undefined ){ |
|
return altVal; |
|
} else { |
|
return def; |
|
} |
|
}; |
|
|
|
let _p = this._private = { |
|
container: container, |
|
ready: false, |
|
options: options, |
|
elements: new Collection( this ), |
|
listeners: [], |
|
aniEles: new Collection( this ), |
|
data: options.data || {}, |
|
scratch: {}, |
|
layout: null, |
|
renderer: null, |
|
destroyed: false, |
|
notificationsEnabled: true, |
|
minZoom: 1e-50, |
|
maxZoom: 1e50, |
|
zoomingEnabled: defVal( true, options.zoomingEnabled ), |
|
userZoomingEnabled: defVal( true, options.userZoomingEnabled ), |
|
panningEnabled: defVal( true, options.panningEnabled ), |
|
userPanningEnabled: defVal( true, options.userPanningEnabled ), |
|
boxSelectionEnabled: defVal( true, options.boxSelectionEnabled ), |
|
autolock: defVal( false, options.autolock, options.autolockNodes ), |
|
autoungrabify: defVal( false, options.autoungrabify, options.autoungrabifyNodes ), |
|
autounselectify: defVal( false, options.autounselectify ), |
|
styleEnabled: options.styleEnabled === undefined ? head : options.styleEnabled, |
|
zoom: is.number( options.zoom ) ? options.zoom : 1, |
|
pan: { |
|
x: is.plainObject( options.pan ) && is.number( options.pan.x ) ? options.pan.x : 0, |
|
y: is.plainObject( options.pan ) && is.number( options.pan.y ) ? options.pan.y : 0 |
|
}, |
|
animation: { |
|
current: [], |
|
queue: [] |
|
}, |
|
hasCompoundNodes: false, |
|
multiClickDebounceTime: defVal(250, options.multiClickDebounceTime) |
|
}; |
|
|
|
this.createEmitter(); |
|
|
|
|
|
this.selectionType( options.selectionType ); |
|
|
|
|
|
this.zoomRange({ min: options.minZoom, max: options.maxZoom }); |
|
|
|
let loadExtData = function( extData, next ){ |
|
let anyIsPromise = extData.some( is.promise ); |
|
|
|
if( anyIsPromise ){ |
|
return Promise.all( extData ).then( next ); |
|
} else { |
|
next( extData ); |
|
} |
|
}; |
|
|
|
|
|
if( _p.styleEnabled ){ |
|
cy.setStyle([]); |
|
} |
|
|
|
|
|
let rendererOptions = util.assign({}, options, options.renderer); |
|
cy.initRenderer( rendererOptions ); |
|
|
|
let setElesAndLayout = function( elements, onload, ondone ){ |
|
cy.notifications( false ); |
|
|
|
|
|
let oldEles = cy.mutableElements(); |
|
if( oldEles.length > 0 ){ |
|
oldEles.remove(); |
|
} |
|
|
|
if( elements != null ){ |
|
if( is.plainObject( elements ) || is.array( elements ) ){ |
|
cy.add( elements ); |
|
} |
|
} |
|
|
|
cy.one( 'layoutready', function( e ){ |
|
cy.notifications( true ); |
|
cy.emit( e ); |
|
|
|
cy.one( 'load', onload ); |
|
cy.emitAndNotify( 'load' ); |
|
} ).one( 'layoutstop', function(){ |
|
cy.one( 'done', ondone ); |
|
cy.emit( 'done' ); |
|
} ); |
|
|
|
let layoutOpts = util.extend( {}, cy._private.options.layout ); |
|
layoutOpts.eles = cy.elements(); |
|
|
|
cy.layout( layoutOpts ).run(); |
|
}; |
|
|
|
loadExtData([ options.style, options.elements ], function( thens ){ |
|
let initStyle = thens[0]; |
|
let initEles = thens[1]; |
|
|
|
|
|
if( _p.styleEnabled ){ |
|
cy.style().append( initStyle ); |
|
} |
|
|
|
|
|
setElesAndLayout( initEles, function(){ |
|
cy.startAnimationLoop(); |
|
_p.ready = true; |
|
|
|
|
|
if( is.fn( options.ready ) ){ |
|
cy.on( 'ready', options.ready ); |
|
} |
|
|
|
|
|
for( let i = 0; i < readies.length; i++ ){ |
|
let fn = readies[ i ]; |
|
cy.on( 'ready', fn ); |
|
} |
|
if( reg ){ reg.readies = []; } |
|
|
|
cy.emit( 'ready' ); |
|
}, options.done ); |
|
|
|
} ); |
|
}; |
|
|
|
let corefn = Core.prototype; |
|
|
|
util.extend( corefn, { |
|
instanceString: function(){ |
|
return 'core'; |
|
}, |
|
|
|
isReady: function(){ |
|
return this._private.ready; |
|
}, |
|
|
|
destroyed: function(){ |
|
return this._private.destroyed; |
|
}, |
|
|
|
ready: function( fn ){ |
|
if( this.isReady() ){ |
|
this.emitter().emit( 'ready', [], fn ); |
|
} else { |
|
this.on( 'ready', fn ); |
|
} |
|
|
|
return this; |
|
}, |
|
|
|
destroy: function(){ |
|
let cy = this; |
|
if( cy.destroyed() ) return; |
|
|
|
cy.stopAnimationLoop(); |
|
|
|
cy.destroyRenderer(); |
|
|
|
this.emit( 'destroy' ); |
|
|
|
cy._private.destroyed = true; |
|
|
|
return cy; |
|
}, |
|
|
|
hasElementWithId: function( id ){ |
|
return this._private.elements.hasElementWithId( id ); |
|
}, |
|
|
|
getElementById: function( id ){ |
|
return this._private.elements.getElementById( id ); |
|
}, |
|
|
|
hasCompoundNodes: function(){ |
|
return this._private.hasCompoundNodes; |
|
}, |
|
|
|
headless: function(){ |
|
return this._private.renderer.isHeadless(); |
|
}, |
|
|
|
styleEnabled: function(){ |
|
return this._private.styleEnabled; |
|
}, |
|
|
|
addToPool: function( eles ){ |
|
this._private.elements.merge( eles ); |
|
|
|
return this; |
|
}, |
|
|
|
removeFromPool: function( eles ){ |
|
this._private.elements.unmerge( eles ); |
|
|
|
return this; |
|
}, |
|
|
|
container: function(){ |
|
return this._private.container || null; |
|
}, |
|
|
|
window: function() { |
|
let container = this._private.container; |
|
if (container == null) return window; |
|
|
|
let ownerDocument = this._private.container.ownerDocument; |
|
|
|
if (ownerDocument === undefined || ownerDocument == null) { |
|
return window; |
|
} |
|
|
|
return ownerDocument.defaultView || window; |
|
}, |
|
|
|
mount: function( container ){ |
|
if( container == null ){ return; } |
|
|
|
let cy = this; |
|
let _p = cy._private; |
|
let options = _p.options; |
|
|
|
if( !is.htmlElement( container ) && is.htmlElement( container[0] ) ){ |
|
container = container[0]; |
|
} |
|
|
|
cy.stopAnimationLoop(); |
|
|
|
cy.destroyRenderer(); |
|
|
|
_p.container = container; |
|
_p.styleEnabled = true; |
|
|
|
cy.invalidateSize(); |
|
|
|
cy.initRenderer( util.assign({}, options, options.renderer, { |
|
|
|
name: options.renderer.name === 'null' ? 'canvas' : options.renderer.name |
|
}) ); |
|
|
|
cy.startAnimationLoop(); |
|
|
|
cy.style( options.style ); |
|
|
|
cy.emit( 'mount' ); |
|
|
|
return cy; |
|
}, |
|
|
|
unmount: function(){ |
|
let cy = this; |
|
|
|
cy.stopAnimationLoop(); |
|
|
|
cy.destroyRenderer(); |
|
|
|
cy.initRenderer( { name: 'null' } ); |
|
|
|
cy.emit( 'unmount' ); |
|
|
|
return cy; |
|
}, |
|
|
|
options: function(){ |
|
return util.copy( this._private.options ); |
|
}, |
|
|
|
json: function( obj ){ |
|
let cy = this; |
|
let _p = cy._private; |
|
let eles = cy.mutableElements(); |
|
let getFreshRef = ele => cy.getElementById(ele.id()); |
|
|
|
if( is.plainObject( obj ) ){ |
|
|
|
cy.startBatch(); |
|
|
|
if( obj.elements ){ |
|
let idInJson = {}; |
|
|
|
let updateEles = function( jsons, gr ){ |
|
let toAdd = []; |
|
let toMod = []; |
|
|
|
for( let i = 0; i < jsons.length; i++ ){ |
|
let json = jsons[ i ]; |
|
|
|
if( !json.data.id ){ |
|
util.warn( 'cy.json() cannot handle elements without an ID attribute' ); |
|
continue; |
|
} |
|
|
|
let id = '' + json.data.id; |
|
let ele = cy.getElementById( id ); |
|
|
|
idInJson[ id ] = true; |
|
|
|
if( ele.length !== 0 ){ |
|
toMod.push({ ele, json }); |
|
} else { |
|
if( gr ){ |
|
json.group = gr; |
|
|
|
toAdd.push( json ); |
|
} else { |
|
toAdd.push( json ); |
|
} |
|
} |
|
} |
|
|
|
cy.add( toAdd ); |
|
|
|
for( let i = 0; i < toMod.length; i++ ){ |
|
let { ele, json } = toMod[i]; |
|
|
|
ele.json(json); |
|
} |
|
}; |
|
|
|
if( is.array( obj.elements ) ){ |
|
updateEles( obj.elements ); |
|
|
|
} else { |
|
let grs = [ 'nodes', 'edges' ]; |
|
for( let i = 0; i < grs.length; i++ ){ |
|
let gr = grs[ i ]; |
|
let elements = obj.elements[ gr ]; |
|
|
|
if( is.array( elements ) ){ |
|
updateEles( elements, gr ); |
|
} |
|
} |
|
} |
|
|
|
let parentsToRemove = cy.collection(); |
|
|
|
(eles |
|
.filter(ele => !idInJson[ ele.id() ]) |
|
.forEach(ele => { |
|
if ( ele.isParent() ) { |
|
parentsToRemove.merge(ele); |
|
} else { |
|
ele.remove(); |
|
} |
|
}) |
|
); |
|
|
|
|
|
parentsToRemove.forEach(ele => ele.children().move({ parent: null })); |
|
|
|
|
|
parentsToRemove.forEach(ele => getFreshRef(ele).remove()); |
|
} |
|
|
|
if( obj.style ){ |
|
cy.style( obj.style ); |
|
} |
|
|
|
if( obj.zoom != null && obj.zoom !== _p.zoom ){ |
|
cy.zoom( obj.zoom ); |
|
} |
|
|
|
if( obj.pan ){ |
|
if( obj.pan.x !== _p.pan.x || obj.pan.y !== _p.pan.y ){ |
|
cy.pan( obj.pan ); |
|
} |
|
} |
|
|
|
if( obj.data ){ |
|
cy.data( obj.data ); |
|
} |
|
|
|
let fields = [ |
|
'minZoom', 'maxZoom', 'zoomingEnabled', 'userZoomingEnabled', |
|
'panningEnabled', 'userPanningEnabled', |
|
'boxSelectionEnabled', |
|
'autolock', 'autoungrabify', 'autounselectify', |
|
'multiClickDebounceTime' |
|
]; |
|
|
|
for( let i = 0; i < fields.length; i++ ){ |
|
let f = fields[ i ]; |
|
|
|
if( obj[ f ] != null ){ |
|
cy[ f ]( obj[ f ] ); |
|
} |
|
} |
|
|
|
cy.endBatch(); |
|
|
|
return this; |
|
} else { |
|
let flat = !!obj; |
|
let json = {}; |
|
|
|
if( flat ){ |
|
json.elements = this.elements().map( ele => ele.json() ); |
|
} else { |
|
json.elements = {}; |
|
|
|
eles.forEach( function( ele ){ |
|
let group = ele.group(); |
|
|
|
if( !json.elements[ group ] ){ |
|
json.elements[ group ] = []; |
|
} |
|
|
|
json.elements[ group ].push( ele.json() ); |
|
} ); |
|
} |
|
|
|
if( this._private.styleEnabled ){ |
|
json.style = cy.style().json(); |
|
} |
|
|
|
json.data = util.copy( cy.data() ); |
|
|
|
let options = _p.options; |
|
|
|
json.zoomingEnabled = _p.zoomingEnabled; |
|
json.userZoomingEnabled = _p.userZoomingEnabled; |
|
json.zoom = _p.zoom; |
|
json.minZoom = _p.minZoom; |
|
json.maxZoom = _p.maxZoom; |
|
json.panningEnabled = _p.panningEnabled; |
|
json.userPanningEnabled = _p.userPanningEnabled; |
|
json.pan = util.copy( _p.pan ); |
|
json.boxSelectionEnabled = _p.boxSelectionEnabled; |
|
json.renderer = util.copy( options.renderer ); |
|
json.hideEdgesOnViewport = options.hideEdgesOnViewport; |
|
json.textureOnViewport = options.textureOnViewport; |
|
json.wheelSensitivity = options.wheelSensitivity; |
|
json.motionBlur = options.motionBlur; |
|
json.multiClickDebounceTime = options.multiClickDebounceTime; |
|
|
|
return json; |
|
} |
|
} |
|
|
|
} ); |
|
|
|
corefn.$id = corefn.getElementById; |
|
|
|
[ |
|
addRemove, |
|
animation, |
|
events, |
|
exportFormat, |
|
layout, |
|
notification, |
|
renderer, |
|
search, |
|
style, |
|
viewport, |
|
data |
|
].forEach( function( props ){ |
|
util.extend( corefn, props ); |
|
} ); |
|
|
|
export default Core; |
|
|