ontoref/assets/vendor/cytoscape-navigator.js

961 lines
28 KiB
JavaScript
Raw Permalink Normal View History

2026-03-29 00:19:56 +00:00
;(function(){ 'use strict';
var defaults = {
container: false // can be a HTML or jQuery element or jQuery selector
, viewLiveFramerate: 0 // set false to update graph pan only on drag end; set 0 to do it instantly; set a number (frames per second) to update not more than N times per second
, dblClickDelay: 200 // milliseconds
, removeCustomContainer: true // destroy the container specified by user on plugin destroy
, rerenderDelay: 500 // ms to throttle rerender updates to the panzoom for performance
};
var debounce = (function(){
/**
* lodash 3.1.1 (Custom Build) <https://lodash.com/>
* Build: `lodash modern modularize exports="npm" -o ./`
* Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
* Available under MIT license <https://lodash.com/license>
*/
/** Used as the `TypeError` message for "Functions" methods. */
var FUNC_ERROR_TEXT = 'Expected a function';
/* Native method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max,
nativeNow = Date.now;
/**
* Gets the number of milliseconds that have elapsed since the Unix epoch
* (1 January 1970 00:00:00 UTC).
*
* @static
* @memberOf _
* @category Date
* @example
*
* _.defer(function(stamp) {
* console.log(_.now() - stamp);
* }, _.now());
* // => logs the number of milliseconds it took for the deferred function to be invoked
*/
var now = nativeNow || function() {
return new Date().getTime();
};
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked. The debounced function comes with a `cancel` method to cancel
* delayed invocations. Provide an options object to indicate that `func`
* should be invoked on the leading and/or trailing edge of the `wait` timeout.
* Subsequent calls to the debounced function return the result of the last
* `func` invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
* on the trailing edge of the timeout only if the the debounced function is
* invoked more than once during the `wait` timeout.
*
* See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
* for details over the differences between `_.debounce` and `_.throttle`.
*
* @static
* @memberOf _
* @category Function
* @param {Function} func The function to debounce.
* @param {number} [wait=0] The number of milliseconds to delay.
* @param {Object} [options] The options object.
* @param {boolean} [options.leading=false] Specify invoking on the leading
* edge of the timeout.
* @param {number} [options.maxWait] The maximum time `func` is allowed to be
* delayed before it's invoked.
* @param {boolean} [options.trailing=true] Specify invoking on the trailing
* edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // avoid costly calculations while the window size is in flux
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
*
* // invoke `sendMail` when the click event is fired, debouncing subsequent calls
* jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
* 'leading': true,
* 'trailing': false
* }));
*
* // ensure `batchLog` is invoked once after 1 second of debounced calls
* var source = new EventSource('/stream');
* jQuery(source).on('message', _.debounce(batchLog, 250, {
* 'maxWait': 1000
* }));
*
* // cancel a debounced call
* var todoChanges = _.debounce(batchLog, 1000);
* Object.observe(models.todo, todoChanges);
*
* Object.observe(models, function(changes) {
* if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
* todoChanges.cancel();
* }
* }, ['delete']);
*
* // ...at some point `models.todo` is changed
* models.todo.completed = true;
*
* // ...before 1 second has passed `models.todo` is deleted
* // which cancels the debounced `todoChanges` call
* delete models.todo;
*/
function debounce(func, wait, options) {
var args,
maxTimeoutId,
result,
stamp,
thisArg,
timeoutId,
trailingCall,
lastCalled = 0,
maxWait = false,
trailing = true;
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
wait = wait < 0 ? 0 : (+wait || 0);
if (options === true) {
var leading = true;
trailing = false;
} else if (isObject(options)) {
leading = !!options.leading;
maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
function cancel() {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (maxTimeoutId) {
clearTimeout(maxTimeoutId);
}
lastCalled = 0;
maxTimeoutId = timeoutId = trailingCall = undefined;
}
function complete(isCalled, id) {
if (id) {
clearTimeout(id);
}
maxTimeoutId = timeoutId = trailingCall = undefined;
if (isCalled) {
lastCalled = now();
result = func.apply(thisArg, args);
if (!timeoutId && !maxTimeoutId) {
args = thisArg = undefined;
}
}
}
function delayed() {
var remaining = wait - (now() - stamp);
if (remaining <= 0 || remaining > wait) {
complete(trailingCall, maxTimeoutId);
} else {
timeoutId = setTimeout(delayed, remaining);
}
}
function maxDelayed() {
complete(trailing, timeoutId);
}
function debounced() {
args = arguments;
stamp = now();
thisArg = this;
trailingCall = trailing && (timeoutId || !leading);
if (maxWait === false) {
var leadingCall = leading && !timeoutId;
} else {
if (!maxTimeoutId && !leading) {
lastCalled = stamp;
}
var remaining = maxWait - (stamp - lastCalled),
isCalled = remaining <= 0 || remaining > maxWait;
if (isCalled) {
if (maxTimeoutId) {
maxTimeoutId = clearTimeout(maxTimeoutId);
}
lastCalled = stamp;
result = func.apply(thisArg, args);
}
else if (!maxTimeoutId) {
maxTimeoutId = setTimeout(maxDelayed, remaining);
}
}
if (isCalled && timeoutId) {
timeoutId = clearTimeout(timeoutId);
}
else if (!timeoutId && wait !== maxWait) {
timeoutId = setTimeout(delayed, wait);
}
if (leadingCall) {
isCalled = true;
result = func.apply(thisArg, args);
}
if (isCalled && !timeoutId && !maxTimeoutId) {
args = thisArg = undefined;
}
return result;
}
debounced.cancel = cancel;
return debounced;
}
/**
* Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
* (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(1);
* // => false
*/
function isObject(value) {
// Avoid a V8 JIT bug in Chrome 19-20.
// See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
var type = typeof value;
return !!value && (type == 'object' || type == 'function');
}
return debounce;
})();
// ported lodash throttle function
var throttle = function( func, wait, options ){
var leading = true,
trailing = true;
if( options === false ){
leading = false;
} else if( typeof options === typeof {} ){
leading = 'leading' in options ? options.leading : leading;
trailing = 'trailing' in options ? options.trailing : trailing;
}
options = options || {};
options.leading = leading;
options.maxWait = wait;
options.trailing = trailing;
return debounce( func, wait, options );
};
var Navigator = function ( element, options ) {
this._init(element, options)
};
var extend = function() {
for(var i = 1; i < arguments.length; i++) {
for(var key in arguments[i]) {
if(arguments[i].hasOwnProperty(key)) {
arguments[0][key] = arguments[i][key];
}
}
}
return arguments[0];
};
var wid = function(elem) {
return elem.getBoundingClientRect().width;
};
var hei = function(elem) {
return elem.getBoundingClientRect().height;
};
Navigator.prototype = {
constructor: Navigator
/****************************
Main functions
****************************/
, bb: function(){
var bb = this.cy.elements().boundingBox()
if( bb.w === 0 || bb.h === 0 ){
return {
x1: 0,
x2: Infinity,
y1: 0,
y2: Infinity,
w: Infinity,
h: Infinity
} // => hide interactive overlay
}
return bb
}
, _addCyListener: function(events, handler){
this._cyListeners.push({
events: events,
handler: handler
})
this.cy.on(events, handler)
}
, _removeCyListeners: function(){
var cy = this.cy
this._cyListeners.forEach(function(l){
cy.off(l.events, l.handler)
})
cy.offRender(this._onRenderHandler)
}
, _init: function ( cy, options ) {
this._cyListeners = []
this.$element = cy.container()
this.options = extend({}, defaults, options)
this.cy = cy
// Cache bounding box
this.boundingBox = this.bb()
// Cache sizes
this.width = wid(this.$element);
this.height = hei(this.$element)
// Init components
this._initPanel()
this._initThumbnail()
this._initView()
this._initOverlay()
}
, destroy: function () {
this._removeEventsHandling();
// If container is not created by navigator and its removal is prohibited
if (this.options.container && !this.options.removeCustomContainer) {
this.$panel.innerHTML = '';
} else {
this.$panel.parentElement.removeChild(this.$panel);
}
}
/****************************
Navigator elements functions
****************************/
/*
* Used inner attributes
*
* w {number} width
* h {number} height
*/
, _initPanel: function () {
var options = this.options
if(options.container && typeof options.container === 'string' && options.container.length > 0) {
// to not break users which gives a jquery string selector
if (options.container.indexOf('#') !== -1) {
this.$panel = document.getElementById(options.container.replace('#', ''));
} else {
this.$panel = document.getElementsByClassName(options.container.replace('.', ''))[0];
}
} else {
this.$panel = document.createElement('div');
this.$panel.className = 'cytoscape-navigator';
document.body.appendChild(this.$panel);
}
this._setupPanel()
this._addCyListener('resize', this.resize.bind(this))
}
, _setupPanel: function () {
// Cache sizes
this.panelWidth = wid(this.$panel);
this.panelHeight = hei(this.$panel);
}
/*
* Used inner attributes
*
* zoom {number}
* pan {object} - {x: 0, y: 0}
*/
, _initThumbnail: function () {
// Create thumbnail
this.$thumbnail = document.createElement('img');
// Add thumbnail canvas to the DOM
this.$panel.appendChild(this.$thumbnail);
// Setup thumbnail
this._setupThumbnailSizes()
this._setupThumbnail()
}
, _setupThumbnail: function () {
this._updateThumbnailImage()
}
, _setupThumbnailSizes: function () {
// Update bounding box cache
this.boundingBox = this.bb()
this.thumbnailZoom = Math.min(this.panelHeight / this.boundingBox.h, this.panelWidth / this.boundingBox.w)
// Used on thumbnail generation
this.thumbnailPan = {
x: (this.panelWidth - this.thumbnailZoom * (this.boundingBox.x1 + this.boundingBox.x2))/2
, y: (this.panelHeight - this.thumbnailZoom * (this.boundingBox.y1 + this.boundingBox.y2))/2
}
}
// If bounding box has changed then update sizes
// Otherwise just update the thumbnail
, _checkThumbnailSizesAndUpdate: function () {
// Cache previous values
var _zoom = this.thumbnailZoom
, _pan_x = this.thumbnailPan.x
, _pan_y = this.thumbnailPan.y
this._setupThumbnailSizes()
if (_zoom != this.thumbnailZoom || _pan_x != this.thumbnailPan.x || _pan_y != this.thumbnailPan.y) {
this._setupThumbnail()
this._setupView()
} else {
this._updateThumbnailImage()
}
}
/*
* Used inner attributes
*
* w {number} width
* h {number} height
* x {number}
* y {number}
* borderWidth {number}
* locked {boolean}
*/
, _initView: function () {
this.$view = document.createElement('div');
this.$view.className = 'cytoscape-navigatorView';
this.$panel.appendChild(this.$view)
// Compute borders
this.viewBorderTop = parseInt(this.$view.style['border-top-width'], 10) || 0;
this.viewBorderRight = parseInt(this.$view.style['border-right-width'], 10) || 0;
this.viewBorderBottom = parseInt(this.$view.style['border-bottom-width'], 10) || 0;
this.viewBorderLeft = parseInt(this.$view.style['border-left-width'], 10) || 0;
// Abstract borders
this.viewBorderHorizontal = this.viewBorderLeft + this.viewBorderRight
this.viewBorderVertical = this.viewBorderTop + this.viewBorderBottom
this._setupView()
// Hook graph zoom and pan
this._addCyListener('zoom pan', this._setupView.bind(this))
}
, _setupView: function () {
if (this.viewLocked)
return
var cyZoom = this.cy.zoom()
, cyPan = this.cy.pan()
// Horizontal computation
this.viewW = this.width / cyZoom * this.thumbnailZoom
this.viewX = -cyPan.x * this.viewW / this.width + this.thumbnailPan.x - this.viewBorderLeft
// Vertical computation
this.viewH = this.height / cyZoom * this.thumbnailZoom
this.viewY = -cyPan.y * this.viewH / this.height + this.thumbnailPan.y - this.viewBorderTop
// CSS view
this.$view.style['width'] = this.viewW + 'px';
this.$view.style['height'] = this.viewH + 'px';
this.$view.style['position'] = 'absolute';
this.$view.style['left'] = this.viewX + 'px';
this.$view.style['top'] = this.viewY + 'px';
}
/*
* Used inner attributes
*
* timeout {number} used to keep stable frame rate
* lastMoveStartTime {number}
* inMovement {boolean}
* hookPoint {object} {x: 0, y: 0}
*/
, _initOverlay: function () {
// Used to capture mouse events
this.$overlay = document.createElement('div');
this.$overlay.className = 'cytoscape-navigatorOverlay';
// Add overlay to the DOM
this.$panel.appendChild(this.$overlay)
// Init some attributes
this.overlayHookPointX = 0;
this.overlayHookPointY = 0;
// Listen for events
this._initEventsHandling()
}
/****************************
Event handling functions
****************************/
, resize: function () {
// Cache sizes
this.width = wid(this.$element);
this.height = hei(this.$element);
this._thumbnailSetup = false
this._setupPanel()
this._checkThumbnailSizesAndUpdate()
this._setupView()
}
, _initEventsHandling: function () {
var that = this
, eventsLocal = [
// Mouse events
'mousedown'
, 'mousewheel'
, 'DOMMouseScroll' // Mozilla specific event
// Touch events
, 'touchstart'
]
, eventsGlobal = [
'mouseup'
, 'mouseout'
, 'mousemove'
// Touch events
, 'touchmove'
, 'touchend'
]
// handle events and stop their propagation
var overlayListener = function (ev) {
// Touch events
if (ev.type == 'touchstart') {
// Will count as middle of View
Object.defineProperty(ev, 'offsetX', {
value: that.viewX + that.viewW / 2,
writable: true
});
Object.defineProperty(ev, 'offsetY', {
value: that.viewY + that.viewH / 2,
writable: true
});
}
// Normalize offset for browsers which do not provide that value
if (ev.offsetX === undefined || ev.offsetY === undefined) {
var rect = ev.target.getBoundingClientRect();
var targetOffset = {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
Object.defineProperty(ev, 'offsetX', {
value: ev.pageX - targetOffset.left,
writable: true
});
Object.defineProperty(ev, 'offsetY', {
value: ev.pageY - targetOffset.top,
writable: true
});
}
if (ev.type == 'mousedown' || ev.type == 'touchstart') {
that._eventMoveStart(ev)
} else if (ev.type == 'mousewheel' || ev.type == 'DOMMouseScroll') {
that._eventZoom(ev)
}
// Prevent default and propagation
// Don't use peventPropagation as it breaks mouse events
return false;
};
// Hook global events
var globalListener = function (ev) {
// Do not make any computations if it is has no effect on Navigator
if (!that.overlayInMovement)
return;
// Touch events
if (ev.type == 'touchend') {
// Will count as middle of View
Object.defineProperty(ev, 'offsetX', {
value: that.viewX + that.viewW / 2,
writable: true
});
Object.defineProperty(ev, 'offsetY', {
value: that.viewY + that.viewH / 2,
writable: true
});
} else if (ev.type == 'touchmove') {
// Hack - we take in account only first touch
Object.defineProperty(ev, 'pageX', {
value: ev.originalEvent.touches[0].pageX,
writable: true
});
Object.defineProperty(ev, 'pageY', {
value: ev.originalEvent.touches[0].pageY,
writable: true
});
}
// Normalize offset for browsers which do not provide that value
if (ev.offsetX === undefined || ev.offsetY === undefined) {
var rect = ev.target.getBoundingClientRect();
var targetOffset = {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
Object.defineProperty(ev, 'offsetX', {
value: ev.pageX - targetOffset.left,
writable: true
});
Object.defineProperty(ev, 'offsetY', {
value: ev.pageY - targetOffset.top,
writable: true
});
}
// Translate global events into local coordinates
if (ev.target !== that.$overlay) {
var rect = ev.target.getBoundingClientRect();
var rect2 = that.$overlay.getBoundingClientRect();
var targetOffset = {
top: rect.top + window.scrollY,
left: rect.left + window.scrollX,
};
var overlayOffset = {
top: rect2.top + window.scrollY,
left: rect2.left + window.scrollX,
};
if(targetOffset && overlayOffset) {
Object.defineProperty(ev, 'offsetX', {
value: ev.offsetX - overlayOffset.left + targetOffset.left,
writable: true
});
Object.defineProperty(ev, 'offsetY', {
value: ev.offsetY - overlayOffset.top + targetOffset.top,
writable: true
});
} else {
return false;
}
}
if (ev.type == 'mousemove' || ev.type == 'touchmove') {
that._eventMove(ev)
} else if (ev.type == 'mouseup' || ev.type == 'touchend') {
that._eventMoveEnd(ev)
}
// Prevent default and propagation
// Don't use peventPropagation as it breaks mouse events
return false;
};
for (var i = 0; i < eventsLocal.length; i++) {
this.$overlay.addEventListener(eventsLocal[i], overlayListener, false);
}
for (var i = 0; i < eventsGlobal.length; i++) {
window.addEventListener(eventsGlobal[i], globalListener, false);
}
this._removeEventsHandling = function(){
for (var i = 0; i < eventsLocal.length; i++) {
this.$overlay.removeEventListener(eventsLocal[i], overlayListener);
}
for (var i = 0; i < eventsGlobal.length; i++) {
window.removeEventListener(eventsGlobal[i], globalListener);
}
}
}
, _eventMoveStart: function (ev) {
var now = new Date().getTime()
// Check if it was double click
if (this.overlayLastMoveStartTime
&& this.overlayLastMoveStartTime + this.options.dblClickDelay > now) {
// Reset lastMoveStartTime
this.overlayLastMoveStartTime = 0
// Enable View in order to move it to the center
this.overlayInMovement = true
// Set hook point as View center
this.overlayHookPointX = this.viewW / 2
this.overlayHookPointY = this.viewH / 2
// Move View to start point
if (this.options.viewLiveFramerate !== false) {
this._eventMove({
offsetX: this.panelWidth / 2
, offsetY: this.panelHeight / 2
})
} else {
this._eventMoveEnd({
offsetX: this.panelWidth / 2
, offsetY: this.panelHeight / 2
})
}
// View should be inactive as we don't want to move it right after double click
this.overlayInMovement = false
}
// This is a single click
// Take care as single click happens before double click 2 times
else {
this.overlayLastMoveStartTime = now
this.overlayInMovement = true
// Lock view moving caused by cy events
this.viewLocked = true
// if event started in View
if (ev.offsetX >= this.viewX && ev.offsetX <= this.viewX + this.viewW
&& ev.offsetY >= this.viewY && ev.offsetY <= this.viewY + this.viewH
) {
this.overlayHookPointX = ev.offsetX - this.viewX
this.overlayHookPointY = ev.offsetY - this.viewY
}
// if event started in Thumbnail (outside of View)
else {
// Set hook point as View center
this.overlayHookPointX = this.viewW / 2
this.overlayHookPointY = this.viewH / 2
// Move View to start point
this._eventMove(ev)
}
}
}
, _eventMove: function (ev) {
var that = this
this._checkMousePosition(ev)
// break if it is useless event
if (!this.overlayInMovement) {
return;
}
// Update cache
this.viewX = ev.offsetX - this.overlayHookPointX
this.viewY = ev.offsetY - this.overlayHookPointY
// Update view position
this.$view.style['left'] = this.viewX + 'px';
this.$view.style['top'] = this.viewY + 'px';
// Move Cy
if (this.options.viewLiveFramerate !== false) {
// trigger instantly
if (this.options.viewLiveFramerate == 0) {
this._moveCy()
}
// trigger less often than frame rate
else if (!this.overlayTimeout) {
// Set a timeout for graph movement
this.overlayTimeout = setTimeout(function () {
that._moveCy()
that.overlayTimeout = false
}, 1000 / this.options.viewLiveFramerate)
}
}
}
, _checkMousePosition: function (ev) {
// If mouse in over View
if(ev.offsetX > this.viewX && ev.offsetX < this.viewX + this.viewBorderHorizontal + this.viewW
&& ev.offsetY > this.viewY && ev.offsetY < this.viewY + this.viewBorderVertical + this.viewH) {
this.$panel.classList.add('mouseover-view')
} else {
this.$panel.classList.remove('mouseover-view')
}
}
, _eventMoveEnd: function (ev) {
// Unlock view changing caused by graph events
this.viewLocked = false
// Remove class when mouse is not over Navigator
this.$panel.classList.remove('mouseover-view')
if (!this.overlayInMovement) {
return;
}
// Trigger one last move
this._eventMove(ev)
// If mode is not live then move graph on drag end
if (this.options.viewLiveFramerate === false) {
this._moveCy()
}
// Stop movement permission
this.overlayInMovement = false
}
, _eventZoom: function (ev) {
var ev2 = extend({}, ev.originalEvent);
var delta = ev.wheelDeltaY / 1000 || ev.wheelDelta / 1000 || ev.detail / -32 || ev2.wheelDeltaY / 1000 || ev2.wheelDelta / 1000 || ev2.detail / -32;
var zoomRate = Math.pow(10, delta)
, mousePosition = {
left: ev.offsetX
, top: ev.offsetY
}
if (this.cy.zoomingEnabled()) {
this._zoomCy(zoomRate, mousePosition)
}
}
, _updateThumbnailImage: function () {
var that = this;
if( this._thumbnailUpdating ){
return;
}
this._thumbnailUpdating = true;
var render = function() {
that._checkThumbnailSizesAndUpdate();
that._setupView();
var $img = that.$thumbnail;
var img = $img;
var w = that.panelWidth;
var h = that.panelHeight;
var bb = that.boundingBox;
var zoom = Math.min( w/bb.w, h/bb.h );
var png = that.cy.png({
full: true,
scale: zoom,
maxHeight: h,
maxWidth: w
});
if( png.indexOf('image/png') < 0 ){
img.removeAttribute( 'src' );
} else {
img.setAttribute( 'src', png );
}
var translate = {
x: (w - zoom*( bb.w ))/2,
y: (h - zoom*( bb.h ))/2
};
$img.style['position'] = 'absolute';
$img.style['left'] = translate.x + 'px';
$img.style['top'] = translate.y + 'px';
}
this._onRenderHandler = throttle(render, that.options.rerenderDelay)
this.cy.onRender( this._onRenderHandler )
}
/****************************
Navigator view moving
****************************/
, _moveCy: function () {
this.cy.pan({
x: -(this.viewX + this.viewBorderLeft - this.thumbnailPan.x) * this.width / this.viewW
, y: -(this.viewY + this.viewBorderLeft - this.thumbnailPan.y) * this.height / this.viewH
})
}
/**
* Zooms graph.
*
* @this {cytoscapeNavigator}
* @param {number} zoomRate The zoom rate value. 1 is 100%.
*/
, _zoomCy: function (zoomRate, zoomCenterRaw) {
var zoomCenter
, isZoomCenterInView = false
zoomCenter = {
x: this.width / 2
, y: this.height / 2
};
this.cy.zoom({
level: this.cy.zoom() * zoomRate
, renderedPosition: zoomCenter
})
}
}
// registers the extension on a cytoscape lib ref
var register = function( cytoscape ){
if (!cytoscape){ return; } // can't register if cytoscape unspecified
cytoscape( 'core', 'navigator', function( options ){
var cy = this;
return new Navigator( cy, options );
} );
};
if (typeof module !== 'undefined' && module.exports) { // expose as a commonjs module
module.exports = function( cytoscape ){
register( cytoscape );
};
} else if (typeof define !== 'undefined' && define.amd) { // expose as an amd/requirejs module
define('cytoscape-navigator', function(){
return register;
});
}
if (typeof cytoscape !== 'undefined') { // expose to global cytoscape (i.e. window.cytoscape)
register(cytoscape);
}
})();