escher-vis
Version:
Escher: A Web Application for Building, Sharing, and Embedding Data-Rich Visualizations of Biological Pathways
140 lines (125 loc) • 4.07 kB
JavaScript
var utils = require('./utils')
var PlacedDiv = require('./PlacedDiv')
var tinier = require('tinier')
var _ = require('underscore')
/**
* Manage the tooltip that lives in a PlacedDiv.
* @param selection
* @param map
* @param tooltip_component
* @param zoom_container
*/
var TooltipContainer = utils.make_class()
// instance methods
TooltipContainer.prototype = {
init: init,
setup_map_callbacks: setup_map_callbacks,
setup_zoom_callbacks: setup_zoom_callbacks,
is_visible: is_visible,
show: show,
hide: hide,
delay_hide: delay_hide,
cancel_hide_tooltip: cancel_hide_tooltip,
}
module.exports = TooltipContainer
// definitions
function init (selection, map, tooltip_component, zoom_container) {
var div = selection.append('div').attr('id', 'tooltip-container')
this.placed_div = PlacedDiv(div, map)
div.on('mouseover', this.cancel_hide_tooltip.bind(this))
div.on('mouseleave', this.hide.bind(this))
this.map = map
this.setup_map_callbacks(map)
this.zoom_container = zoom_container
this.setup_zoom_callbacks(zoom_container)
// keep a reference to tinier tooltip
this.tooltip_component = tooltip_component
// if they pass in a function, then use that
this.tooltip_function = (_.isFunction(tooltip_component) ?
function (state) { tooltip_component({ state: state, el: div.node() })} :
null)
// if they pass in a tinier component, use that
this.tinier_tooltip = (tinier.checkType(tinier.COMPONENT, tooltip_component) ?
tinier.run(tooltip_component, div.node()) :
null)
this.delay_hide_timeout = null
}
function setup_map_callbacks (map) {
map.callback_manager.set('show_tooltip.tooltip_container', function (type, d) {
if (map.settings.get_option('enable_tooltips')) {
this.show(type, d)
}
}.bind(this))
map.callback_manager.set('hide_tooltip.tooltip_container',
this.hide.bind(this))
map.callback_manager.set('delay_hide_tooltip.tooltip_container',
this.delay_hide.bind(this))
}
function setup_zoom_callbacks (zoom_container) {
zoom_container.callback_manager.set('zoom.tooltip_container', function () {
if (this.is_visible()) {
this.hide()
}
}.bind(this))
zoom_container.callback_manager.set('go_to.tooltip_container', function () {
if (this.is_visible()) {
this.hide()
}
}.bind(this))
}
/**
* Return visibility of tooltip container.
* @return {Boolean} Whether tooltip is visible.
*/
function is_visible () {
return this.placed_div.is_visible()
}
/**
* Show and place the input.
* @param {String} type - 'reaction_label', 'node_label', or 'gene_label'
* @param {Object} d - D3 data for DOM element
* @param {Object} coords - Object with x and y coords. Cannot use coords from
* 'd' because gene labels do not have them.
*/
function show (type, d) {
// get rid of a lingering delayed hide
this.cancel_hide_tooltip()
if (_.contains([ 'reaction_label', 'node_label', 'gene_label' ], type)) {
var coords = { x: d.label_x, y: d.label_y + 10 }
this.placed_div.place(coords)
const data = {
biggId: d.bigg_id,
name: d.name,
loc: coords,
data: d.data_string,
type: type.replace('_label', '').replace('node', 'metabolite'),
}
if (this.tooltip_function !== null) {
this.tooltip_function(data)
} else if (this.tinier_tooltip) {
this.tinier_tooltip.reducers.setContainerData(data)
}
} else {
throw new Error('Tooltip not supported for object type ' + type)
}
}
/**
* Hide the input.
*/
function hide () {
this.placed_div.hide()
}
/**
* Hide the input after a short delay, so that mousing onto the tooltip does not
* cause it to hide.
*/
function delay_hide () {
this.delay_hide_timeout = setTimeout(function () {
this.hide()
}.bind(this), 100)
}
function cancel_hide_tooltip () {
if (this.delay_hide_timeout !== null) {
clearTimeout(this.delay_hide_timeout)
}
}