escher-vis
Version:
Escher: A Web Application for Building, Sharing, and Embedding Data-Rich Visualizations of Biological Pathways
148 lines (135 loc) • 4.69 kB
JavaScript
/**
* Define a brush to select elements in a map.
* @param {D3 Selection} selection - A d3 selection to place the brush in.
* @param {Boolean} is_enabled - Whether to turn the brush on.
* @param {escher.Map} map - The map where the brush will be active.
* @param {String} insert_after - A d3 selector string to choose the svg element
* that the brush will be inserted after. Often a
* canvas element (e.g. '.canvas-group').
*/
var utils = require('./utils')
var d3_brush = require('d3-brush').brush
var d3_brushSelection = require('d3-brush').brushSelection
var d3_scaleIdentity = require('d3-scale').scaleIdentity
var d3_selection = require('d3-selection')
var d3_select = require('d3-selection').select
var Brush = utils.make_class()
Brush.prototype = {
init: init,
toggle: toggle,
setup_selection_brush: setup_selection_brush,
}
module.exports = Brush
/**
* Initialize the brush.
* @param {D3 Selection} selection - The selection for the brush.
* @param {Boolean} is_enabled - Whether to enable right away.
* @param {escher.Map} map - The Escher Map object.
* @param {Node} insert_after - A node within selection to insert after.
*/
function init (selection, is_enabled, map, insert_after) {
this.brush_sel = selection.append('g').attr('id', 'brush-container')
var node = this.brush_sel.node()
var insert_before_node = selection.select(insert_after).node().nextSibling
if (node !== insert_before_node) {
node.parentNode.insertBefore(node, insert_before_node)
}
this.enabled = is_enabled
this.map = map
}
/**
* Returns a boolean for the on/off status of the brush
* @return {Boolean}
*/
function brush_is_enabled () {
return this.map.sel.select('.brush').empty()
}
/**
* Turn the brush on or off
* @param {Boolean} on_off
*/
function toggle (on_off) {
if (on_off === undefined) {
on_off = !this.enabled
}
if (on_off) {
this.setup_selection_brush()
} else {
this.brush_sel.selectAll('*').remove()
}
}
/**
* Turn off the mouse crosshair
*/
function turn_off_crosshair (sel) {
sel.selectAll('rect').attr('cursor', null)
}
function setup_selection_brush () {
var map = this.map
var selection = this.brush_sel
var selectable_selection = map.sel.selectAll('#nodes,#text-labels')
var size_and_location = map.canvas.size_and_location()
var width = size_and_location.width
var height = size_and_location.height
var x = size_and_location.x
var y = size_and_location.y
// Clear existing brush
selection.selectAll('*').remove()
// Set a flag so we know that the brush is being cleared at the end of a
// successful brush
var clearing_flag = false
var brush = d3_brush()
.extent([ [ x, y ], [ x + width, y + height ] ])
.on('start', function () {
turn_off_crosshair(selection)
// unhide secondary metabolites if they are hidden
if (map.settings.get_option('hide_secondary_metabolites')) {
map.settings.set_conditional('hide_secondary_metabolites', false)
map.draw_everything()
map.set_status('Showing secondary metabolites. You can hide them ' +
'again in Settings.', 2000)
}
})
.on('brush', function () {
var shift_key_on = d3_selection.event.sourceEvent.shiftKey
var rect = d3_brushSelection(this)
// Check for no selection (e.g. after clearing brush)
if (rect !== null) {
// When shift is pressed, ignore the currently selected nodes.
// Otherwise, brush all nodes.
var selection = (
shift_key_on ?
selectable_selection.selectAll('.node:not(.selected),.text-label:not(.selected)') :
selectable_selection.selectAll('.node,.text-label')
)
selection.classed('selected', function (d) {
var sx = d.x
var sy = d.y
return (rect[0][0] <= sx && sx < rect[1][0] &&
rect[0][1] <= sy && sy < rect[1][1])
})
}
})
.on('end', function () {
turn_off_crosshair(selection)
// Clear brush
var rect = d3_brushSelection(this)
if (rect === null) {
if (clearing_flag) {
clearing_flag = false
} else {
// Empty selection, deselect all
map.select_none()
}
} else {
// Not empty, then clear the box
clearing_flag = true
selection.call(brush.move, null)
}
})
selection
// Initialize brush
.call(brush)
// Turn off the pan grab icons
turn_off_crosshair(selection)
}