escher-vis
Version:
Escher: A Web Application for Building, Sharing, and Embedding Data-Rich Visualizations of Biological Pathways
422 lines (388 loc) • 14.4 kB
JavaScript
/**
* SettingsMenu
*/
var utils = require('./utils')
var CallbackManager = require('./CallbackManager')
var ScaleEditor = require('./ScaleEditor')
var SettingsMenu = utils.make_class()
SettingsMenu.prototype = {
init: init,
is_visible: is_visible,
toggle: toggle,
hold_changes: hold_changes,
abandon_changes: abandon_changes,
accept_changes: accept_changes,
style_gui: style_gui,
view_gui: view_gui,
}
module.exports = SettingsMenu
function init (sel, settings, map, toggle_abs_and_apply_data) {
this.sel = sel
this.settings = settings
this.draw = false
var unique_map_id = this.settings.get_option('unique_map_id')
this.unique_string = (unique_map_id === null ? '' : '.' + unique_map_id)
var background = sel.append('div')
.attr('class', 'settings-box-background')
.style('display', 'none')
var container = background.append('div')
.attr('class', 'settings-box-container')
.style('display', 'none')
// done button
container.append('button')
.attr("class", "btn btn-sm btn-default settings-button")
.on('click', function () {
this.accept_changes()
}.bind(this))
.append("span").attr("class", "glyphicon glyphicon-ok")
// quit button
container.append('button')
.attr("class", "btn btn-sm btn-default settings-button settings-button-close")
.on('click', function () {
this.abandon_changes()
}.bind(this))
.append("span").attr("class", "glyphicon glyphicon-remove")
var box = container.append('div')
.attr('class', 'settings-box')
// Tip
box.append('div')
.text('Tip: Hover over an option to see more details about it.')
.classed('settings-tip', true)
box.append('hr')
// view and build
box.append('div').text('View and build options')
.attr('class', 'settings-section-heading-large')
this.view_gui(box.append('div'))
// reactions
box.append('hr')
box.append('div')
.text('Reactions').attr('class', 'settings-section-heading-large')
var rse = new ScaleEditor(box.append('div'), 'reaction', this.settings,
map.get_data_statistics.bind(map))
map.callback_manager.set('calc_data_stats__reaction', function (changed) {
if (changed) {
rse.update()
rse.update_no_data()
}
})
box.append('div')
.text('Reaction or Gene data').attr('class', 'settings-section-heading')
this.style_gui(box.append('div'), 'reaction', function (on_off) {
if (toggle_abs_and_apply_data) {
toggle_abs_and_apply_data('reaction', on_off)
rse.update()
rse.update_no_data()
}
})
// metabolite data
box.append('hr')
box.append('div').text('Metabolites')
.attr('class', 'settings-section-heading-large')
var mse = new ScaleEditor(box.append('div'), 'metabolite', this.settings,
map.get_data_statistics.bind(map))
map.callback_manager.set('calc_data_stats__metabolite', function (changed) {
if (changed) {
mse.update()
mse.update_no_data()
}
})
box.append('div').text('Metabolite data')
.attr('class', 'settings-section-heading')
this.style_gui(box.append('div'), 'metabolite', function (on_off) {
if (toggle_abs_and_apply_data) {
toggle_abs_and_apply_data('metabolite', on_off)
mse.update()
mse.update_no_data()
}
})
this.callback_manager = new CallbackManager()
this.map = map
this.selection = container
this.background = background
}
function is_visible() {
return this.selection.style('display') != 'none'
}
function toggle(on_off) {
if (on_off===undefined) on_off = !this.is_visible()
if (on_off) {
// hold changes until accepting/abandoning
this.hold_changes()
// show the menu
this.selection.style("display", "inline-block")
this.background.style("display", "block")
this.selection.select('input').node().focus()
// escape key
this.clear_escape = this.map.key_manager
.add_escape_listener(function () {
this.abandon_changes()
}.bind(this), true)
// enter key
this.clear_enter = this.map.key_manager
.add_enter_listener(function () {
this.accept_changes()
}.bind(this), true)
// run the show callback
this.callback_manager.run('show')
} else {
// draw on finish
if (this.draw) this.map.draw_everything()
// hide the menu
this.selection.style("display", "none")
this.background.style("display", "none")
if (this.clear_escape) this.clear_escape()
this.clear_escape = null
if (this.clear_enter) this.clear_enter()
this.clear_enter = null
// run the hide callback
this.callback_manager.run('hide')
}
}
function hold_changes() {
this.settings.hold_changes()
}
function abandon_changes() {
this.draw = false
this.settings.abandon_changes()
this.toggle(false)
}
function accept_changes() {
this.sel.selectAll('input').each(function (s) {
this.blur()
})
this.draw = true
this.settings.accept_changes()
this.toggle(false)
}
/**
* A UI to edit style.
*/
function style_gui (sel, type, abs_callback) {
var t = sel.append('table').attr('class', 'settings-table')
var settings = this.settings
// styles
t.append('tr').call(function (r) {
r.append('td').text('Options:')
.attr('class', 'options-label')
.attr('title', ('Options for ' + type + ' data.'))
var cell = r.append('td')
var styles = [['Absolute value', 'abs',
('If checked, use the absolute value when ' +
'calculating colors and sizes of ' + type + 's on the map')],
['Size', 'size',
('If checked, then size the ' +
(type == 'metabolite' ? 'radius of metabolite circles ' : 'thickness of reaction lines ') +
'according to the value of the ' + type + ' data')],
['Color', 'color',
('If checked, then color the ' +
(type == 'metabolite' ? 'metabolite circles ' : 'reaction lines ') +
'according to the value of the ' + type + ' data')],
['Text (Show data in label)', 'text',
('If checked, then show data values in the ' + type + ' ' +
'labels')]]
var style_cells = cell.selectAll('.option-group').data(styles)
var style_enter = style_cells.enter()
.append('label')
.attr('class', 'option-group')
// make the checkbox
var streams = []
var get_styles = function () {
var styles = []
cell.selectAll('input')
.each(function (d) { if (this.checked) styles.push(d[1]) })
return styles
}
style_enter.append('input').attr('type', 'checkbox')
.on('change', function (d) {
settings.set_conditional(type + '_styles', get_styles())
if (d[1] == 'abs')
abs_callback(this.checked)
}).each(function (d) {
// subscribe to changes in the model
settings.streams[type + '_styles'].onValue(function (ar) {
// check the box if the style is present
this.checked = (ar.indexOf(d[1]) != -1)
}.bind(this))
})
style_enter.append('span')
.text(function (d) { return d[0] })
.attr('title', function (d) { return d[2] })
})
// compare_style
t.append('tr').call(function (r) {
r.append('td')
.text('Comparison:')
.attr('class', 'options-label')
.attr('title', ('The function that will be used to compare ' +
'datasets, when paired data is loaded'))
var cell = r.append('td')
.attr('title', ('The function that will be used to compare ' +
'datasets, when paired data is loaded'));
var styles = [['Fold Change', 'fold'],
['Log2(Fold Change)', 'log2_fold'],
['Difference', 'diff']]
var style_cells = cell.selectAll('.option-group')
.data(styles)
var style_enter = style_cells.enter()
.append('label')
.attr('class', 'option-group')
// make the radio
style_enter.append('input').attr('type', 'radio')
.attr('name', type + '_compare_style' + this.unique_string)
.attr('value', function (d) { return d[1] })
.on('change', function () {
if (this.checked)
settings.set_conditional(type + '_compare_style', this.value)
})
.each(function () {
// subscribe to changes in the model
settings.streams[type + '_compare_style'].onValue(function (value) {
// check the box for the new value
this.checked = (this.value == value)
}.bind(this))
})
style_enter.append('span')
.text(function (d) { return d[0] })
}.bind(this))
// gene-specific settings
if (type === 'reaction') {
sel.append('table').attr('class', 'settings-table')
.attr('title', ('The function that will be used to evaluate ' +
'AND connections in gene reaction rules (AND ' +
'connections generally connect components of ' +
'an enzyme complex)'))
// and_method_in_gene_reaction_rule
.append('tr').call(function (r) {
r.append('td')
.text('Method for evaluating AND:')
.attr('class', 'options-label-wide')
var cell = r.append('td')
var styles = [ [ 'Mean', 'mean' ], [ 'Min', 'min' ] ]
var style_cells = cell.selectAll('.option-group')
.data(styles)
var style_enter = style_cells.enter()
.append('label')
.attr('class', 'option-group')
// make the radio
var name = 'and_method_in_gene_reaction_rule'
style_enter.append('input').attr('type', 'radio')
.attr('name', name + this.unique_string)
.attr('value', function (d) { return d[1] })
.on('change', function () {
if (this.checked)
settings.set_conditional(name, this.value)
})
.each(function () {
// subscribe to changes in the model
settings.streams[name].onValue(function (value) {
// check the box for the new value
this.checked = (this.value == value)
}.bind(this))
})
style_enter.append('span')
.text(function (d) { return d[0] })
}.bind(this))
}
}
function view_gui (s, option_name, string, options) {
// columns
var settings = this.settings
var t = s.append('table').attr('class', 'settings-table')
t.append('tr').call(function (r) {
// identifiers
r.attr('title', ('The identifiers that are show in the reaction, ' +
'gene, and metabolite labels on the map.'))
r.append('td').text('Identifiers:')
.attr('class', 'options-label')
var cell = r.append('td')
var options = [ [ 'ID\'s', 'bigg_id' ], [ 'Descriptive names', 'name' ] ]
var style_cells = cell.selectAll('.option-group')
.data(options)
var style_enter = style_cells.enter()
.append('label')
.attr('class', 'option-group')
// make the checkbox
var name = 'identifiers_on_map'
style_enter.append('input').attr('type', 'radio')
.attr('name', name + this.unique_string)
.attr('value', function (d) { return d[1] })
.on('change', function () {
if (this.checked) {
settings.set_conditional(name, this.value)
}
})
.each(function () {
// subscribe to changes in the model
settings.streams[name].onValue(function (value) {
// check the box for the new value
this.checked = (this.value == value)
}.bind(this))
})
style_enter.append('span').text(function (d) { return d[0] })
}.bind(this))
var boolean_options = [
['scroll_behavior', 'Scroll to zoom (instead of scroll to pan)',
('If checked, then the scroll wheel and trackpad will control zoom ' +
'rather than pan.'), {'zoom': true, 'pan': false}],
['hide_secondary_metabolites', 'Hide secondary metabolites',
('If checked, then only the primary metabolites ' +
'will be displayed.')],
['show_gene_reaction_rules', 'Show gene reaction rules',
('If checked, then gene reaction rules will be displayed ' +
'below each reaction label. (Gene reaction rules are always ' +
'shown when gene data is loaded.)')],
['hide_all_labels', 'Hide reaction, gene, and metabolite labels',
('If checked, hide all reaction, gene, and metabolite labels')],
['allow_building_duplicate_reactions', 'Allow duplicate reactions',
('If checked, then allow duplicate reactions during model building.')],
['highlight_missing', 'Highlight reactions not in model',
('If checked, then highlight in red all the ' +
'reactions on the map that are not present in ' +
'the loaded model.')],
['enable_tooltips', 'Show tooltips',
('Show tooltips when hovering over reactions, metabolites, and genes')],
]
var opts = s.append('div').attr('class', 'settings-container')
.selectAll('.option-group')
.data(boolean_options)
// enter
var e = opts.enter()
.append('label')
.attr('class', 'option-group full-line')
e.append('input').attr('type', 'checkbox')
e.append('span')
// update
var opts_update = e.merge(opts)
opts_update.attr('title', function (d) { return d[2]; })
opts_update.select('input')
.on('change', function (d) {
if (d.length >= 4) { // not a boolean setting
for (var key in d[3]) {
if (d[3][key] == this.checked) {
settings.set_conditional(d[0], key)
break
}
}
} else { // boolean setting
settings.set_conditional(d[0], this.checked)
}
})
.each(function (d) {
settings.streams[d[0]].onValue(function (value) {
if (d.length >= 4) { // not a boolean setting
this.checked = d[3][value]
} else { // boolean setting
this.checked = value
}
}.bind(this))
})
opts_update.select('span').text(function (d) { return d[1] })
// exit
opts.exit().remove()
// message about text performance
s.append('div')
.style('margin-top', '16px')
.classed('settings-tip', true)
.text('Tip: To increase map performance, turn off text boxes (i.e. ' +
'labels and gene reaction rules).')
}