visjs-network
Version:
A dynamic, browser-based network visualization library.
484 lines (452 loc) • 11.6 kB
JavaScript
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