UNPKG

visjs-network

Version:

A dynamic, browser-based network visualization library.

484 lines (452 loc) 11.6 kB
var Hammer = require('../../../module/hammer') var hammerUtil = require('../../../hammerUtil') var keycharm = require('keycharm') /** * Navigation Handler */ class NavigationHandler { /** * @param {Object} body * @param {Canvas} canvas */ constructor(body, canvas) { this.body = body this.canvas = canvas this.iconsCreated = false this.navigationHammers = [] this.boundFunctions = {} this.touchTime = 0 this.activated = false this.body.emitter.on('activate', () => { this.activated = true this.configureKeyboardBindings() }) this.body.emitter.on('deactivate', () => { this.activated = false this.configureKeyboardBindings() }) this.body.emitter.on('destroy', () => { if (this.keycharm !== undefined) { this.keycharm.destroy() } }) this.options = {} } /** * * @param {Object} options */ setOptions(options) { if (options !== undefined) { this.options = options this.create() } } /** * Creates or refreshes navigation and sets key bindings */ create() { if (this.options.navigationButtons === true) { if (this.iconsCreated === false) { this.loadNavigationElements() } } else if (this.iconsCreated === true) { this.cleanNavigation() } this.configureKeyboardBindings() } /** * Cleans up previous navigation items */ cleanNavigation() { // clean hammer bindings if (this.navigationHammers.length != 0) { for (var i = 0; i < this.navigationHammers.length; i++) { this.navigationHammers[i].destroy() } this.navigationHammers = [] } // clean up previous navigation items if ( this.navigationDOM && this.navigationDOM['wrapper'] && this.navigationDOM['wrapper'].parentNode ) { this.navigationDOM['wrapper'].parentNode.removeChild( this.navigationDOM['wrapper'] ) } this.iconsCreated = false } /** * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas. * * @private */ loadNavigationElements() { this.cleanNavigation() this.navigationDOM = {} var navigationDivs = [ 'up', 'down', 'left', 'right', 'zoomIn', 'zoomOut', 'zoomExtends' ] var navigationDivActions = [ '_moveUp', '_moveDown', '_moveLeft', '_moveRight', '_zoomIn', '_zoomOut', '_fit' ] this.navigationDOM['wrapper'] = document.createElement('div') this.navigationDOM['wrapper'].className = 'vis-navigation' this.canvas.frame.appendChild(this.navigationDOM['wrapper']) for (var i = 0; i < navigationDivs.length; i++) { this.navigationDOM[navigationDivs[i]] = document.createElement('div') this.navigationDOM[navigationDivs[i]].className = 'vis-button vis-' + navigationDivs[i] this.navigationDOM['wrapper'].appendChild( this.navigationDOM[navigationDivs[i]] ) var hammer = new Hammer(this.navigationDOM[navigationDivs[i]]) if (navigationDivActions[i] === '_fit') { hammerUtil.onTouch(hammer, this._fit.bind(this)) } else { hammerUtil.onTouch( hammer, this.bindToRedraw.bind(this, navigationDivActions[i]) ) } this.navigationHammers.push(hammer) } // use a hammer for the release so we do not require the one used in the rest of the network // the one the rest uses can be overloaded by the manipulation system. var hammerFrame = new Hammer(this.canvas.frame) hammerUtil.onRelease(hammerFrame, () => { this._stopMovement() }) this.navigationHammers.push(hammerFrame) this.iconsCreated = true } /** * * @param {string} action */ bindToRedraw(action) { if (this.boundFunctions[action] === undefined) { this.boundFunctions[action] = this[action].bind(this) this.body.emitter.on('initRedraw', this.boundFunctions[action]) this.body.emitter.emit('_startRendering') } } /** * * @param {string} action */ unbindFromRedraw(action) { if (this.boundFunctions[action] !== undefined) { this.body.emitter.off('initRedraw', this.boundFunctions[action]) this.body.emitter.emit('_stopRendering') delete this.boundFunctions[action] } } /** * this stops all movement induced by the navigation buttons * * @private */ _fit() { if (new Date().valueOf() - this.touchTime > 700) { // TODO: fix ugly hack to avoid hammer's double fireing of event (because we use release?) this.body.emitter.emit('fit', { duration: 700 }) this.touchTime = new Date().valueOf() } } /** * this stops all movement induced by the navigation buttons * * @private */ _stopMovement() { for (let boundAction in this.boundFunctions) { if (this.boundFunctions.hasOwnProperty(boundAction)) { this.body.emitter.off('initRedraw', this.boundFunctions[boundAction]) this.body.emitter.emit('_stopRendering') } } this.boundFunctions = {} } /** * * @private */ _moveUp() { this.body.view.translation.y += this.options.keyboard.speed.y } /** * * @private */ _moveDown() { this.body.view.translation.y -= this.options.keyboard.speed.y } /** * * @private */ _moveLeft() { this.body.view.translation.x += this.options.keyboard.speed.x } /** * * @private */ _moveRight() { this.body.view.translation.x -= this.options.keyboard.speed.x } /** * * @private */ _zoomIn() { var scaleOld = this.body.view.scale var scale = this.body.view.scale * (1 + this.options.keyboard.speed.zoom) var translation = this.body.view.translation var scaleFrac = scale / scaleOld var tx = (1 - scaleFrac) * this.canvas.canvasViewCenter.x + translation.x * scaleFrac var ty = (1 - scaleFrac) * this.canvas.canvasViewCenter.y + translation.y * scaleFrac this.body.view.scale = scale this.body.view.translation = { x: tx, y: ty } this.body.emitter.emit('zoom', { direction: '+', scale: this.body.view.scale, pointer: null }) } /** * * @private */ _zoomOut() { var scaleOld = this.body.view.scale var scale = this.body.view.scale / (1 + this.options.keyboard.speed.zoom) var translation = this.body.view.translation var scaleFrac = scale / scaleOld var tx = (1 - scaleFrac) * this.canvas.canvasViewCenter.x + translation.x * scaleFrac var ty = (1 - scaleFrac) * this.canvas.canvasViewCenter.y + translation.y * scaleFrac this.body.view.scale = scale this.body.view.translation = { x: tx, y: ty } this.body.emitter.emit('zoom', { direction: '-', scale: this.body.view.scale, pointer: null }) } /** * bind all keys using keycharm. */ configureKeyboardBindings() { if (this.keycharm !== undefined) { this.keycharm.destroy() } if (this.options.keyboard.enabled === true) { if (this.options.keyboard.bindToWindow === true) { this.keycharm = keycharm({ container: window, preventDefault: true }) } else { this.keycharm = keycharm({ container: this.canvas.frame, preventDefault: true }) } this.keycharm.reset() if (this.activated === true) { this.keycharm.bind( 'up', () => { this.bindToRedraw('_moveUp') }, 'keydown' ) this.keycharm.bind( 'down', () => { this.bindToRedraw('_moveDown') }, 'keydown' ) this.keycharm.bind( 'left', () => { this.bindToRedraw('_moveLeft') }, 'keydown' ) this.keycharm.bind( 'right', () => { this.bindToRedraw('_moveRight') }, 'keydown' ) this.keycharm.bind( '=', () => { this.bindToRedraw('_zoomIn') }, 'keydown' ) this.keycharm.bind( 'num+', () => { this.bindToRedraw('_zoomIn') }, 'keydown' ) this.keycharm.bind( 'num-', () => { this.bindToRedraw('_zoomOut') }, 'keydown' ) this.keycharm.bind( '-', () => { this.bindToRedraw('_zoomOut') }, 'keydown' ) this.keycharm.bind( '[', () => { this.bindToRedraw('_zoomOut') }, 'keydown' ) this.keycharm.bind( ']', () => { this.bindToRedraw('_zoomIn') }, 'keydown' ) this.keycharm.bind( 'pageup', () => { this.bindToRedraw('_zoomIn') }, 'keydown' ) this.keycharm.bind( 'pagedown', () => { this.bindToRedraw('_zoomOut') }, 'keydown' ) this.keycharm.bind( 'up', () => { this.unbindFromRedraw('_moveUp') }, 'keyup' ) this.keycharm.bind( 'down', () => { this.unbindFromRedraw('_moveDown') }, 'keyup' ) this.keycharm.bind( 'left', () => { this.unbindFromRedraw('_moveLeft') }, 'keyup' ) this.keycharm.bind( 'right', () => { this.unbindFromRedraw('_moveRight') }, 'keyup' ) this.keycharm.bind( '=', () => { this.unbindFromRedraw('_zoomIn') }, 'keyup' ) this.keycharm.bind( 'num+', () => { this.unbindFromRedraw('_zoomIn') }, 'keyup' ) this.keycharm.bind( 'num-', () => { this.unbindFromRedraw('_zoomOut') }, 'keyup' ) this.keycharm.bind( '-', () => { this.unbindFromRedraw('_zoomOut') }, 'keyup' ) this.keycharm.bind( '[', () => { this.unbindFromRedraw('_zoomOut') }, 'keyup' ) this.keycharm.bind( ']', () => { this.unbindFromRedraw('_zoomIn') }, 'keyup' ) this.keycharm.bind( 'pageup', () => { this.unbindFromRedraw('_zoomIn') }, 'keyup' ) this.keycharm.bind( 'pagedown', () => { this.unbindFromRedraw('_zoomOut') }, 'keyup' ) } } } } export default NavigationHandler