/static/scripts/viz/visualization.js
JavaScript | 1081 lines | 702 code | 124 blank | 255 comment | 67 complexity | b3049583148c0a4ca7516ab6572949e5 MD5 | raw file
Possible License(s): CC-BY-3.0
- define( ["libs/underscore", "mvc/data", "viz/trackster/util", "utils/config"], function(_, data_mod, util_mod, config_mod) {
- /**
- * Mixin for returning custom JSON representation from toJSON. Class attribute to_json_keys defines a set of attributes
- * to include in the representation; to_json_mappers defines mappers for returned objects.
- */
- var CustomToJSON = {
- /**
- * Returns JSON representation of object using to_json_keys and to_json_mappers.
- */
- toJSON: function() {
- var self = this,
- json = {};
- _.each(self.constructor.to_json_keys, function(k) {
- var val = self.get(k);
- if (k in self.constructor.to_json_mappers) {
- val = self.constructor.to_json_mappers[k](val, self);
- }
- json[k] = val;
- });
- return json;
- }
- };
- /**
- * 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) {
- Galaxy.modal.show({
- title : "Select datasets for new tracks",
- body : table_html,
- buttons :
- {
- "Cancel": function() {
- Galaxy.modal.hide();
- },
- "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);
- });
- Galaxy.modal.hide();
- }
- }
- });
- }
- });
- };
- // --------- 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_config.root + "static/images" + 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. Keys can be any object that can be
- * converted to a String and compared.
- */
- var Cache = Backbone.Model.extend({
- defaults: {
- num_elements: 20,
- // Objects in cache; indexes into cache are strings of keys.
- obj_cache: null,
- // key_ary contains keys for objects in cache.
- key_ary: null
- },
- initialize: function(options) {
- this.clear();
- },
- /**
- * Get an element from the cache using its key.
- */
- get_elt: function(key) {
- var obj_cache = this.attributes.obj_cache,
- key_ary = this.attributes.key_ary,
- key_str = key.toString(),
- index = _.indexOf(key_ary, function(k) {
- return k.toString() === key_str;
- });
- // Update cache.
- if (index !== -1) {
- // Object is in cache, so update it.
- if (obj_cache[key_str].stale) {
- // Object is stale: remove key and object.
- key_ary.splice(index, 1);
- delete obj_cache[key_str];
- }
- else {
- // Move key to back because it is most recently used.
- this.move_key_to_end(key, index);
- }
- }
- return obj_cache[key_str];
- },
-
- /**
- * Put an element into the cache.
- */
- set_elt: function(key, value) {
- var obj_cache = this.attributes.obj_cache,
- key_ary = this.attributes.key_ary,
- key_str = key.toString(),
- num_elements = this.attributes.num_elements;
- // Update keys, objects.
- if (!obj_cache[key_str]) {
- // Add object to cache.
- if (key_ary.length >= num_elements) {
- // Cache full, so remove first element.
- var deleted_key = key_ary.shift();
- delete obj_cache[deleted_key.toString()];
- }
- // Add key.
- key_ary.push(key);
- }
- // Add object.
- obj_cache[key_str] = 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) {
- // 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 &&
- ( util_mod.is_deferred(entry) || this.get('data_mode_compatible')(entry, mode) ) ) {
- return entry;
- }
- //
- // Look in cache for data that can be used.
- // 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'),
- entry_region, is_subregion;
- for (var i = 0; i < key_ary.length; i++) {
- entry_region = key_ary[i];
-
- 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[entry_region.toString()];
- if ( util_mod.is_deferred(entry) ||
- ( this.get('data_mode_compatible')(entry, mode) && this.get('can_subset')(entry) ) ) {
- this.move_key_to_end(entry_region, i);
- // If there's data, subset it.
- if ( !util_mod.is_deferred(entry) ) {
- var subset_entry = this.subset_entry(entry, region);
- this.set_data(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 copy of region to avoid changing actual region.
- region = region.copy();
- // Use heuristic to extend region: extend relative to last data request.
- var last_request = 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');
- return entry.data.slice(seq_start, seq_start + subregion.length());
- }
- };
- // 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
- };
- }
- });
- 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.Model.extend({
- defaults: {
- chrom: null,
- start: 0,
- end: 0,
- str_val: null,
- 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)
- });
- }
- // Keep a copy of region's string value for fast lookup.
- this.attributes.str_val = this.get('chrom') + ":" + this.get('start') + "-" + this.get('end');
- // Set str_val on attribute change.
- this.on('change', function() {
- this.attributes.str_val = this.get('chrom') + ":" + this.get('start') + "-" + this.get('end');
- }, this);
- },
-
- 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.attributes.str_val;
- },
-
- 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.Model.extend({
- defaults: {
- region: null,
- note: ''
- },
- initialize: function(options) {
- this.set('region', new GenomeRegion(options.region));
- }
- });
- /**
- * 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 = Backbone.Model.extend(CustomToJSON).extend({
- defaults: {
- mode: 'Auto'
- },
- initialize: function(options) {
- this.set('dataset', new data_mod.Dataset(options.dataset));
- // -- Set up config settings. --
- var models = [
- { key: 'name', default_value: this.get('dataset').get('name') },
- { key: 'color' },
- { key: 'min_value', label: 'Min Value', type: 'float', default_value: 0 },
- { key: 'max_value', label: 'Max Value', type: 'float', default_value: 1 }
- ];
- this.set('config', config_mod.ConfigSettingCollection.from_models_and_saved_values(models, options.prefs));
- // -- 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.get('dataset'),
- init_data: preloaded_data
- }));
- }
- },
- {
- // This definition matches that produced by to_dict() methods in tracks.js
- to_json_keys: [
- 'track_type',
- 'dataset',
- 'prefs',
- 'mode',
- 'filters',
- 'tool_state'
- ],
- to_json_mappers: {
- prefs: function(p, self) {
- if (_.size(p) === 0) {
- p = {
- name: self.get('config').get('name').get('value'),
- color: self.get('config').get('color').get('value')
- };
- }
- return p;
- },
- dataset: function(d) {
- return {
- id: d.id,
- hda_ldda: d.get('hda_ldda')
- };
- }
- }
- });
- var BackboneTrackCollection = Backbone.Collection.extend({
- model: BackboneTrack
- });
- /**
- * A visualization.
- */
- var Visualization = Backbone.Model.extend({
- defaults: {
- title: '',
- type: ''
- },
- // No API to create/save visualization yet, so use this path:
- url: galaxy_config.root + "visualization/save",
-
- /**
- * 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(CustomToJSON).extend({
- defaults: _.extend({}, Visualization.prototype.defaults, {
- dbkey: '',
- drawables: null,
- bookmarks: null,
- viewport: null
- }),
- initialize: function(options) {
- // Replace drawables with tracks.
- this.set('drawables', new BackboneTrackCollection(options.tracks));
- var models = [];
- this.set('config', config_mod.ConfigSettingCollection.from_models_and_saved_values(models, options.prefs));
-
- // Clear track and data definitions to avoid storing large objects.
- this.unset('tracks');
- this.get('drawables').each(function(d) {
- d.unset('preloaded_data');
- });
- },
- /**
- * Add a track or array of tracks to the visualization.
- */
- add_tracks: function(tracks) {
- this.get('drawables').add(tracks);
- }
- },
- {
- // This definition matches that produced by to_dict() methods in tracks.js
- to_json_keys: [
- 'view',
- 'viewport',
- 'bookmarks'
- ],
- to_json_mappers: {
- 'view': function(dummy, self) {
- return {
- obj_type: 'View',
- prefs: {
- name: self.get('title'),
- content_visible: true
- },
- drawables: self.get('drawables')
- };
- }
- }
- }
- );
- /**
- * -- 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,
- Visualization: Visualization,
- select_datasets: select_datasets
- };
- });