cytoscape
Version:
Graph theory (a.k.a. network) library for analysis and visualisation
247 lines (191 loc) • 6.71 kB
JavaScript
import * as util from'./util';
import define from './define';
import Collection from './collection';
import Core from './core';
import incExts from './extensions';
import * as is from './is';
import Emitter from './emitter';
// registered extensions to cytoscape, indexed by name
let extensions = {};
// registered modules for extensions, indexed by name
let modules = {};
function setExtension( type, name, registrant ){
let ext = registrant;
let overrideErr = function( field ){
util.warn( 'Can not register `' + name + '` for `' + type + '` since `' + field + '` already exists in the prototype and can not be overridden' );
};
if( type === 'core' ){
if( Core.prototype[ name ] ){
return overrideErr( name );
} else {
Core.prototype[ name ] = registrant;
}
} else if( type === 'collection' ){
if( Collection.prototype[ name ] ){
return overrideErr( name );
} else {
Collection.prototype[ name ] = registrant;
}
} else if( type === 'layout' ){
// fill in missing layout functions in the prototype
let Layout = function( options ){
this.options = options;
registrant.call( this, options );
// make sure layout has _private for use w/ std apis like .on()
if( !is.plainObject( this._private ) ){
this._private = {};
}
this._private.cy = options.cy;
this._private.listeners = [];
this.createEmitter();
};
let layoutProto = Layout.prototype = Object.create( registrant.prototype );
let optLayoutFns = [];
for( let i = 0; i < optLayoutFns.length; i++ ){
let fnName = optLayoutFns[ i ];
layoutProto[ fnName ] = layoutProto[ fnName ] || function(){ return this; };
}
// either .start() or .run() is defined, so autogen the other
if( layoutProto.start && !layoutProto.run ){
layoutProto.run = function(){ this.start(); return this; };
} else if( !layoutProto.start && layoutProto.run ){
layoutProto.start = function(){ this.run(); return this; };
}
let regStop = registrant.prototype.stop;
layoutProto.stop = function(){
let opts = this.options;
if( opts && opts.animate ){
let anis = this.animations;
if( anis ){
for( let i = 0; i < anis.length; i++ ){
anis[ i ].stop();
}
}
}
if( regStop ){
regStop.call( this );
} else {
this.emit( 'layoutstop' );
}
return this;
};
if( !layoutProto.destroy ){
layoutProto.destroy = function(){
return this;
};
}
layoutProto.cy = function(){
return this._private.cy;
};
let getCy = layout => layout._private.cy;
let emitterOpts = {
addEventFields: function( layout, evt ){
evt.layout = layout;
evt.cy = getCy(layout);
evt.target = layout;
},
bubble: function(){ return true; },
parent: function( layout ){ return getCy(layout); }
};
util.assign( layoutProto, {
createEmitter: function(){
this._private.emitter = new Emitter( emitterOpts, this );
return this;
},
emitter: function(){ return this._private.emitter; },
on: function( evt, cb ){ this.emitter().on( evt, cb ); return this; },
one: function( evt, cb ){ this.emitter().one( evt, cb ); return this; },
once: function( evt, cb ){ this.emitter().one( evt, cb ); return this; },
removeListener: function( evt, cb ){ this.emitter().removeListener( evt, cb ); return this; },
removeAllListeners: function(){ this.emitter().removeAllListeners(); return this; },
emit: function( evt, params ){ this.emitter().emit( evt, params ); return this; }
} );
define.eventAliasesOn( layoutProto );
ext = Layout; // replace with our wrapped layout
} else if( type === 'renderer' && name !== 'null' && name !== 'base' ){
// user registered renderers inherit from base
let BaseRenderer = getExtension( 'renderer', 'base' );
let bProto = BaseRenderer.prototype;
let RegistrantRenderer = registrant;
let rProto = registrant.prototype;
let Renderer = function(){
BaseRenderer.apply( this, arguments );
RegistrantRenderer.apply( this, arguments );
};
let proto = Renderer.prototype;
for( let pName in bProto ){
let pVal = bProto[ pName ];
let existsInR = rProto[ pName ] != null;
if( existsInR ){
return overrideErr( pName );
}
proto[ pName ] = pVal; // take impl from base
}
for( let pName in rProto ){
proto[ pName ] = rProto[ pName ]; // take impl from registrant
}
bProto.clientFunctions.forEach( function( name ){
proto[ name ] = proto[ name ] || function(){
util.error( 'Renderer does not implement `renderer.' + name + '()` on its prototype' );
};
} );
ext = Renderer;
} else if (type === '__proto__' || type === 'constructor' || type === 'prototype'){
// to avoid potential prototype pollution
return util.error( type + ' is an illegal type to be registered, possibly lead to prototype pollutions' );
}
return util.setMap( {
map: extensions,
keys: [ type, name ],
value: ext
} );
}
function getExtension( type, name ){
return util.getMap( {
map: extensions,
keys: [ type, name ]
} );
}
function setModule( type, name, moduleType, moduleName, registrant ){
return util.setMap( {
map: modules,
keys: [ type, name, moduleType, moduleName ],
value: registrant
} );
}
function getModule( type, name, moduleType, moduleName ){
return util.getMap( {
map: modules,
keys: [ type, name, moduleType, moduleName ]
} );
}
let extension = function(){
// e.g. extension('renderer', 'svg')
if( arguments.length === 2 ){
return getExtension.apply( null, arguments );
}
// e.g. extension('renderer', 'svg', { ... })
else if( arguments.length === 3 ){
return setExtension.apply( null, arguments );
}
// e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse')
else if( arguments.length === 4 ){
return getModule.apply( null, arguments );
}
// e.g. extension('renderer', 'svg', 'nodeShape', 'ellipse', { ... })
else if( arguments.length === 5 ){
return setModule.apply( null, arguments );
}
else {
util.error( 'Invalid extension access syntax' );
}
};
// allows a core instance to access extensions internally
Core.prototype.extension = extension;
// included extensions
incExts.forEach( function( group ){
group.extensions.forEach( function( ext ){
setExtension( group.type, ext.name, ext.impl );
} );
} );
export default extension;