Files
galaxy/static/scripts/viz/visualization.js
T

1004 lines
33 KiB
JavaScript

define( ["libs/underscore", "mvc/data", "viz/trackster/util", "utils/config"],
function(_, data_mod, util_mod, config_mod) {
/**
* Model, view, and controller objects for Galaxy visualization framework.
*
* Models have no references to views, instead using events to indicate state
* changes; this is advantageous because multiple views can use the same object
* and models can be used without views.
*/
/**
* Use a popup grid to select datasets from histories or libraries. After datasets are selected,
* track definitions are obtained from the server and the success_fn is called with the list of
* definitions for selected datasets.
*/
var select_datasets = function(dataset_url, add_track_async_url, filters, success_fn) {
$.ajax({
url: dataset_url,
data: filters,
error: function() { alert( "Grid failed" ); },
success: function(table_html) {
show_modal(
"Select datasets for new tracks",
table_html, {
"Cancel": function() {
hide_modal();
},
"Add": function() {
var requests = [];
$('input[name=id]:checked,input[name=ldda_ids]:checked').each(function() {
var data = {
data_type: 'track_config',
'hda_ldda': 'hda'
},
id = $(this).val();
if ($(this).attr("name") !== "id") {
data.hda_ldda = 'ldda';
}
requests[requests.length] = $.ajax({
url: add_track_async_url + "/" + id,
data: data,
dataType: "json"
});
});
// To preserve order, wait until there are definitions for all tracks and then add
// them sequentially.
$.when.apply($, requests).then(function() {
// jQuery always returns an Array for arguments, so need to look at first element
// to determine whether multiple requests were made and consequently how to
// map arguments to track definitions.
var track_defs = (arguments[0] instanceof Array ?
$.map(arguments, function(arg) { return arg[0]; }) :
[ arguments[0] ]
);
success_fn(track_defs);
});
hide_modal();
}
}
);
}
});
};
/**
* Helper to determine if object is jQuery deferred.
*/
var is_deferred = function ( d ) {
return ( 'isResolved' in d );
};
// --------- Models ---------
/**
* Canvas manager is used to create canvases, for browsers, this deals with
* backward comparibility using excanvas, as well as providing a pattern cache
*/
var CanvasManager = function(default_font) {
this.default_font = default_font !== undefined ? default_font : "9px Monaco, Lucida Console, monospace";
this.dummy_canvas = this.new_canvas();
this.dummy_context = this.dummy_canvas.getContext('2d');
this.dummy_context.font = this.default_font;
this.char_width_px = this.dummy_context.measureText("A").width;
this.patterns = {};
// FIXME: move somewhere to make this more general
this.load_pattern( 'right_strand', "/visualization/strand_right.png" );
this.load_pattern( 'left_strand', "/visualization/strand_left.png" );
this.load_pattern( 'right_strand_inv', "/visualization/strand_right_inv.png" );
this.load_pattern( 'left_strand_inv', "/visualization/strand_left_inv.png" );
};
_.extend( CanvasManager.prototype, {
load_pattern: function( key, path ) {
var patterns = this.patterns,
dummy_context = this.dummy_context,
image = new Image();
image.src = galaxy_paths.attributes.image_path + path;
image.onload = function() {
patterns[key] = dummy_context.createPattern( image, "repeat" );
};
},
get_pattern: function( key ) {
return this.patterns[key];
},
new_canvas: function() {
var canvas = $("<canvas/>")[0];
// If using excanvas in IE, we need to explicately attach the canvas
// methods to the DOM element
if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); }
// Keep a reference back to the manager
canvas.manager = this;
return canvas;
}
});
/**
* Generic cache that handles key/value pairs.
*/
var Cache = Backbone.Model.extend({
defaults: {
num_elements: 20,
obj_cache: null,
key_ary: null
},
initialize: function(options) {
this.clear();
},
get_elt: function(key) {
var obj_cache = this.attributes.obj_cache,
key_ary = this.attributes.key_ary,
index = key_ary.indexOf(key);
if (index !== -1) {
if (obj_cache[key].stale) {
// Object is stale, so remove key and object.
key_ary.splice(index, 1);
delete obj_cache[key];
}
else {
this.move_key_to_end(key, index);
}
}
return obj_cache[key];
},
set_elt: function(key, value) {
var obj_cache = this.attributes.obj_cache,
key_ary = this.attributes.key_ary,
num_elements = this.attributes.num_elements;
if (!obj_cache[key]) {
if (key_ary.length >= num_elements) {
// Remove first element
var deleted_key = key_ary.shift();
delete obj_cache[deleted_key];
}
key_ary.push(key);
}
obj_cache[key] = value;
return value;
},
/**
* Move key to end of cache. Keys are removed from the front, so moving a key to the end
* delays the key's removal.
*/
move_key_to_end: function(key, index) {
this.attributes.key_ary.splice(index, 1);
this.attributes.key_ary.push(key);
},
/**
* Clear all elements from the cache.
*/
clear: function() {
this.attributes.obj_cache = {};
this.attributes.key_ary = [];
},
/** Returns the number of elements in the cache. */
size: function() {
return this.attributes.key_ary.length;
},
/** Returns key most recently added to cache. */
most_recently_added: function() {
return this.size() === 0 ? null :
// Most recent key is at the end of key array.
this.attributes.key_ary[this.attributes.key_ary.length - 1];
}
});
/**
* Data manager for genomic data. Data is connected to and queryable by genomic regions.
*/
var GenomeDataManager = Cache.extend({
defaults: _.extend({}, Cache.prototype.defaults, {
dataset: null,
genome: null,
init_data: null,
min_region_size: 200,
filters_manager: null,
data_type: "data",
data_mode_compatible: function(entry, mode) { return true; },
can_subset: function(entry) { return false; }
}),
/**
* Initialization.
*/
initialize: function(options) {
Cache.prototype.initialize.call(this);
// Set initial entries in data manager.
var initial_entries = this.get('init_data');
if (initial_entries) {
this.add_data(initial_entries);
}
},
/**
* Add data entries to manager; each entry should be a dict with attributes region (key), data, and data_type.
* If necessary, manager size is increased to hold all data.
*/
add_data: function(entries) {
// Increase size to accomodate all entries.
if (this.get('num_elements') < entries.length) {
this.set('num_elements', entries.length);
}
// Put data into manager.
var self = this;
_.each(entries, function(entry) {
self.set_data(entry.region, entry);
});
},
/**
* Returns deferred that resolves to true when dataset is ready (or false if dataset
* cannot be used).
*/
data_is_ready: function() {
var dataset = this.get('dataset'),
ready_deferred = $.Deferred(),
// If requesting raw data, query dataset state; if requesting (converted) data,
// need to query converted datasets state.
query_type = (this.get('data_type') === 'raw_data' ? 'state' :
this.get('data_type') === 'data' ? 'converted_datasets_state' : "error" ),
ss_deferred = new util_mod.ServerStateDeferred({
ajax_settings: {
url: this.get('dataset').url(),
data: {
hda_ldda: dataset.get('hda_ldda'),
data_type: query_type
},
dataType: "json"
},
interval: 5000,
success_fn: function(response) { return response !== "pending"; }
});
$.when(ss_deferred.go()).then(function(response) {
ready_deferred.resolve(response === "ok" || response === "data" );
});
return ready_deferred;
},
/**
* Perform a feature search from server; returns Deferred object that resolves when data is available.
*/
search_features: function(query) {
var dataset = this.get('dataset'),
params = {
query: query,
hda_ldda: dataset.get('hda_ldda'),
data_type: 'features'
};
return $.getJSON(dataset.url(), params);
},
/**
* Load data from server and manages data entries. Adds a Deferred to manager
* for region; when data becomes available, replaces Deferred with data.
* Returns the Deferred that resolves when data is available.
*/
load_data: function(region, mode, resolution, extra_params) {
// Setup data request params.
var dataset = this.get('dataset'),
params = {
"data_type": this.get('data_type'),
"chrom": region.get('chrom'),
"low": region.get('start'),
"high": region.get('end'),
"mode": mode,
"resolution": resolution,
"hda_ldda": dataset.get('hda_ldda')
};
$.extend(params, extra_params);
// Add track filters to params.
var filters_manager = this.get('filters_manager');
if (filters_manager) {
var filter_names = [];
var filters = filters_manager.filters;
for (var i = 0; i < filters.length; i++) {
filter_names.push(filters[i].name);
}
params.filter_cols = JSON.stringify(filter_names);
}
// Do request.
var manager = this,
entry = $.getJSON(dataset.url(), params, function (result) {
// Add region to the result.
result.region = region;
manager.set_data(region, result);
});
this.set_data(region, entry);
return entry;
},
/**
* Get data from dataset.
*/
get_data: function(region, mode, resolution, extra_params) {
// Debugging:
//console.log("get_data", low, high, mode);
/*
console.log("cache contents:")
for (var i = 0; i < this.key_ary.length; i++) {
console.log("\t", this.key_ary[i], this.obj_cache[this.key_ary[i]]);
}
*/
// Look for entry and return if it's a deferred or if data available is compatible with mode.
var entry = this.get_elt(region);
if ( entry &&
( is_deferred(entry) || this.get('data_mode_compatible')(entry, mode) ) ) {
return entry;
}
//
// Look in cache for data that can be used. Data can be reused if it
// has the requested data and is not summary tree and has details.
// TODO: this logic could be improved if the visualization knew whether
// the data was "index" or "data."
//
var key_ary = this.get('key_ary'),
obj_cache = this.get('obj_cache'),
key, entry_region, is_subregion;
for (var i = 0; i < key_ary.length; i++) {
key = key_ary[i];
entry_region = new GenomeRegion({from_str: key});
if (entry_region.contains(region)) {
is_subregion = true;
// This entry has data in the requested range. Return if data
// is compatible and can be subsetted.
entry = obj_cache[key];
if ( is_deferred(entry) ||
( this.get('data_mode_compatible')(entry, mode) && this.get('can_subset')(entry) ) ) {
this.move_key_to_end(key, i);
// If there's data, subset it.
if ( !is_deferred(entry) ) {
var subset_entry = this.subset_entry(entry, region);
this.set(region, subset_entry);
entry = subset_entry;
}
return entry;
}
}
}
// FIXME: There _may_ be instances where region is a subregion of another entry but cannot be
// subsetted. For these cases, do not increase length because region will never be found (and
// an infinite loop will occur.)
// If needed, extend region to make it minimum size.
if (!is_subregion && region.length() < this.attributes.min_region_size) {
// IDEA: alternative heuristic is to find adjacent cache entry to region and use that to extend.
// This would prevent bad extensions when zooming in/out while still preserving the behavior
// below.
// Use heuristic to extend region: extend relative to last data request.
var last_request = new GenomeRegion({from_str: this.most_recently_added()});
if (!last_request || (region.get('start') > last_request.get('start'))) {
// This request is after the last request, so extend right.
region.set('end', region.get('start') + this.attributes.min_region_size);
}
else {
// This request is after the last request, so extend left.
region.set('start', region.get('end') - this.attributes.min_region_size);
}
// Trim region to avoid invalid coordinates.
region.set('genome', this.attributes.genome);
region.trim();
}
return this.load_data(region, mode, resolution, extra_params);
},
/**
* Alias for set_elt for readbility.
*/
set_data: function(region, entry) {
this.set_elt(region, entry);
},
/** "Deep" data request; used as a parameter for DataManager.get_more_data() */
DEEP_DATA_REQ: "deep",
/** "Broad" data request; used as a parameter for DataManager.get_more_data() */
BROAD_DATA_REQ: "breadth",
/**
* Gets more data for a region using either a depth-first or a breadth-first approach.
*/
get_more_data: function(region, mode, resolution, extra_params, req_type) {
var cur_data = this._mark_stale(region);
if (!(cur_data && this.get('data_mode_compatible')(cur_data, mode))) {
console.log('ERROR: problem with getting more data: current data is not compatible');
return;
}
//
// Set parameters based on request type.
//
var query_low = region.get('start');
if (req_type === this.DEEP_DATA_REQ) {
// Use same interval but set start_val to skip data that's already in cur_data.
$.extend(extra_params, {start_val: cur_data.data.length + 1});
}
else if (req_type === this.BROAD_DATA_REQ) {
// To get past an area of extreme feature depth, set query low to be after either
// (a) the maximum high or HACK/FIXME (b) the end of the last feature returned.
query_low = (cur_data.max_high ? cur_data.max_high : cur_data.data[cur_data.data.length - 1][2]) + 1;
}
var query_region = region.copy().set('start', query_low);
//
// Get additional data, append to current data, and set new data. Use a custom deferred object
// to signal when new data is available.
//
var data_manager = this,
new_data_request = this.load_data(query_region, mode, resolution, extra_params),
new_data_available = $.Deferred();
// load_data sets cache to new_data_request, but use custom deferred object so that signal and data
// is all data, not just new data.
this.set_data(region, new_data_available);
$.when(new_data_request).then(function(result) {
// Update data and message.
if (result.data) {
result.data = cur_data.data.concat(result.data);
if (result.max_low) {
result.max_low = cur_data.max_low;
}
if (result.message) {
// HACK: replace number in message with current data length. Works but is ugly.
result.message = result.message.replace(/[0-9]+/, result.data.length);
}
}
data_manager.set_data(region, result);
new_data_available.resolve(result);
});
return new_data_available;
},
/**
* Returns true if more detailed data can be obtained for entry.
*/
can_get_more_detailed_data: function(region) {
var cur_data = this.get_elt(region);
// Can only get more detailed data for bigwig data that has less than 8000 data points.
// Summary tree returns *way* too much data, and 8000 data points ~ 500KB.
return (cur_data.dataset_type === 'bigwig' && cur_data.data.length < 8000);
},
/**
* Returns more detailed data for an entry.
*/
get_more_detailed_data: function(region, mode, resolution, detail_multiplier, extra_params) {
// Mark current entry as stale.
var cur_data = this._mark_stale(region);
if (!cur_data) {
console.log("ERROR getting more detailed data: no current data");
return;
}
if (!extra_params) { extra_params = {}; }
// Use additional parameters to get more detailed data.
if (cur_data.dataset_type === 'bigwig') {
// FIXME: constant should go somewhere.
extra_params.num_samples = 1000 * detail_multiplier;
}
return this.load_data(region, mode, resolution, extra_params);
},
/**
* Marks cache data as stale.
*/
_mark_stale: function(region) {
var entry = this.get_elt(region);
if (!entry) {
console.log("ERROR: no data to mark as stale: ", this.get('dataset'), region.toString());
}
entry.stale = true;
return entry;
},
/**
* Returns an array of data with each entry representing one chromosome/contig
* of data or, if data is not available, returns a Deferred that resolves to the
* data when it becomes available.
*/
get_genome_wide_data: function(genome) {
// -- Get all data. --
var self = this,
all_data_available = true,
// Map chromosome info into genome data.
gw_data = _.map(genome.get('chroms_info').chrom_info, function(chrom_info) {
var chrom_data = self.get_elt(
new GenomeRegion({
chrom: chrom_info.chrom,
start: 0,
end: chrom_info.len
})
);
// Set flag if data is not available.
if (!chrom_data) { all_data_available = false; }
return chrom_data;
});
// -- If all data is available, return it. --
if (all_data_available) {
return gw_data;
}
// -- All data is not available, so load from server. --
var deferred = $.Deferred();
$.getJSON(this.get('dataset').url(), { data_type: 'genome_data' }, function(genome_wide_data) {
self.add_data(genome_wide_data.data);
deferred.resolve(genome_wide_data.data);
});
return deferred;
},
/**
* Returns entry with only data in the subregion.
*/
subset_entry: function(entry, subregion) {
// Dictionary from entry type to function for subsetting data.
var subset_fns = {
bigwig: function(data, subregion) {
return _.filter(data, function(data_point) {
return data_point[0] >= subregion.get('start') &&
data_point[0] <= subregion.get('end');
});
},
refseq: function(data, subregion) {
var seq_start = subregion.get('start') - entry.region.get('start'),
seq_end = entry.data.length - ( entry.region.get('end') - subregion.get('end') );
return entry.data.slice(seq_start, seq_end);
}
};
// Subset entry if there is a function for subsetting and regions are not the same.
var subregion_data = entry.data;
if (!entry.region.same(subregion) && entry.dataset_type in subset_fns) {
subregion_data = subset_fns[entry.dataset_type](entry.data, subregion);
}
// Return entry with subregion's data.
return {
region: subregion,
data: subregion_data,
dataset_type: entry.dataset_type
};
},
/**
* Get data from the cache.
*/
get_elt: function(region) {
return Cache.prototype.get_elt.call(this, region.toString());
},
/**
* Sets data in the cache.
*/
set_elt: function(region, result) {
return Cache.prototype.set_elt.call(this, region.toString(), result);
}
});
var GenomeReferenceDataManager = GenomeDataManager.extend({
initialize: function(options) {
// Use generic object in place of dataset and set urlRoot to fetch data.
var dataset_placeholder = new Backbone.Model();
dataset_placeholder.urlRoot = options.data_url;
this.set('dataset', dataset_placeholder);
},
load_data: function(region, mode, resolution, extra_params) {
// Fetch data if region is not too large.
return ( region.length() <= 100000 ?
GenomeDataManager.prototype.load_data.call(this, region, mode, resolution, extra_params) :
{ data: null, region: region } );
}
});
/**
* A genome build.
*/
var Genome = Backbone.Model.extend({
defaults: {
name: null,
key: null,
chroms_info: null
},
initialize: function(options) {
this.id = options.dbkey;
},
/**
* Shorthand for getting to chromosome information.
*/
get_chroms_info: function() {
return this.attributes.chroms_info.chrom_info;
},
/**
* Returns a GenomeRegion object denoting a complete chromosome.
*/
get_chrom_region: function(chr_name) {
// FIXME: use findWhere in underscore 1.4
var chrom_info = _.find(this.get_chroms_info(), function(chrom_info) {
return chrom_info.chrom === chr_name;
});
return new GenomeRegion({
chrom: chrom_info.chrom,
end: chrom_info.len
});
},
/** Returns the length of a chromosome. */
get_chrom_len: function(chr_name) {
// FIXME: use findWhere in underscore 1.4
return _.find(this.get_chroms_info(), function(chrom_info) {
return chrom_info.chrom === chr_name;
}).len;
}
});
/**
* A genomic region.
*/
var GenomeRegion = Backbone.RelationalModel.extend({
defaults: {
chrom: null,
start: 0,
end: 0,
genome: null
},
/**
* Returns true if this region is the same as a given region.
* It does not test the genome right now.
*/
same: function(region) {
return this.attributes.chrom === region.get('chrom') &&
this.attributes.start === region.get('start') &&
this.attributes.end === region.get('end');
},
/**
* If from_str specified, use it to initialize attributes.
*/
initialize: function(options) {
if (options.from_str) {
var pieces = options.from_str.split(':'),
chrom = pieces[0],
start_end = pieces[1].split('-');
this.set({
chrom: chrom,
start: parseInt(start_end[0], 10),
end: parseInt(start_end[1], 10)
});
}
},
copy: function() {
return new GenomeRegion({
chrom: this.get('chrom'),
start: this.get('start'),
end: this.get('end')
});
},
length: function() {
return this.get('end') - this.get('start');
},
/** Returns region in canonical form chrom:start-end */
toString: function() {
return this.get('chrom') + ":" + this.get('start') + "-" + this.get('end');
},
toJSON: function() {
return {
chrom: this.get('chrom'),
start: this.get('start'),
end: this.get('end')
};
},
/**
* Compute the type of overlap between this region and another region. The overlap is computed relative to the given/second region;
* hence, OVERLAP_START indicates that the first region overlaps the start (but not the end) of the second region.
*/
compute_overlap: function(a_region) {
var first_chrom = this.get('chrom'), second_chrom = a_region.get('chrom'),
first_start = this.get('start'), second_start = a_region.get('start'),
first_end = this.get('end'), second_end = a_region.get('end'),
overlap;
// Compare chroms.
if (first_chrom && second_chrom && first_chrom !== second_chrom) {
return GenomeRegion.overlap_results.DIF_CHROMS;
}
// Compare regions.
if (first_start < second_start) {
if (first_end < second_start) {
overlap = GenomeRegion.overlap_results.BEFORE;
}
else if (first_end < second_end) {
overlap = GenomeRegion.overlap_results.OVERLAP_START;
}
else { // first_end >= second_end
overlap = GenomeRegion.overlap_results.CONTAINS;
}
}
else if (first_start > second_start) {
if (first_start > second_end) {
overlap = GenomeRegion.overlap_results.AFTER;
}
else if (first_end <= second_end) {
overlap = GenomeRegion.overlap_results.CONTAINED_BY;
}
else {
overlap = GenomeRegion.overlap_results.OVERLAP_END;
}
}
else { // first_start === second_start
overlap = (first_end >= second_end ?
GenomeRegion.overlap_results.CONTAINS :
GenomeRegion.overlap_results.CONTAINED_BY);
}
return overlap;
},
/**
* Trim a region to match genome's constraints.
*/
trim: function(genome) {
// Assume that all chromosome/contigs start at 0.
if (this.attributes.start < 0) {
this.attributes.start = 0;
}
// Only try to trim the end if genome is set.
if (this.attributes.genome) {
var chrom_len = this.attributes.genome.get_chrom_len(this.attributes.chrom);
if (this.attributes.end > chrom_len) {
this.attributes.end = chrom_len - 1;
}
}
return this;
},
/**
* Returns true if this region contains a given region.
*/
contains: function(a_region) {
return this.compute_overlap(a_region) === GenomeRegion.overlap_results.CONTAINS;
},
/**
* Returns true if regions overlap.
*/
overlaps: function(a_region) {
return _.intersection( [this.compute_overlap(a_region)],
[GenomeRegion.overlap_results.DIF_CHROMS, GenomeRegion.overlap_results.BEFORE, GenomeRegion.overlap_results.AFTER] ).length === 0;
}
},
{
overlap_results: {
DIF_CHROMS: 1000,
BEFORE: 1001,
CONTAINS: 1002,
OVERLAP_START: 1003,
OVERLAP_END: 1004,
CONTAINED_BY: 1005,
AFTER: 1006
}
});
var GenomeRegionCollection = Backbone.Collection.extend({
model: GenomeRegion
});
/**
* A genome browser bookmark.
*/
var BrowserBookmark = Backbone.RelationalModel.extend({
defaults: {
region: null,
note: ''
},
relations: [
{
type: Backbone.HasOne,
key: 'region',
relatedModel: GenomeRegion
}
]
});
/**
* Bookmarks collection.
*/
var BrowserBookmarkCollection = Backbone.Collection.extend({
model: BrowserBookmark
});
/**
* A track of data in a genome visualization.
*/
// TODO: rename to Track and merge with Trackster's Track object.
var BackboneTrack = data_mod.Dataset.extend({
initialize: function(options) {
// Dataset id is unique ID for now.
this.set('id', options.dataset_id);
// -- Set up config settings. --
this.set('config', config_mod.ConfigSettingCollection.from_config_dict(options.prefs));
// Set up some minimal config.
this.get('config').add( [
{ key: 'name', value: this.get('name') },
{ key: 'color' }
] );
// -- Set up data manager. --
var preloaded_data = this.get('preloaded_data');
if (preloaded_data) {
preloaded_data = preloaded_data.data;
}
else {
preloaded_data = [];
}
this.set('data_manager', new GenomeDataManager({
dataset: this,
init_data: preloaded_data
}));
}
});
/**
* A visualization.
*/
var Visualization = Backbone.RelationalModel.extend({
defaults: {
title: '',
type: ''
},
// No API to create/save visualization yet, so use this path:
url: galaxy_paths.get("visualization_url"),
/**
* POSTs visualization's JSON to its URL using the parameter 'vis_json'
* Note: This is necessary because (a) Galaxy requires keyword args and
* (b) Galaxy does not handle PUT now.
*/
save: function() {
return $.ajax({
url: this.url(),
type: "POST",
dataType: "json",
data: {
vis_json: JSON.stringify(this)
}
});
}
});
/**
* A visualization of genome data.
*/
var GenomeVisualization = Visualization.extend({
defaults: _.extend({}, Visualization.prototype.defaults, {
dbkey: '',
tracks: null,
bookmarks: null,
viewport: null
}),
relations: [
{
type: Backbone.HasMany,
key: 'tracks',
relatedModel: BackboneTrack
}
],
/**
* Add a track or array of tracks to the visualization.
*/
add_tracks: function(tracks) {
this.get('tracks').add(tracks);
}
});
/**
* Configuration data for a Trackster track.
*/
var TrackConfig = Backbone.Model.extend({
});
/**
* -- Routers --
*/
/**
* Router for track browser.
*/
var TrackBrowserRouter = Backbone.Router.extend({
initialize: function(options) {
this.view = options.view;
// Can't put regular expression in routes dictionary.
// NOTE: parentheses are used to denote parameters returned to callback.
this.route(/([\w]+)$/, 'change_location');
this.route(/([\w]+\:[\d,]+-[\d,]+)$/, 'change_location');
// Handle navigate events from view.
var self = this;
self.view.on("navigate", function(new_loc) {
self.navigate(new_loc);
});
},
change_location: function(new_loc) {
this.view.go_to(new_loc);
}
});
return {
BackboneTrack: BackboneTrack,
BrowserBookmark: BrowserBookmark,
BrowserBookmarkCollection: BrowserBookmarkCollection,
Cache: Cache,
CanvasManager: CanvasManager,
Genome: Genome,
GenomeDataManager: GenomeDataManager,
GenomeRegion: GenomeRegion,
GenomeRegionCollection: GenomeRegionCollection,
GenomeVisualization: GenomeVisualization,
GenomeReferenceDataManager: GenomeReferenceDataManager,
TrackBrowserRouter: TrackBrowserRouter,
TrackConfig: TrackConfig,
Visualization: Visualization,
select_datasets: select_datasets
};
});