escher-vis
Version:
Escher: A Web Application for Building, Sharing, and Embedding Data-Rich Visualizations of Biological Pathways
258 lines (230 loc) • 8.52 kB
JavaScript
/**
* Canvas. Defines a canvas that accepts drag/zoom events and can be resized.
* Canvas(selection, x, y, width, height)
* Adapted from http://bl.ocks.org/mccannf/1629464.
*/
var utils = require('./utils')
var CallbackManager = require('./CallbackManager')
var d3_drag = require('d3-drag').drag
var d3_selection = require('d3-selection')
var _ = require('underscore')
var Canvas = utils.make_class()
Canvas.prototype = {
init: init,
toggle_resize: toggle_resize,
setup: setup,
size_and_location: size_and_location
}
module.exports = Canvas
function init (selection, size_and_location) {
this.selection = selection
this.x = size_and_location.x
this.y = size_and_location.y
this.width = size_and_location.width
this.height = size_and_location.height
// enable by default
this.resize_enabled = true
// set up the callbacks
this.callback_manager = new CallbackManager()
this.setup()
}
/**
* Turn the resize on or off
*/
function toggle_resize (on_off) {
if (_.isUndefined(on_off)) on_off = !this.resize_enabled
if (on_off) {
this.selection.selectAll('.drag-rect')
.style('pointer-events', 'auto')
} else {
this.selection.selectAll('.drag-rect')
.style('pointer-events', 'none')
}
}
function setup () {
var self = this
var extent = { x: this.width, y: this.height }
var dragbar_width = 100
var mouse_node_mult = 10
var new_sel = this.selection.append('g')
.classed('canvas-group', true)
.data([ { x: this.x, y: this.y } ])
var mouse_node = new_sel.append('rect')
.attr('id', 'mouse-node')
.attr('width', this.width * mouse_node_mult)
.attr('height', this.height * mouse_node_mult)
.attr('transform', 'translate(' +
[ self.x - this.width*mouse_node_mult/2,
self.y - this.height*mouse_node_mult/2 ] +
')')
.attr('pointer-events', 'all')
this.mouse_node = mouse_node
var rect = new_sel.append('rect')
.attr('id', 'canvas')
.attr('width', this.width)
.attr('height', this.height)
.attr('transform', 'translate(' + [ self.x, self.y ] + ')')
var drag_right = d3_drag()
// TODO do we need these? in d3 v4, origin is now subject
// .origin(Object)
.on('start', stop_propagation)
.on('drag', rdragresize),
drag_left = d3_drag()
// .origin(Object)
.on('start', stop_propagation)
.on('drag', ldragresize),
drag_top = d3_drag()
// .origin(Object)
.on('start', stop_propagation)
.on('drag', tdragresize),
drag_bottom = d3_drag()
// .origin(Object)
.on('start', stop_propagation)
.on('drag', bdragresize)
var left = new_sel.append('rect')
.classed('drag-rect', true)
.attr('transform', function(d) {
return 'translate('+[ d.x - (dragbar_width/2),
d.y + (dragbar_width/2) ]+')'
})
.attr('height', this.height - dragbar_width)
.attr('id', 'dragleft')
.attr('width', dragbar_width)
.attr('cursor', 'ew-resize')
.classed('resize-rect', true)
.call(drag_left)
var right = new_sel.append('rect')
.classed('drag-rect', true)
.attr('transform', function(d) {
return 'translate('+[ d.x + self.width - (dragbar_width/2),
d.y + (dragbar_width/2) ]+')'
})
.attr('id', 'dragright')
.attr('height', this.height - dragbar_width)
.attr('width', dragbar_width)
.attr('cursor', 'ew-resize')
.classed('resize-rect', true)
.call(drag_right)
var top = new_sel.append('rect')
.classed('drag-rect', true)
.attr('transform', function(d) {
return 'translate('+[ d.x + (dragbar_width/2),
d.y - (dragbar_width/2) ]+')'
})
.attr('height', dragbar_width)
.attr('id', 'dragtop')
.attr('width', this.width - dragbar_width)
.attr('cursor', 'ns-resize')
.classed('resize-rect', true)
.call(drag_top)
var bottom = new_sel.append('rect')
.classed('drag-rect', true)
.attr('transform', function(d) {
return 'translate('+[ d.x + (dragbar_width/2),
d.y + self.height - (dragbar_width/2) ]+')'
})
.attr('id', 'dragbottom')
.attr('height', dragbar_width)
.attr('width', this.width - dragbar_width)
.attr('cursor', 'ns-resize')
.classed('resize-rect', true)
.call(drag_bottom)
// definitions
function stop_propagation () {
d3_selection.event.sourceEvent.stopPropagation()
}
function transform_string (x, y, current_transform) {
var tr = utils.d3_transform_catch(current_transform),
translate = tr.translate
if (x !== null) translate[0] = x
if (y !== null) translate[1] = y
return 'translate(' + translate + ')'
}
function ldragresize (d) {
var oldx = d.x
d.x = Math.min(d.x + self.width - (dragbar_width / 2), d3_selection.event.x)
self.x = d.x
self.width = self.width + (oldx - d.x)
left.attr('transform', function(d) {
return transform_string(d.x - (dragbar_width / 2), null, left.attr('transform'))
})
mouse_node.attr('transform', function(d) {
return transform_string(d.x, null, mouse_node.attr('transform'))
}).attr('width', self.width*mouse_node_mult)
rect.attr('transform', function(d) {
return transform_string(d.x, null, rect.attr('transform'))
}).attr('width', self.width)
top.attr('transform', function(d) {
return transform_string(d.x + (dragbar_width/2), null, top.attr('transform'))
}).attr('width', self.width - dragbar_width)
bottom.attr('transform', function(d) {
return transform_string(d.x + (dragbar_width/2), null, bottom.attr('transform'))
}).attr('width', self.width - dragbar_width)
self.callback_manager.run('resize')
}
function rdragresize (d) {
d3_selection.event.sourceEvent.stopPropagation()
var dragx = Math.max(d.x + (dragbar_width/2), d.x + self.width + d3_selection.event.dx)
//recalculate width
self.width = dragx - d.x
//move the right drag handle
right.attr('transform', function(d) {
return transform_string(dragx - (dragbar_width/2), null, right.attr('transform'))
})
//resize the drag rectangle
//as we are only resizing from the right, the x coordinate does not need to change
mouse_node.attr('width', self.width*mouse_node_mult)
rect.attr('width', self.width)
top.attr('width', self.width - dragbar_width)
bottom.attr('width', self.width - dragbar_width)
self.callback_manager.run('resize')
}
function tdragresize (d) {
d3_selection.event.sourceEvent.stopPropagation()
var oldy = d.y
d.y = Math.min(d.y + self.height - (dragbar_width / 2), d3_selection.event.y)
self.y = d.y
self.height = self.height + (oldy - d.y)
top.attr('transform', function(d) {
return transform_string(null, d.y - (dragbar_width / 2), top.attr('transform'))
})
mouse_node.attr('transform', function(d) {
return transform_string(null, d.y, mouse_node.attr('transform'))
}).attr('width', self.height*mouse_node_mult)
rect.attr('transform', function(d) {
return transform_string(null, d.y, rect.attr('transform'))
}).attr('height', self.height)
left.attr('transform', function(d) {
return transform_string(null, d.y + (dragbar_width/2), left.attr('transform'))
}).attr('height', self.height - dragbar_width)
right.attr('transform', function(d) {
return transform_string(null, d.y + (dragbar_width/2), right.attr('transform'))
}).attr('height', self.height - dragbar_width)
self.callback_manager.run('resize')
}
function bdragresize(d) {
d3_selection.event.sourceEvent.stopPropagation()
var dragy = Math.max(d.y + (dragbar_width/2), d.y + self.height + d3_selection.event.dy)
//recalculate width
self.height = dragy - d.y
//move the right drag handle
bottom.attr('transform', function(d) {
return transform_string(null, dragy - (dragbar_width/2), bottom.attr('transform'))
})
//resize the drag rectangle
//as we are only resizing from the right, the x coordinate does not need to change
mouse_node.attr('height', self.height*mouse_node_mult)
rect.attr('height', self.height)
left.attr('height', self.height - dragbar_width)
right.attr('height', self.height - dragbar_width)
self.callback_manager.run('resize')
}
}
function size_and_location () {
return {
x: this.x,
y: this.y,
width: this.width,
height: this.height
}
}