mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Visualization infrastructure: (a) Backbone-ify cache and data managers; (b) push parameter value sampling code into Tool objects. Pack scripts.
This commit is contained in:
@@ -63,13 +63,48 @@ var Tool = BaseModel.extend({
|
||||
* A tool input.
|
||||
*/
|
||||
var ToolInput = Backbone.RelationalModel.extend({
|
||||
defaults: {
|
||||
name: null,
|
||||
type: null
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
this.attributes.html = unescape(this.attributes.html);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns samples from a tool input.
|
||||
*/
|
||||
get_samples: function(num_samples) {
|
||||
var
|
||||
type = this.get('type'),
|
||||
samples = []
|
||||
if (type === 'number') {
|
||||
samples = d3.scale.linear().domain([this.get('min'), this.get('max')]).ticks(num_samples);
|
||||
}
|
||||
else if (type === 'select') {
|
||||
samples = _.map(this.get('options'), function(option) {
|
||||
return option[0];
|
||||
});
|
||||
}
|
||||
|
||||
return new ParamSamples({
|
||||
param: this,
|
||||
samples: samples
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A tool and parameter samples.
|
||||
*/
|
||||
var ParamSamples = Backbone.Model.extend({
|
||||
defaults: {
|
||||
param: null,
|
||||
samples: null
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Wrap collection of tools for fast access/manipulation.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
var ToolParameterTree=Backbone.Model.extend({defaults:{tool:null,samples:4},initialize:function(d){var c=this.get("tool"),b=this.get("samples"),a=c.get("inputs").filter(function(h){return(["number","select"].indexOf(h.get("type"))!==-1)}),f=_.map(a,function(h){return h.get("name")});sampling=_.map(a,function(h){var i=h.get("type");if(i==="number"){return d3.scale.linear().domain([h.get("min"),h.get("max")]).ticks(b)}else{if(i==="select"){return _.map(h.get("options"),function(j){return j[0]})}}});var g=function(j,h,i){if(h.length-1===i){return _.map(h[i],function(k){return{name:k}})}return _.map(h[i],function(k){return{name:j[i]+":"+k,children:g(j,h,i+1)}})};var e={name:"Parameter Tree for "+c.get("name"),children:g(f,sampling,0)};this.set("valid_inputs",a);this.set("tree_data",e)}});var TileView=Backbone.View.extend({});var ToolParameterTreeView=Backbone.View.extend({className:"paramamonster",initialize:function(a){this.model=a.model},render:function(){var e=960,b=2000;var a=d3.layout.cluster().size([b,e-160]);var d=d3.svg.diagonal().projection(function(i){return[i.y,i.x]});var h=d3.select(this.$el[0]).append("svg").attr("width",e).attr("height",b).append("g").attr("transform","translate(80, 0)");var c=a.nodes(this.model.get("tree_data"));var g=h.selectAll("path.link").data(a.links(c)).enter().append("path").attr("class","link").attr("d",d);var f=h.selectAll("g.node").data(c).enter().append("g").attr("class","node").attr("transform",function(i){return"translate("+i.y+","+i.x+")"});f.append("circle").attr("r",4.5);f.append("text").attr("dx",function(i){return i.children?-8:8}).attr("dy",3).attr("text-anchor",function(i){return i.children?"end":"start"}).text(function(i){return i.name})}});
|
||||
var ToolParameterTree=Backbone.Model.extend({defaults:{tool:null,params:null,num_samples:4},initialize:function(d){var c=this.get("tool"),b=this.get("num_samples"),f=c.get("inputs").map(function(h){return h.get_samples(b)}),a=_.filter(f,function(h){return h.get("samples").length!==0});var g=function(j,h){var m=j[h],l=m.get("param"),k=l.get("name"),i=m.get("samples");if(j.length-1===h){return _.map(i,function(n){return{name:k+"="+n,param:l}})}return _.map(i,function(n){return{name:k+"="+n,param:l,children:g(a,h+1)}})};var e={name:"Parameter Tree for "+c.get("name"),children:g(a,0)};this.set("params",_.map(f,function(h){return h.get("param")}));this.set("tree_data",e)}});var Tile=Backbone.Model.extend({defaults:{track:null,index:null,region:null,resolution:null,data:null,stale:null,html_elt:null},initialize:function(a){}});var Track=Backbone.Model.extend({defaults:{dataset:null}});var FeatureTrack=Track.extend({defaults:{track:null},draw_tile:function(n,r,p,s,d,j,c){var q=this,b=r.canvas,y=d.get("start"),a=d.get("end"),D=25,e=this.left_offset;if(p==="summary_tree"||p==="Histogram"){if(n.dataset_type!=="summary_tree"){var k=this.get_summary_tree_data(n.data,y,a,200);if(n.max){k.max=n.max}n=k}var A=new painters.SummaryTreePainter(n,y,a,this.prefs);A.draw(r,b.width,b.height,j);return new SummaryTreeTile(q,tile_index,s,b,n.data,n.max)}var h=[],o=this.slotters[j].slots;all_slotted=true;if(n.data){var l=this.filters_manager.filters;for(var t=0,v=n.data.length;t<v;t++){var g=n.data[t];var u=false;var m;for(var x=0,C=l.length;x<C;x++){m=l[x];m.update_attrs(g);if(!m.keep(g)){u=true;break}}if(!u){h.push(g);if(!(g[0] in o)){all_slotted=false}}}}var B=(this.filters_manager.alpha_filter?new FilterScaler(this.filters_manager.alpha_filter):null);var z=(this.filters_manager.height_filter?new FilterScaler(this.filters_manager.height_filter):null);var A=new (this.painter)(h,y,a,this.prefs,p,B,z,c);var w=null;r.fillStyle=this.prefs.block_color;r.font=r.canvas.manager.default_font;r.textAlign="right";if(n.data){w=A.draw(r,b.width,b.height,j,o);w.translation=-e}return new FeatureTrackTile(q,tile_index,s,b,n.data,j,p,n.message,all_slotted,w)}});var TileView=Backbone.View.extend({});var ToolParameterTreeView=Backbone.View.extend({className:"tool-parameter-tree",initialize:function(a){this.model=a.model},render:function(){var e=960,b=2000;var a=d3.layout.cluster().size([b,e-160]);var d=d3.svg.diagonal().projection(function(i){return[i.y,i.x]});var h=d3.select(this.$el[0]).append("svg").attr("width",e).attr("height",b).append("g").attr("transform","translate(80, 0)");var c=a.nodes(this.model.get("tree_data"));var g=h.selectAll("path.link").data(a.links(c)).enter().append("path").attr("class","link").attr("d",d);var f=h.selectAll("g.node").data(c).enter().append("g").attr("class","node").attr("transform",function(i){return"translate("+i.y+","+i.x+")"});f.on("click",function(k,j){console.log(k,j)});f.append("circle").attr("r",4.5);f.append("text").attr("dx",function(i){return i.children?-8:8}).attr("dy",3).attr("text-anchor",function(i){return i.children?"end":"start"}).text(function(i){return i.name})}});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,10 +3,15 @@
|
||||
* genomic visualization.
|
||||
*/
|
||||
|
||||
/**
|
||||
* --- Models ---
|
||||
*/
|
||||
|
||||
var ToolParameterTree = Backbone.Model.extend({
|
||||
defaults: {
|
||||
tool: null,
|
||||
samples: 4
|
||||
params: null,
|
||||
num_samples: 4
|
||||
},
|
||||
|
||||
|
||||
@@ -17,64 +22,189 @@ var ToolParameterTree = Backbone.Model.extend({
|
||||
|
||||
// Valid inputs for tree are number, select parameters.
|
||||
var tool = this.get('tool'),
|
||||
samples = this.get('samples'),
|
||||
inputs = tool.get('inputs').filter(function(input) {
|
||||
return ( ['number', 'select'].indexOf(input.get('type')) !== -1 );
|
||||
num_samples = this.get('num_samples'),
|
||||
params_samples = tool.get('inputs').map(function(input) {
|
||||
return input.get_samples(num_samples);
|
||||
}),
|
||||
inputs_names = _.map(inputs, function(i) { return i.get('name')});
|
||||
// Sample from all valid inputs.
|
||||
sampling = _.map(inputs, function(input) {
|
||||
var type = input.get('type');
|
||||
if (type === 'number') {
|
||||
return d3.scale.linear().domain([input.get('min'), input.get('max')]).ticks(samples);
|
||||
}
|
||||
else if (type === 'select') {
|
||||
return _.map(input.get('options'), function(option) {
|
||||
return option[0];
|
||||
});
|
||||
}
|
||||
filtered_params_samples = _.filter(params_samples, function(param_sample) {
|
||||
return param_sample.get('samples').length !== 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns tree data.
|
||||
* Returns tree data. Params_sampling is an array of
|
||||
*
|
||||
*/
|
||||
var create_tree_data = function(param_names, param_settings, index) {
|
||||
// Terminate when last parameter setting is reached.
|
||||
if (param_settings.length - 1 === index) {
|
||||
return _.map(param_settings[index], function(setting) {
|
||||
var create_tree_data = function(params_samples, index) {
|
||||
var
|
||||
param_samples = params_samples[index],
|
||||
param = param_samples.get('param'),
|
||||
param_name = param.get('name'),
|
||||
settings = param_samples.get('samples');
|
||||
|
||||
// Create leaves when last parameter setting is reached.
|
||||
if (params_samples.length - 1 === index) {
|
||||
return _.map(settings, function(setting) {
|
||||
return {
|
||||
name: setting
|
||||
name: param_name + '=' + setting,
|
||||
param: param
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Recurse to handle other parameters.
|
||||
return _.map(param_settings[index], function(setting) {
|
||||
return _.map(settings, function(setting) {
|
||||
return {
|
||||
name: param_names[index] + ':' + setting,
|
||||
children: create_tree_data(param_names, param_settings, index + 1)
|
||||
name: param_name + '=' + setting,
|
||||
param: param,
|
||||
children: create_tree_data(filtered_params_samples, index + 1)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var tree_data = {
|
||||
name: 'Parameter Tree for ' + tool.get('name'),
|
||||
children: create_tree_data(inputs_names, sampling, 0)
|
||||
children: create_tree_data(filtered_params_samples, 0)
|
||||
};
|
||||
|
||||
// Set valid inputs, tree data for later use.
|
||||
this.set('valid_inputs', inputs);
|
||||
this.set('params', _.map(params_samples, function(s) { return s.get('param') }));
|
||||
this.set('tree_data', tree_data);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Tile of rendered genomic data.
|
||||
*/
|
||||
var Tile = Backbone.Model.extend({
|
||||
defaults: {
|
||||
track: null,
|
||||
index: null,
|
||||
region: null,
|
||||
resolution: null,
|
||||
data: null,
|
||||
stale: null,
|
||||
html_elt: null
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* A track in a genome browser.
|
||||
*/
|
||||
var Track = Backbone.Model.extend({
|
||||
defaults: {
|
||||
dataset: null
|
||||
}
|
||||
});
|
||||
|
||||
var FeatureTrack = Track.extend({
|
||||
defaults: {
|
||||
track: null
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw FeatureTrack tile.
|
||||
* @param result result from server
|
||||
* @param cxt canvas context to draw on
|
||||
* @param mode mode to draw in
|
||||
* @param resolution view resolution
|
||||
* @param region region to draw
|
||||
* @param w_scale pixels per base
|
||||
* @param ref_seq reference sequence data
|
||||
*/
|
||||
draw_tile: function(result, ctx, mode, resolution, region, w_scale, ref_seq) {
|
||||
var track = this,
|
||||
canvas = ctx.canvas,
|
||||
tile_low = region.get('start'),
|
||||
tile_high = region.get('end'),
|
||||
min_height = 25,
|
||||
left_offset = this.left_offset;
|
||||
|
||||
// Drawing the summary tree (feature coverage histogram)
|
||||
if (mode === "summary_tree" || mode === "Histogram") {
|
||||
// Get summary tree data if necessary and set max if there is one.
|
||||
if (result.dataset_type !== "summary_tree") {
|
||||
var st_data = this.get_summary_tree_data(result.data, tile_low, tile_high, 200);
|
||||
if (result.max) {
|
||||
st_data.max = result.max;
|
||||
}
|
||||
result = st_data;
|
||||
}
|
||||
// Paint summary tree into canvas
|
||||
var painter = new painters.SummaryTreePainter(result, tile_low, tile_high, this.prefs);
|
||||
painter.draw(ctx, canvas.width, canvas.height, w_scale);
|
||||
return new SummaryTreeTile(track, tile_index, resolution, canvas, result.data, result.max);
|
||||
}
|
||||
|
||||
// Handle row-by-row tracks
|
||||
|
||||
// Preprocessing: filter features and determine whether all unfiltered features have been slotted.
|
||||
var
|
||||
filtered = [],
|
||||
slots = this.slotters[w_scale].slots;
|
||||
all_slotted = true;
|
||||
if ( result.data ) {
|
||||
var filters = this.filters_manager.filters;
|
||||
for (var i = 0, len = result.data.length; i < len; i++) {
|
||||
var feature = result.data[i];
|
||||
var hide_feature = false;
|
||||
var filter;
|
||||
for (var f = 0, flen = filters.length; f < flen; f++) {
|
||||
filter = filters[f];
|
||||
filter.update_attrs(feature);
|
||||
if (!filter.keep(feature)) {
|
||||
hide_feature = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hide_feature) {
|
||||
// Feature visible.
|
||||
filtered.push(feature);
|
||||
// Set flag if not slotted.
|
||||
if ( !(feature[0] in slots) ) {
|
||||
all_slotted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create painter.
|
||||
var filter_alpha_scaler = (this.filters_manager.alpha_filter ? new FilterScaler(this.filters_manager.alpha_filter) : null);
|
||||
var filter_height_scaler = (this.filters_manager.height_filter ? new FilterScaler(this.filters_manager.height_filter) : null);
|
||||
// HACK: ref_seq will only be defined for ReadTracks, and only the ReadPainter accepts that argument
|
||||
var painter = new (this.painter)(filtered, tile_low, tile_high, this.prefs, mode, filter_alpha_scaler, filter_height_scaler, ref_seq);
|
||||
var feature_mapper = null;
|
||||
|
||||
// console.log(( tile_low - this.view.low ) * w_scale, tile_index, w_scale);
|
||||
ctx.fillStyle = this.prefs.block_color;
|
||||
ctx.font = ctx.canvas.manager.default_font;
|
||||
ctx.textAlign = "right";
|
||||
|
||||
if (result.data) {
|
||||
// Draw features.
|
||||
feature_mapper = painter.draw(ctx, canvas.width, canvas.height, w_scale, slots);
|
||||
feature_mapper.translation = -left_offset;
|
||||
}
|
||||
|
||||
return new FeatureTrackTile(track, tile_index, resolution, canvas, result.data, w_scale, mode, result.message, all_slotted, feature_mapper);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* --- Views ---
|
||||
*/
|
||||
|
||||
var TileView = Backbone.View.extend({
|
||||
|
||||
});
|
||||
|
||||
var ToolParameterTreeView = Backbone.View.extend({
|
||||
className: 'paramamonster',
|
||||
className: 'tool-parameter-tree',
|
||||
|
||||
initialize: function(options) {
|
||||
this.model = options.model;
|
||||
@@ -84,12 +214,14 @@ var ToolParameterTreeView = Backbone.View.extend({
|
||||
var width = 960,
|
||||
height = 2000;
|
||||
|
||||
// Layout tree.
|
||||
var cluster = d3.layout.cluster()
|
||||
.size([height, width - 160]);
|
||||
|
||||
var diagonal = d3.svg.diagonal()
|
||||
.projection(function(d) { return [d.y, d.x]; });
|
||||
|
||||
// Set up vis element.
|
||||
var vis = d3.select(this.$el[0])
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
@@ -97,27 +229,32 @@ var ToolParameterTreeView = Backbone.View.extend({
|
||||
.append("g")
|
||||
.attr("transform", "translate(80, 0)");
|
||||
|
||||
var nodes = cluster.nodes(this.model.get('tree_data'));
|
||||
// Set up nodes, links.
|
||||
var nodes = cluster.nodes(this.model.get('tree_data'));
|
||||
|
||||
var link = vis.selectAll("path.link")
|
||||
.data(cluster.links(nodes))
|
||||
.enter().append("path")
|
||||
.attr("class", "link")
|
||||
.attr("d", diagonal);
|
||||
var link = vis.selectAll("path.link")
|
||||
.data(cluster.links(nodes))
|
||||
.enter().append("path")
|
||||
.attr("class", "link")
|
||||
.attr("d", diagonal);
|
||||
|
||||
var node = vis.selectAll("g.node")
|
||||
.data(nodes)
|
||||
.enter().append("g")
|
||||
.attr("class", "node")
|
||||
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
|
||||
var node = vis.selectAll("g.node")
|
||||
.data(nodes)
|
||||
.enter().append("g")
|
||||
.attr("class", "node")
|
||||
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
|
||||
|
||||
node.on("click", function(d, i) {
|
||||
console.log(d, i);
|
||||
});
|
||||
|
||||
node.append("circle")
|
||||
.attr("r", 4.5);
|
||||
node.append("circle")
|
||||
.attr("r", 4.5);
|
||||
|
||||
node.append("text")
|
||||
.attr("dx", function(d) { return d.children ? -8 : 8; })
|
||||
.attr("dy", 3)
|
||||
.attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
|
||||
.text(function(d) { return d.name; });
|
||||
node.append("text")
|
||||
.attr("dx", function(d) { return d.children ? -8 : 8; })
|
||||
.attr("dy", 3)
|
||||
.attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
|
||||
.text(function(d) { return d.name; });
|
||||
}
|
||||
});
|
||||
+41
-260
@@ -436,249 +436,6 @@ function round(num, places) {
|
||||
return Math.round(num * val) / val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic cache that handles key/value pairs.
|
||||
*/
|
||||
var Cache = function( num_elements ) {
|
||||
this.num_elements = num_elements;
|
||||
this.clear();
|
||||
};
|
||||
extend(Cache.prototype, {
|
||||
get: function(key) {
|
||||
var index = this.key_ary.indexOf(key);
|
||||
if (index !== -1) {
|
||||
if (this.obj_cache[key].stale) {
|
||||
// Object is stale, so remove key and object.
|
||||
this.key_ary.splice(index, 1);
|
||||
delete this.obj_cache[key];
|
||||
}
|
||||
else {
|
||||
this.move_key_to_end(key, index);
|
||||
}
|
||||
}
|
||||
return this.obj_cache[key];
|
||||
},
|
||||
set: function(key, value) {
|
||||
if (!this.obj_cache[key]) {
|
||||
if (this.key_ary.length >= this.num_elements) {
|
||||
// Remove first element
|
||||
var deleted_key = this.key_ary.shift();
|
||||
delete this.obj_cache[deleted_key];
|
||||
}
|
||||
this.key_ary.push(key);
|
||||
}
|
||||
this.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.key_ary.splice(index, 1);
|
||||
this.key_ary.push(key);
|
||||
},
|
||||
clear: function() {
|
||||
this.obj_cache = {};
|
||||
this.key_ary = [];
|
||||
},
|
||||
// Returns the number of elements in the cache.
|
||||
size: function() {
|
||||
return this.key_ary.length;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Data manager for a track.
|
||||
*/
|
||||
var DataManager = function(num_elements, track) {
|
||||
Cache.call(this, num_elements);
|
||||
this.track = track;
|
||||
};
|
||||
extend(DataManager.prototype, Cache.prototype, {
|
||||
/**
|
||||
* Load data from server; returns AJAX object so that use of Deferred is possible.
|
||||
*/
|
||||
load_data: function(low, high, mode, resolution, extra_params) {
|
||||
// Setup data request params.
|
||||
var
|
||||
chrom = this.track.view.chrom,
|
||||
params = {"chrom": chrom, "low": low, "high": high, "mode": mode,
|
||||
"resolution": resolution, "dataset_id" : this.track.dataset_id,
|
||||
"hda_ldda": this.track.hda_ldda};
|
||||
$.extend(params, extra_params);
|
||||
|
||||
// Add track filters to params.
|
||||
if (this.track.filters_manager) {
|
||||
var filter_names = [];
|
||||
var filters = this.track.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;
|
||||
return $.getJSON(this.track.data_url, params, function (result) {
|
||||
manager.set_data(low, high, result);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get track data.
|
||||
*/
|
||||
get_data: function(low, high, 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(low, high);
|
||||
if ( entry &&
|
||||
( is_deferred(entry) || this.track.data_and_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, split_key, entry_low, entry_high, mode, entry;
|
||||
for (var i = 0; i < this.key_ary.length; i++) {
|
||||
key = this.key_ary[i];
|
||||
split_key = this.split_key(key);
|
||||
entry_low = split_key[0];
|
||||
entry_high = split_key[1];
|
||||
|
||||
if (low >= entry_low && high <= entry_high) {
|
||||
// This entry has data in the requested range. Return if data
|
||||
// is compatible and can be subsetted.
|
||||
var entry = this.obj_cache[key];
|
||||
if ( is_deferred(entry) ||
|
||||
( this.track.data_and_mode_compatible(entry, mode) && this.track.can_subset(entry) ) ) {
|
||||
this.move_key_to_end(key, i);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load data from server. The deferred is immediately saved until the
|
||||
// data is ready, it then replaces itself with the actual data.
|
||||
entry = this.load_data(low, high, mode, resolution, extra_params);
|
||||
this.set_data(low, high, entry);
|
||||
return 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(low, high, mode, resolution, extra_params, req_type) {
|
||||
//
|
||||
// Get current data from cache and mark as stale.
|
||||
//
|
||||
var cur_data = this.get(low, high);
|
||||
if ( !(cur_data && this.track.data_and_mode_compatible(cur_data, mode)) ) {
|
||||
console.log("ERROR: no current data for: ", this.track, low, high, mode, resolution, extra_params);
|
||||
return;
|
||||
}
|
||||
cur_data.stale = true;
|
||||
|
||||
//
|
||||
// Set parameters based on request type.
|
||||
//
|
||||
var query_low = low;
|
||||
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;
|
||||
}
|
||||
|
||||
//
|
||||
// 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_low, high, 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(low, high, 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(low, high, result);
|
||||
new_data_available.resolve(result);
|
||||
});
|
||||
return new_data_available;
|
||||
},
|
||||
/**
|
||||
* Get data from the cache.
|
||||
*/
|
||||
get: function(low, high) {
|
||||
return Cache.prototype.get.call( this, this.gen_key(low, high) );
|
||||
},
|
||||
/**
|
||||
* Sets data in the cache.
|
||||
*/
|
||||
set_data: function(low, high, result) {
|
||||
return this.set(this.gen_key(low, high), result);
|
||||
},
|
||||
/**
|
||||
* Generate key for cache.
|
||||
*/
|
||||
// TODO: use chrom in key so that (a) data is not thrown away when changing chroms and (b)
|
||||
// manager does not need to be cleared when changing chroms.
|
||||
// TODO: use resolution in key b/c summary tree data is dependent on resolution -- is this
|
||||
// necessary, i.e. will resolution change but not low/high/mode?
|
||||
gen_key: function(low, high) {
|
||||
var key = low + "_" + high;
|
||||
return key;
|
||||
},
|
||||
/**
|
||||
* Split key from cache into array with format [low, high]
|
||||
*/
|
||||
split_key: function(key) {
|
||||
return key.split("_");
|
||||
}
|
||||
});
|
||||
|
||||
var ReferenceTrackDataManager = function(num_elements, track, subset) {
|
||||
DataManager.call(this, num_elements, track, subset);
|
||||
};
|
||||
extend(ReferenceTrackDataManager.prototype, DataManager.prototype, Cache.prototype, {
|
||||
get: DataManager.prototype.get,
|
||||
load_data: function(low, high, mode, resolution, extra_params) {
|
||||
if (resolution > 1) {
|
||||
// Now that data is pre-fetched before draw, we don't load reference tracks
|
||||
// unless it's at the bottom level.
|
||||
return { data: null };
|
||||
}
|
||||
return DataManager.prototype.load_data.call(this, low, high, mode, resolution, extra_params);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Drawables hierarchy:
|
||||
*
|
||||
@@ -3082,7 +2839,11 @@ var FeatureTrackTile = function(track, index, resolution, canvas, data, w_scale,
|
||||
.css({'height': ERROR_PADDING-1, 'width': canvas.width}).prependTo(this.html_elt);
|
||||
|
||||
// Handle message; only message currently is that only the first N elements are displayed.
|
||||
var
|
||||
var tile_region = new GenomeRegion({
|
||||
chrom: track.view.chrom,
|
||||
start: this.low,
|
||||
end: this.high
|
||||
}),
|
||||
num_features = data.length,
|
||||
more_down_icon = $("<a href='javascript:void(0);'/>").addClass("icon more-down")
|
||||
.attr("title", "For speed, only the first " + num_features + " features in this region were obtained from server. Click to get more data including depth")
|
||||
@@ -3095,7 +2856,7 @@ var FeatureTrackTile = function(track, index, resolution, canvas, data, w_scale,
|
||||
more_down_icon.click(function() {
|
||||
// Mark tile as stale, request more data, and redraw track.
|
||||
tile.stale = true;
|
||||
track.data_manager.get_more_data(tile.low, tile.high, track.mode, tile.resolution, {}, track.data_manager.DEEP_DATA_REQ);
|
||||
track.data_manager.get_more_data(tile_region, track.mode, tile.resolution, {}, track.data_manager.DEEP_DATA_REQ);
|
||||
$(".tipsy").hide();
|
||||
track.request_draw(true);
|
||||
}).dblclick(function(e) {
|
||||
@@ -3106,7 +2867,7 @@ var FeatureTrackTile = function(track, index, resolution, canvas, data, w_scale,
|
||||
more_across_icon.click(function() {
|
||||
// Mark tile as stale, request more data, and redraw track.
|
||||
tile.stale = true;
|
||||
track.data_manager.get_more_data(tile.low, tile.high, track.mode, tile.resolution, {}, track.data_manager.BROAD_DATA_REQ);
|
||||
track.data_manager.get_more_data(tile_region, track.mode, tile.resolution, {}, track.data_manager.BROAD_DATA_REQ);
|
||||
$(".tipsy").hide();
|
||||
track.request_draw(true);
|
||||
}).dblclick(function(e) {
|
||||
@@ -3245,7 +3006,20 @@ var Track = function(view, container, obj_dict) {
|
||||
this.data_url_extra_params = {}
|
||||
this.data_query_wait = ('data_query_wait' in obj_dict ? obj_dict.data_query_wait : DEFAULT_DATA_QUERY_WAIT);
|
||||
this.dataset_check_url = converted_datasets_state_url;
|
||||
this.data_manager = ('data_manager' in obj_dict ? obj_dict.data_manager : new DataManager(DATA_CACHE_SIZE, this));
|
||||
|
||||
// A little ugly creating data manager right now due to transition to Backbone-based objects.
|
||||
var dataset = new Dataset({
|
||||
id: obj_dict.dataset_id,
|
||||
hda_ldda: obj_dict.hda_ldda
|
||||
});
|
||||
this.data_manager = ('data_manager' in obj_dict ?
|
||||
obj_dict.data_manager :
|
||||
new GenomeDataManager({
|
||||
dataset: dataset,
|
||||
data_url: default_data_url,
|
||||
data_mode_compatible: this.data_and_mode_compatible,
|
||||
can_subset: this.can_subset,
|
||||
}));
|
||||
|
||||
// Height attributes: min height, max height, and visible height.
|
||||
this.min_height_px = 16;
|
||||
@@ -3560,6 +3334,9 @@ var TiledTrack = function(view, container, obj_dict) {
|
||||
|
||||
// Attribute init.
|
||||
this.filters_manager = new FiltersManager(this, ('filters' in obj_dict ? obj_dict.filters : null));
|
||||
// HACK: set filters manager for data manager.
|
||||
// FIXME: prolly need function to set filters and update data_manager reference.
|
||||
this.data_manager.set('filters_manager', this.filters_manager);
|
||||
this.filters_available = false;
|
||||
this.tool = ('tool' in obj_dict && obj_dict.tool ? new Tool(this, obj_dict.tool, obj_dict.tool_state) : null);
|
||||
this.tile_cache = new Cache(TILE_CACHE_SIZE);
|
||||
@@ -3810,15 +3587,18 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
|
||||
var track = this,
|
||||
key = this._gen_tile_cache_key(width, w_scale, tile_index),
|
||||
tile_bounds = this._get_tile_bounds(tile_index, resolution),
|
||||
tile_low = tile_bounds[0],
|
||||
tile_high = tile_bounds[1];
|
||||
region = new GenomeRegion({
|
||||
chrom: this.view.chrom,
|
||||
start: tile_bounds[0],
|
||||
end: tile_bounds[1]
|
||||
});
|
||||
|
||||
// Init kwargs if necessary to avoid having to check if kwargs defined.
|
||||
if (!kwargs) { kwargs = {}; }
|
||||
|
||||
// Check tile cache, if found show existing tile in correct position
|
||||
var tile = (force ? undefined : track.tile_cache.get(key));
|
||||
if (tile) {
|
||||
var tile = (force ? undefined : track.tile_cache.get_elt(key));
|
||||
if (tile) {
|
||||
track.show_tile(tile, parent_element, w_scale);
|
||||
return tile;
|
||||
}
|
||||
@@ -3827,7 +3607,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
|
||||
var can_draw_now = true
|
||||
|
||||
// Get the track data, maybe a deferred
|
||||
var tile_data = track.data_manager.get_data( tile_low, tile_high, track.mode, resolution, track.data_url_extra_params );
|
||||
var tile_data = track.data_manager.get_data( region, track.mode, resolution, track.data_url_extra_params );
|
||||
if ( is_deferred( tile_data ) ) {
|
||||
can_draw_now = false;
|
||||
}
|
||||
@@ -3835,7 +3615,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
|
||||
// Get seq data if needed, maybe a deferred
|
||||
var seq_data;
|
||||
if ( view.reference_track && w_scale > view.canvas_manager.char_width_px ) {
|
||||
seq_data = view.reference_track.data_manager.get_data(tile_low, tile_high, track.mode, resolution, view.reference_track.data_url_extra_params)
|
||||
seq_data = view.reference_track.data_manager.get_data(region, track.mode, resolution, view.reference_track.data_url_extra_params)
|
||||
if ( is_deferred( seq_data ) ) {
|
||||
can_draw_now = false;
|
||||
}
|
||||
@@ -3857,9 +3637,8 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
|
||||
// Draw canvas.
|
||||
var
|
||||
canvas = track.view.canvas_manager.new_canvas(),
|
||||
tile_bounds = track._get_tile_bounds(tile_index, resolution),
|
||||
tile_low = tile_bounds[0],
|
||||
tile_high = tile_bounds[1],
|
||||
tile_low = region.get('start'),
|
||||
tile_high = region.get('end'),
|
||||
width = Math.ceil( (tile_high - tile_low) * w_scale ) + track.left_offset,
|
||||
height = track.get_canvas_height(tile_data, mode, w_scale, width);
|
||||
|
||||
@@ -3871,7 +3650,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
|
||||
|
||||
// Don't cache, show if no tile.
|
||||
if (tile !== undefined) {
|
||||
track.tile_cache.set(key, tile);
|
||||
track.tile_cache.set_elt(key, tile);
|
||||
track.show_tile(tile, parent_element, w_scale);
|
||||
}
|
||||
return tile;
|
||||
@@ -4158,7 +3937,7 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, {
|
||||
if (!kwargs) { kwargs = {}; }
|
||||
|
||||
// Check tile cache, if found show existing tile in correct position
|
||||
var tile = (force ? undefined : track.tile_cache.get(key));
|
||||
var tile = (force ? undefined : track.tile_cache.get_elt(key));
|
||||
if (tile) {
|
||||
track.show_tile(tile, parent_element, w_scale);
|
||||
return tile;
|
||||
@@ -4246,7 +4025,7 @@ extend(CompositeTrack.prototype, TiledTrack.prototype, {
|
||||
}
|
||||
|
||||
// Don't cache, show if no tile.
|
||||
this.tile_cache.set(key, tile);
|
||||
this.tile_cache.set_elt(key, tile);
|
||||
this.show_tile(tile, parent_element, w_scale);
|
||||
return tile;
|
||||
}
|
||||
@@ -4353,7 +4132,9 @@ var ReferenceTrack = function (view) {
|
||||
this.content_div.css("border", "none");
|
||||
this.data_url = reference_url;
|
||||
this.data_url_extra_params = {dbkey: view.dbkey};
|
||||
this.data_manager = new ReferenceTrackDataManager(DATA_CACHE_SIZE, this, false);
|
||||
this.data_manager = new ReferenceTrackDataManager({
|
||||
data_url: reference_url
|
||||
});
|
||||
this.hide_contents();
|
||||
};
|
||||
extend(ReferenceTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
|
||||
|
||||
@@ -6,9 +6,271 @@
|
||||
* and models can be used without views.
|
||||
*/
|
||||
|
||||
// --------- Models ---------
|
||||
|
||||
/**
|
||||
* -- Models --
|
||||
* Generic cache that handles key/value pairs.
|
||||
*/
|
||||
var Cache = Backbone.Model.extend({
|
||||
defaults: {
|
||||
num_elements: 20,
|
||||
obj_cache: {},
|
||||
key_ary: []
|
||||
},
|
||||
|
||||
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: 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;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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,
|
||||
filters_manager: null,
|
||||
data_url: null,
|
||||
data_mode_compatible: function(entry, mode) { return true; },
|
||||
can_subset: function(entry) { return false; }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Load data from server; returns AJAX object so that use of Deferred is possible.
|
||||
*/
|
||||
load_data: function(region, mode, resolution, extra_params) {
|
||||
// Setup data request params.
|
||||
var params = {
|
||||
"chrom": region.get('chrom'),
|
||||
"low": region.get('start'),
|
||||
"high": region.get('end'),
|
||||
"mode": mode,
|
||||
"resolution": resolution
|
||||
};
|
||||
dataset = this.get('dataset');
|
||||
|
||||
// ReferenceDataManager does not have dataset.
|
||||
if (dataset) {
|
||||
params['dataset_id'] = dataset.id;
|
||||
params['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;
|
||||
return $.getJSON(this.get('data_url'), params, function (result) {
|
||||
manager.set_data(region, result);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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, region, entry_region, mode, entry;
|
||||
for (var i = 0; i < key_ary.length; i++) {
|
||||
entry_region = new GenomeRegion(key_ary[i]);
|
||||
|
||||
if (entry_region.contains(region)) {
|
||||
// This entry has data in the requested range. Return if data
|
||||
// is compatible and can be subsetted.
|
||||
var 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);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load data from server. The deferred is immediately saved until the
|
||||
// data is ready, it then replaces itself with the actual data.
|
||||
entry = this.load_data(region, mode, resolution, extra_params);
|
||||
this.set_data(region, entry);
|
||||
return entry;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
//
|
||||
// Get current data from cache and mark as stale.
|
||||
//
|
||||
var cur_data = this.get_elt(region);
|
||||
if ( !(cur_data && this.get('data_mode_compatible')(cur_data, mode)) ) {
|
||||
console.log("ERROR: no current data for: ", dataset, region.toString(), mode, resolution, extra_params);
|
||||
return;
|
||||
}
|
||||
cur_data.stale = true;
|
||||
|
||||
//
|
||||
// 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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 ReferenceTrackDataManager = GenomeDataManager.extend({
|
||||
load_data: function(low, high, mode, resolution, extra_params) {
|
||||
if (resolution > 1) {
|
||||
// Now that data is pre-fetched before draw, we don't load reference tracks
|
||||
// unless it's at the bottom level.
|
||||
return { data: null };
|
||||
}
|
||||
return GenomeDataManager.prototype.load_data.call(this, low, high, mode, resolution, extra_params);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A genome build.
|
||||
@@ -26,13 +288,117 @@ var Genome = Backbone.Model.extend({
|
||||
});
|
||||
|
||||
/**
|
||||
* A genome browser bookmark.
|
||||
* A genomic region.
|
||||
*/
|
||||
var BrowserBookmark = Backbone.Model.extend({
|
||||
var GenomeRegion = Backbone.Model.extend({
|
||||
defaults: {
|
||||
chrom: null,
|
||||
start: 0,
|
||||
end: 0,
|
||||
DIF_CHROMS: 1000,
|
||||
BEFORE: 1001,
|
||||
CONTAINS: 1002,
|
||||
OVERLAP_START: 1003,
|
||||
OVERLAP_END: 1004,
|
||||
CONTAINED_BY: 1005,
|
||||
AFTER: 1006
|
||||
},
|
||||
|
||||
/**
|
||||
* as_str attribute using the format chrom:start-end can be
|
||||
* used to set object's attributes.
|
||||
*/
|
||||
initialize: function(options) {
|
||||
if (!this.get('chrom') && !this.get('start') &&
|
||||
!this.get('end') && 'as_str' in options) {
|
||||
var pieces = options.as_str.split(':'),
|
||||
chrom = pieces[0],
|
||||
pieces = pieces.split('-'),
|
||||
start = pieces[0],
|
||||
end = pieces[1];
|
||||
this.set('chrom', chrom);
|
||||
this.set('start', start);
|
||||
this.set('end', end);
|
||||
}
|
||||
},
|
||||
|
||||
copy: function() {
|
||||
return new GenomeRegion({
|
||||
chrom: this.get('chrom'),
|
||||
start: this.get('start'),
|
||||
end: this.get('end')
|
||||
});
|
||||
},
|
||||
|
||||
/** Returns region in canonical form chrom:start-end */
|
||||
toString: function() {
|
||||
return this.get('chrom') + ":" + this.get('start') + "-" + 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;
|
||||
|
||||
// Look at chroms.
|
||||
if (first_chrom && second_chrom && first_chrom !== second_chrom) {
|
||||
return this.get('DIF_CHROMS');
|
||||
}
|
||||
|
||||
// Look at regions.
|
||||
if (first_start < second_start) {
|
||||
if (first_end < second_start) {
|
||||
overlap = this.get('BEFORE');
|
||||
}
|
||||
else if (first_end <= second_end) {
|
||||
overlap = this.get('OVERLAP_START');
|
||||
}
|
||||
else { // first_end > second_end
|
||||
overlap = this.get('CONTAINS');
|
||||
}
|
||||
}
|
||||
else { // first_start >= second_start
|
||||
if (first_start > second_end) {
|
||||
overlap = this.get('AFTER');
|
||||
}
|
||||
else if (first_end <= second_end) {
|
||||
overlap = this.get('CONTAINED_BY');
|
||||
}
|
||||
else {
|
||||
overlap = this.get('OVERLAP_END');
|
||||
}
|
||||
}
|
||||
|
||||
return overlap;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this region contains a given region.
|
||||
*/
|
||||
contains: function(a_region) {
|
||||
return this.compute_overlap(a_region) === this.get('CONTAINS');
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if regions overlap.
|
||||
*/
|
||||
overlaps: function(a_region) {
|
||||
return _.intersection( [this.compute_overlap(a_region)],
|
||||
[this.get('DIF_CHROMS'), this.get('BEFORE'), this.get('AFTER')] ).length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* A genome browser bookmark.
|
||||
*/
|
||||
var BrowserBookmark = Backbone.Model.extend({
|
||||
defaults: {
|
||||
region: null,
|
||||
note: ""
|
||||
}
|
||||
});
|
||||
@@ -100,7 +466,7 @@ var Dataset = Backbone.Model.extend({
|
||||
id: "",
|
||||
type: "",
|
||||
name: "",
|
||||
hda_ldda: ""
|
||||
hda_ldda: ""
|
||||
}
|
||||
});
|
||||
|
||||
@@ -232,7 +598,7 @@ var CircsterView = Backbone.View.extend({
|
||||
var chrom_arc = chrom_info[0],
|
||||
chrom_data = chrom_info[1];
|
||||
return arcs_layout.chrom_data_layout(chrom_arc, chrom_data, radius_start, radius_start + dataset_arc_height, dataset_max);
|
||||
});
|
||||
});
|
||||
|
||||
// -- Render viz. --
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ ${parent.javascripts()}
|
||||
<script type='text/javascript' src="${h.url_for('/static/scripts/excanvas.js')}"></script>
|
||||
<![endif]-->
|
||||
|
||||
${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.event.drag", "jquery.event.hover","jquery.mousewheel", "jquery.autocomplete", "viz/trackster", "viz/trackster_ui", "jquery.ui.sortable.slider", "farbtastic", "viz/visualization" )}
|
||||
${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.event.drag", "jquery.event.hover","jquery.mousewheel", "jquery.autocomplete", "viz/visualization", "viz/trackster", "viz/trackster_ui", "jquery.ui.sortable.slider", "farbtastic" )}
|
||||
|
||||
<script type="text/javascript">
|
||||
//
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
tool_param_tree_view = new ToolParameterTreeView({ model: tool_param_tree });
|
||||
|
||||
tool_param_tree_view.render();
|
||||
$('#vis').append(tool_param_tree_view.$el);
|
||||
$('#param-tree').append(tool_param_tree_view.$el);
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
@@ -58,5 +58,8 @@
|
||||
</div>
|
||||
<div style="clear: both"></div>
|
||||
</div>
|
||||
<div id="vis" class="unified-panel-body"></div>
|
||||
<div class="unified-panel-body">
|
||||
<div id="param-tree"></div>
|
||||
<div id="tile-view"></div>
|
||||
</div>
|
||||
</%def>
|
||||
|
||||
Reference in New Issue
Block a user