escher-vis
Version:
Escher: A Web Application for Building, Sharing, and Embedding Data-Rich Visualizations of Biological Pathways
175 lines (143 loc) • 4.99 kB
JavaScript
/** Settings. A class to manage settings for a Map.
Arguments
---------
set_option: A function, fn(key), that returns the option value for the
key.
get_option: A function, fn(key, value), that sets the option for the key
and value.
conditional_options: The options to that are conditionally accepted when
changed. Changes can be abandoned by calling abandon_changes(), or accepted
by calling accept_changes().
*/
var utils = require('./utils');
var bacon = require('baconjs');
var Settings = utils.make_class();
// instance methods
Settings.prototype = {
init: init,
set_conditional: set_conditional,
hold_changes: hold_changes,
abandon_changes: abandon_changes,
accept_changes: accept_changes
};
module.exports = Settings;
// instance methods
function init (set_option, get_option, conditional_options) {
this.set_option = set_option
this.get_option = get_option
// manage accepting/abandoning changes
this.status_bus = new bacon.Bus()
// force an update of ui components
this.force_update_bus = new bacon.Bus()
// modify bacon.observable
bacon.Observable.prototype.convert_to_conditional_stream = _convert_to_conditional_stream
bacon.Observable.prototype.force_update_with_bus = _force_update_with_bus
// create the options
this.busses = {}
this.streams = {}
for (var i = 0, l = conditional_options.length; i < l; i++) {
var name = conditional_options[i]
var out = _create_conditional_setting(name, get_option(name), set_option,
this.status_bus,
this.force_update_bus)
this.busses[name] = out.bus
this.streams[name] = out.stream
}
}
/**
* Hold on to event when hold_property is true, and only keep them if
* accept_property is true (when hold_property becomes false).
*/
function _convert_to_conditional_stream (status_stream) {
// true if hold is pressed
var is_not_hold_event = status_stream
.map(function(x) { return x=='hold'; })
.not()
.toProperty(true),
is_not_first_clear = status_stream
.scan(false, function(c, x) {
// first clear only
return (c==false && (x=='accepted' || x=='rejected'));
}).not(),
is_not_first_hold = status_stream
.scan(false, function(c, x) {
// first clear only
return (c==false && x=='hold');
}).not(),
combined = bacon.combineAsArray(this, status_stream),
held = combined
.scan([], function(c, x) {
if (x[1]=='hold') {
c.push(x[0]);
return c;
} else if (x[1]=='accept') {
return c;
} else if (x[1]=='reject') {
return [];
} else if (x[1]=='rejected' || x[1]=='accepted') {
return [x[0]];
} else {
throw Error('bad status ' + x[1]);
}
})
// don't pass in events until the end of a hold
.filter(is_not_hold_event)
// ignore the event when clear is passed
.filter(is_not_first_clear)
// ignore the event when hold is passed
.filter(is_not_first_hold)
.flatMap(function(ar) {
return bacon.fromArray(ar);
}),
unheld = this.filter(is_not_hold_event);
return unheld.merge(held);
}
function _force_update_with_bus(bus) {
return bacon
.combineAsArray(this, bus.toProperty(false))
.map(function(t) {
return t[0];
});
}
function _create_conditional_setting(name, initial_value, set_option,
status_bus, force_update_bus) {
// set up the bus
var bus = new bacon.Bus();
// make the event stream
var stream = bus
// conditionally accept changes
.convert_to_conditional_stream(status_bus)
// force updates
.force_update_with_bus(force_update_bus);
// get the latest
stream.onValue(function(v) {
set_option(name, v);
});
// push the initial value
bus.push(initial_value);
return { bus: bus, stream: stream };
}
function set_conditional(name, value) {
/** Set the value of a conditional setting, one that will only be
accepted if this.accept_changes() is called.
Arguments
---------
name: The option name
value: The new value
*/
if (!(name in this.busses))
throw new Error('Invalid setting name');
this.busses[name].push(value);
}
function hold_changes() {
this.status_bus.push('hold');
}
function abandon_changes() {
this.status_bus.push('reject');
this.status_bus.push('rejected');
this.force_update_bus.push(true);
}
function accept_changes() {
this.status_bus.push('accept');
this.status_bus.push('accepted');
}