details: http://www.bx.psu.edu/hg/galaxy/rev/62b897ca35ca changeset: 2799:62b897ca35ca user: Kanwei Li <kanwei@gmail.com> date: Tue Sep 29 21:50:44 2009 -0400 description: Trackster: only attempt dataset conversion once, UI/parsing improvements 8 file(s) affected in this change: lib/galaxy/datatypes/converters/bed_to_interval_index_converter.py lib/galaxy/visualization/tracks/data/array_tree.py lib/galaxy/web/controllers/tracks.py static/scripts/packed/trackster.js static/scripts/trackster.js static/trackster.css templates/dataset/edit_attributes.mako templates/tracks/new_browser.mako diffs (425 lines): diff -r e7b9d15e8e93 -r 62b897ca35ca lib/galaxy/datatypes/converters/bed_to_interval_index_converter.py --- a/lib/galaxy/datatypes/converters/bed_to_interval_index_converter.py Tue Sep 29 17:57:45 2009 -0400 +++ b/lib/galaxy/datatypes/converters/bed_to_interval_index_converter.py Tue Sep 29 21:50:44 2009 -0400 @@ -16,7 +16,7 @@ for line in open(input_fname, "r"): feature = line.split() - if feature[0] == "track": + if not feature or feature[0] == "track" or feature[0] == "#": offset += len(line) continue chrom = feature[0] diff -r e7b9d15e8e93 -r 62b897ca35ca lib/galaxy/visualization/tracks/data/array_tree.py --- a/lib/galaxy/visualization/tracks/data/array_tree.py Tue Sep 29 17:57:45 2009 -0400 +++ b/lib/galaxy/visualization/tracks/data/array_tree.py Tue Sep 29 21:50:44 2009 -0400 @@ -24,7 +24,7 @@ try: chrom_array_tree = d[chrom] except KeyError: - return None + return "no data" root_summary = chrom_array_tree.get_summary( 0, chrom_array_tree.levels ) return { 'max': float( max(root_summary.maxs) ), 'min': float( min(root_summary.mins) ) } @@ -33,9 +33,8 @@ start = int( start ) end = int( end ) level = int( ceil( log( end - start, BLOCK_SIZE ) ) ) - 1 - print "!!!!", start, end, level + # Open the file - print self.dataset.file_name d = FileArrayTreeDict( open( self.dataset.file_name ) ) # Get the right chromosome try: diff -r e7b9d15e8e93 -r 62b897ca35ca lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py Tue Sep 29 17:57:45 2009 -0400 +++ b/lib/galaxy/web/controllers/tracks.py Tue Sep 29 21:50:44 2009 -0400 @@ -9,8 +9,6 @@ Problems -------- - - Only line tracks supported - - Resolutions are a bit wonky - Must have a LEN file, not currently able to infer from data (not sure we need to support that, but need to make user defined build support better) """ @@ -31,7 +29,8 @@ PENDING = "pending", NO_DATA = "no data", NO_CHROMOSOME = "no chromosome", - DATA = "data" + DATA = "data", + ERROR = "error" ) # Dataset type required for each track type. This needs to be more flexible, @@ -57,11 +56,11 @@ Controller for track browser interface. Handles building a new browser from datasets in the current history, and display of the resulting browser. """ - + @web.expose def index( self, trans ): return trans.fill_template( "tracks/index.mako" ) - + @web.expose def new_browser( self, trans, dbkey=None, dataset_ids=None, browse=None ): """ @@ -72,13 +71,16 @@ # If the user clicked the submit button explicitly, try to build the browser if browse and dataset_ids: if not isinstance( dataset_ids, list ): - dataset_ids = [ dataset_ids ] + dataset_ids = [ dataset_ids ] dataset_ids = ",".join( map( str, dataset_ids ) ) trans.response.send_redirect( web.url_for( controller='tracks', action='browser', chrom="", dataset_ids=dataset_ids ) ) else: # Determine the set of all dbkeys that are used in the current history dbkeys = [ d.metadata.dbkey for d in trans.get_history().datasets if not d.deleted ] dbkey_set = set( dbkeys ) + if not dbkey_set: + return trans.show_error_message( "Current history has no valid datasets to visualize." ) + # If a dbkey argument was not provided, or is no longer valid, default # to the first one if dbkey is None or dbkey not in dbkey_set: @@ -88,7 +90,7 @@ datasets = {} for dataset in session.query( model.HistoryDatasetAssociation ).filter_by( deleted=False, history_id=trans.history.id ): if dataset.metadata.dbkey == dbkey and dataset.extension in browsable_types: - datasets[dataset.id] = dataset.name + datasets[dataset.id] = (dataset.extension, dataset.name) # Render the template return trans.fill_template( "tracks/new_browser.mako", dbkey=dbkey, dbkey_set=dbkey_set, datasets=datasets ) @@ -110,13 +112,13 @@ chrom_lengths = self._chroms( trans, dbkey ) if chrom_lengths is None: error( "No chromosome lengths file found for '%s'" % dataset.name ) - return trans.fill_template( 'tracks/browser.mako', + return trans.fill_template( 'tracks/browser.mako', dataset_ids=dataset_ids, tracks=tracks, chrom=chrom, dbkey=dbkey, LEN=chrom_lengths.get(chrom, 0) ) - + @web.json def chroms(self, trans, dbkey=None ): """ @@ -127,15 +129,15 @@ return int(s) else: return s - + def split_by_number(s): return [ check_int(c) for c in re.split('([0-9]+)', s) ] - + chroms = self._chroms( trans, dbkey ) to_sort = [{ 'chrom': chrom, 'len': length } for chrom, length in chroms.iteritems()] to_sort.sort(lambda a,b: cmp( split_by_number(a['chrom']), split_by_number(b['chrom']) )) return to_sort - + def _chroms( self, trans, dbkey ): """ Called by the browser to get a list of valid chromosomes and lengths @@ -155,8 +157,8 @@ fields = line.split("\t") manifest[fields[0]] = int(fields[1]) return manifest - - @web.json + + @web.json def data( self, trans, dataset_id, track_type, chrom, low, high, stats=False ): """ Called by the browser to request a block of data @@ -168,34 +170,32 @@ return messages.NO_DATA # Dataset is in error state, can't display if dataset.state == trans.app.model.Job.states.ERROR: - return messages.NO_DATA + return messages.ERROR # Dataset is still being generated if dataset.state != trans.app.model.Job.states.OK: return messages.PENDING # Determine what to return based on the type of track being drawn. - converted_dataset_type = track_type_to_dataset_type[track_type] + converted_dataset_type = track_type_to_dataset_type[track_type] converted_dataset = self.__dataset_as_type( trans, dataset, converted_dataset_type ) # If at this point we still don't have an `array_tree_dataset`, there # is no way we can display this data as an array tree - if converted_dataset is None: - return messages.NO_DATA + if not converted_dataset: + return messages.ERROR # Need to check states again for the converted version if converted_dataset.state == model.Dataset.states.ERROR: - return messages.NO_DATA + return messages.ERROR if converted_dataset.state != model.Dataset.states.OK: return messages.PENDING # We have a dataset in the right format that is ready to use, wrap in # a data provider that knows how to access it data_provider = dataset_type_to_data_provider[ converted_dataset_type ]( converted_dataset, dataset ) - + # Return stats if we need them if stats: return data_provider.get_stats( chrom ) - + # Get the requested chunk of data - data = data_provider.get_data( chrom, low, high ) - # Pack into a dictionary and return - return data - + return data_provider.get_data( chrom, low, high ) + def __dataset_as_type( self, trans, dataset, type ): """ Given a dataset, try to find a way to adapt it to a different type. If the @@ -211,12 +211,15 @@ log.debug( "Conversion from '%s' to '%s' not possible", dataset.extension, type ) return None # See if converted dataset already exists - converted_datasets = dataset.get_converted_files_by_type( type ) + converted_datasets = [c for c in dataset.get_converted_files_by_type( type ) if c != None] if converted_datasets: for d in converted_datasets: - if d and d.state != 'error': + if d.state != 'error': return d - # Conversion is possible but doesn't exist yet, run converter here + else: + return None + + # Conversion is possible but hasn't been done yet, run converter here # FIXME: this is largely duplicated from DefaultToolAction assoc = model.ImplicitlyConvertedDatasetAssociation( parent = dataset, file_type = type, metadata_safe = False ) new_dataset = dataset.datatype.convert_dataset( trans, dataset, type, return_output = True, visible = False ).values()[0] diff -r e7b9d15e8e93 -r 62b897ca35ca static/scripts/packed/trackster.js --- a/static/scripts/packed/trackster.js Tue Sep 29 17:57:45 2009 -0400 +++ b/static/scripts/packed/trackster.js Tue Sep 29 21:50:44 2009 -0400 @@ -1,1 +1,1 @@ -var DENSITY=1000;var DataCache=function(b,a){this.type=b;this.track=a;this.cache=Object()};$.extend(DataCache.prototype,{get:function(d,b){var c=this.cache;if(!(c[d]&&c[d][b])){if(!c[d]){c[d]=Object()}var a=b*DENSITY*d;var e=(b+1)*DENSITY*d;c[d][b]={state:"loading"};$.getJSON(data_url,{track_type:this.track.track_type,chrom:this.track.view.chrom,low:a,high:e,dataset_id:this.track.dataset_id},function(f){if(f=="pending"){setTimeout(fetcher,5000)}else{c[d][b]={state:"loaded",values:f}}$(document).trigger("redraw")})}return c[d][b]}});var View=function(a,b){this.chrom=a;this.tracks=[];this.max_low=0;this.max_high=b;this.low=this.max_low;this.high=this.max_high;this.length=this.max_high-this.max_low};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){$("#overview-box").css({left:(this.low/this.length)*$("#overview-viewport").width(),width:Math.max(4,((this.high-this.low)/this.length)*$("#overview-viewport").widt h())}).show();$("#low").text(this.low);$("#high").text(this.high);for(var a in this.tracks){this.tracks[a].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},move:function(b,a){this.low=Math.max(this.max_low,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(d,b){if(this.max_high==0){return}var c=this.high-this.low;var e=c/d/2;if(b==undefined){var a=(this.low+this.high)/2}else{var a=this.low+c*b/$(document).width()}this.low=Math.floor(a-e);this.high=Math.ceil(a+e);if(this.low<this.max_low){this.low=this.max_low;this.high=c/d}else{if(this.high>this.max_high){this.high=this.max_high;this.low=this.max_high-c/d}}if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){if(this.max_high==0){return}var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b*c/2;this.low=Math.floor(Math.max(0,a-d));this.high=Math.ceil(Math.min(this.length,a+d))},left:function(b){var a=this.high- this.low;var c=Math.floor(a/b);if(this.low-c<0){this.low=0;this.high=this.low+a}else{this.low-=c;this.high-=c}},right:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.high+c>this.length){this.high=this.length;this.low=this.high-a}else{this.low+=c;this.high+=c}}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(){this.last_resolution=null;this.last_w_scale=null;this.tile_cache={}};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var k=this.view.low,c=this.view.high,e=c-k;var b=Math.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));b=Math.max(b,1);b=Math.min(b,100000);var o=$("< div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(o);var m=this.content_div.width(),d=this.content_div.height(),p=m/e,l={},n={};if(this.last_resolution==b&&this.last_w_scale==p){l=this.tile_cache}var g;var a=Math.floor(k/b/DENSITY);var i=0;while((a*1000*b)<c){if(a in l){g=l[a];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*p});o.append(g)}else{g=this.draw_tile(b,a,o,p,d)}if(g){n[a]=g;i=Math.max(i,g.height())}a+=1}o.css("height",i);this.last_resolution=b;this.last_w_scale=p;this.tile_cache=n}});var LineTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.dataset_id=b;this.cache=new DataCache("",this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{make_container:function(){Track.prototype.make_container.call(this);this.content_div.css("height",this.height_px)},init:function(){track=this;$.getJSON(data_url,{stats:tru e,track_type:track.track_type,chrom:this.view.chrom,low:null,high:null,dataset_id:this.dataset_id},function(a){if(a){track.min_value=a.min;track.max_value=a.max;track.vertical_range=track.max_value-track.min_value;track.view.redraw()}})},draw_tile:function(d,a,o,s,p){if(!this.vertical_range){return}var k=a*DENSITY*d,r=(a+1)*DENSITY*d,c=DENSITY*d;var n=this.cache.get(d,a);var h;if(n.state=="loading"){h=$("<div class='loading tile'></div>")}else{h=$("<canvas class='tile'></canvas>")}h.css({position:"absolute",top:0,left:(k-this.view.low)*s,});o.append(h);if(n.state=="loading"){e=false;return null}var b=h;b.get(0).width=Math.ceil(c*s);b.get(0).height=this.height_px;var q=b.get(0).getContext("2d");var e=false;q.beginPath();var g=n.values;if(!g){return}for(var f=0;f<g.length-1;f++){var m=g[f][0]-k;var l=g[f][1];if(isNaN(l)){e=false}else{m=m*s;y_above_min=l-this.min_value;l=y_above_min/this.vertical_range*this.height_px;if(e){q.lineTo(m,l)}else{q.moveTo(m,l);e=true}}}q.stroke();re turn h}});var LabelTrack=function(a){Track.call(this,null,a);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+a+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var itemHeight=13,itemPad=3,thinHeight=7,thinOffset=3;var FeatureTrack=function(b,a){Track.call(this,b,$("#viewport"));this.track_type="feature";this.container_div.addClass("feature-track");this.dataset_id=a;this.zo_slots=new Object();this.show_labels_scale=0.01;this.showing_labels=false};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{calc_slots:function(d){end_ary=new Array();var c=this.container_div.wid th()/(this.view.high-this.view.low);if(d){this.zi_slots=new Object()}var b=$("<canvas></canvas>").get(0).getContext("2d");for(var a in this.values){feature=this.values[a];f_start=Math.floor(Math.max(this.view.max_low,(feature.start-this.view.max_low)*c));if(d){f_start-=b.measureText(feature.name).width}f_end=Math.ceil(Math.min(this.view.max_high,(feature.end-this.view.max_low)*c));j=0;while(true){if(end_ary[j]==undefined||end_ary[j]<f_start){end_ary[j]=f_end;if(d){this.zi_slots[feature.name]=j}else{this.zo_slots[feature.name]=j}break}j++}}},init:function(){var a=this;$.getJSON(data_url,{track_type:a.track_type,low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()})},draw_tile:function(q,t,e,g,f){if(!this.values){return null}if(g>this.show_labels_scale&&!this.showing_labels){this.showing_labels=true;if(!this.zi_slots){this.calc_slots(true)}this.slots=this.zi_slots}else{if(g<=this.s how_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var u=t*DENSITY*q,c=(t+1)*DENSITY*q,b=DENSITY*q;var k=this.view,m=k.high-k.low,o=Math.ceil(b*g),h=new Array(),n=200,l=$("<canvas class='tile'></canvas>");l.css({position:"absolute",top:0,left:(u-this.view.low)*g,});l.get(0).width=o;l.get(0).height=n;var p=l.get(0).getContext("2d");var r=0;for(var s in this.values){feature=this.values[s];if(feature.start<=c&&feature.end>=u){f_start=Math.floor(Math.max(0,(feature.start-u)*g));f_end=Math.ceil(Math.min(o,(feature.end-u)*g));p.fillStyle="#000";p.fillRect(f_start,this.slots[feature.name]*10+5,f_end-f_start,1);if(this.showing_labels&&p.fillText){p.font="10px monospace";p.textAlign="right";p.fillText(feature.name,f_start,this.slots[feature.name]*10+8)}if(feature.exon_start&&feature.exon_end){var d=Math.floor(Math.max(0,(feature.exon_start-u)*g));var w=Math.ceil(Math.min(o,(feature.exon_end-u)*g))}for(var s in feature.blocks){block=feature.bloc ks[s];block_start=Math.floor(Math.max(0,(block[0]-u)*g));block_end=Math.ceil(Math.min(o,(block[1]-u)*g));var a=3,v=4;if(d&&block_start>=d&&block_end<=w){a=5,v=3}p.fillRect(d,this.slots[feature.name]*10+v,block_end-block_start,a)}r++}}e.append(l);return l},}); \ No newline at end of file +var DENSITY=1000;var DataCache=function(b,a){this.type=b;this.track=a;this.cache=Object()};$.extend(DataCache.prototype,{get:function(d,b){var c=this.cache;if(!(c[d]&&c[d][b])){if(!c[d]){c[d]=Object()}var a=b*DENSITY*d;var e=(b+1)*DENSITY*d;c[d][b]={state:"loading"};$.getJSON(data_url,{track_type:this.track.track_type,chrom:this.track.view.chrom,low:a,high:e,dataset_id:this.track.dataset_id},function(f){if(f=="pending"){setTimeout(fetcher,5000)}else{c[d][b]={state:"loaded",values:f}}$(document).trigger("redraw")})}return c[d][b]}});var View=function(a,b){this.chrom=a;this.tracks=[];this.max_low=0;this.max_high=b;this.low=this.max_low;this.high=this.max_high;this.length=this.max_high-this.max_low};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){$("#overview-box").css({left:(this.low/this.length)*$("#overview-viewport").width(),width:Math.max(4,((this.high-this.low)/this.length)*$("#overview-viewport").widt h())}).show();$("#low").text(this.low);$("#high").text(this.high);for(var a in this.tracks){this.tracks[a].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},move:function(b,a){this.low=Math.max(this.max_low,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(d,b){if(this.max_high==0){return}var c=this.high-this.low;var e=c/d/2;if(b==undefined){var a=(this.low+this.high)/2}else{var a=this.low+c*b/$(document).width()}this.low=Math.floor(a-e);this.high=Math.ceil(a+e);if(this.low<this.max_low){this.low=this.max_low;this.high=c/d}else{if(this.high>this.max_high){this.high=this.max_high;this.low=this.max_high-c/d}}if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){if(this.max_high==0){return}var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b*c/2;this.low=Math.floor(Math.max(0,a-d));this.high=Math.ceil(Math.min(this.length,a+d))},left:function(b){var a=this.high- this.low;var c=Math.floor(a/b);if(this.low-c<0){this.low=0;this.high=this.low+a}else{this.low-=c;this.high-=c}},right:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.high+c>this.length){this.high=this.length;this.low=this.high-a}else{this.low+=c;this.high+=c}}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(){this.last_resolution=null;this.last_w_scale=null;this.tile_cache={}};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var i=this.view.low,c=this.view.high,e=c-i;var b=Math.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));b=Math.max(b,1);b=Math.min(b,100000);var n=$("< div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(n);var l=this.content_div.width(),d=this.content_div.height(),o=l/e,k={},m={};if(this.last_resolution==b&&this.last_w_scale==o){k=this.tile_cache}var g;var a=Math.floor(i/b/DENSITY);while((a*1000*b)<c){if(a in k){g=k[a];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*o});n.append(g)}else{g=this.draw_tile(b,a,n,o,d)}if(g){m[a]=g}a+=1}this.last_resolution=b;this.last_w_scale=o;this.tile_cache=m}});var LabelTrack=function(a){Track.call(this,null,a);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+a+"</div>").css({position:"absolute",left:f-1 }));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var LineTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.cache=new DataCache("",this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this;$.getJSON(data_url,{stats:true,track_type:a.track_type,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(b){if(b){if(b=="error"){a.content_div.addClass("error").text("There was an error in indexing this dataset.")}else{if(b=="no data"){a.content_div.addClass("nodata").text("No data for this chrom/contig.")}else{a.min_value=b.min;a.max_value=b.max;a.vertical_range=a.max_value-a.min_value;a.view.redraw()}}}})},draw_tile:function(d,a,o,s,p){if(!this.vertical_range){return}var k=a*DENSITY*d,r=(a+1)*DENSITY*d,c=DENSITY*d;var n=this.cache.get(d,a) ;var h;if(n.state=="loading"){h=$("<div class='loading tile'></div>")}else{h=$("<canvas class='tile'></canvas>")}h.css({position:"absolute",top:0,left:(k-this.view.low)*s,});o.append(h);if(n.state=="loading"){e=false;return null}var b=h;b.get(0).width=Math.ceil(c*s);b.get(0).height=this.height_px;var q=b.get(0).getContext("2d");var e=false;q.beginPath();var g=n.values;if(!g){return}for(var f=0;f<g.length-1;f++){var m=g[f][0]-k;var l=g[f][1];if(isNaN(l)){e=false}else{m=m*s;y_above_min=l-this.min_value;l=y_above_min/this.vertical_range*this.height_px;if(e){q.lineTo(m,l)}else{q.moveTo(m,l);e=true}}}q.stroke();return h}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="feature";this.height_px=(a?a:100);this.container_div.addClass("feature-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.01;this.showing_labels=false};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{calc_ slots:function(e){var a=new Array();var d=this.container_div.width()/(this.view.high-this.view.low);if(e){this.zi_slots=new Object()}var c=$("<canvas></canvas>").get(0).getContext("2d");for(var b in this.values){feature=this.values[b];f_start=Math.floor(Math.max(this.view.max_low,(feature.start-this.view.max_low)*d));if(e){f_start-=c.measureText(feature.name).width}f_end=Math.ceil(Math.min(this.view.max_high,(feature.end-this.view.max_low)*d));j=0;while(true){if(a[j]==undefined||a[j]<f_start){a[j]=f_end;if(e){this.zi_slots[feature.name]=j}else{this.zo_slots[feature.name]=j}break}j++}}},init:function(){var a=this;$.getJSON(data_url,{track_type:a.track_type,low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()})},draw_tile:function(q,t,e,g,f){if(!this.values){return null}if(g>this.show_labels_scale&&!this.showing_labels){this.showing_labels=true;if(!this.zi_slots){this.calc_slots(tr ue)}this.slots=this.zi_slots}else{if(g<=this.show_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var u=t*DENSITY*q,c=(t+1)*DENSITY*q,b=DENSITY*q;var k=this.view,m=k.high-k.low,o=Math.ceil(b*g),h=new Array(),n=200,l=$("<canvas class='tile'></canvas>");l.css({position:"absolute",top:0,left:(u-this.view.low)*g,});l.get(0).width=o;l.get(0).height=n;var p=l.get(0).getContext("2d");var r=0;for(var s in this.values){feature=this.values[s];if(feature.start<=c&&feature.end>=u){f_start=Math.floor(Math.max(0,(feature.start-u)*g));f_end=Math.ceil(Math.min(o,(feature.end-u)*g));p.fillStyle="#000";p.fillRect(f_start,this.slots[feature.name]*10+5,f_end-f_start,1);if(this.showing_labels&&p.fillText){p.font="10px monospace";p.textAlign="right";p.fillText(feature.name,f_start,this.slots[feature.name]*10+8)}if(feature.exon_start&&feature.exon_end){var d=Math.floor(Math.max(0,(feature.exon_start-u)*g));var w=Math.ceil(Math.min(o,(feature.exon_end-u)*g))}f or(var s in feature.blocks){block=feature.blocks[s];block_start=Math.floor(Math.max(0,(block[0]-u)*g));block_end=Math.ceil(Math.min(o,(block[1]-u)*g));var a=3,v=4;if(d&&block_start>=d&&block_end<=w){a=5,v=3}p.fillRect(d,this.slots[feature.name]*10+v,block_end-block_start,a)}r++}}e.append(l);return l},}); \ No newline at end of file diff -r e7b9d15e8e93 -r 62b897ca35ca static/scripts/trackster.js --- a/static/scripts/trackster.js Tue Sep 29 17:57:45 2009 -0400 +++ b/static/scripts/trackster.js Tue Sep 29 21:50:44 2009 -0400 @@ -172,7 +172,6 @@ var tile_element; // Index of first tile that overlaps visible region var tile_index = Math.floor( low / resolution / DENSITY ); - var max_height = 0; while ( ( tile_index * 1000 * resolution ) < high ) { // Check in cache if ( tile_index in old_tiles ) { @@ -190,16 +189,38 @@ if ( tile_element ) { // console.log( typeof(tile_element) ); new_tiles[tile_index] = tile_element; - max_height = Math.max( max_height, tile_element.height() ); } tile_index += 1; } - - parent_element.css( "height", max_height ); - this.last_resolution = resolution; this.last_w_scale = w_scale; this.tile_cache = new_tiles; + } +}); + +var LabelTrack = function ( parent_element ) { + Track.call( this, null, parent_element ); + this.container_div.addClass( "label-track" ); +}; +$.extend( LabelTrack.prototype, Track.prototype, { + draw: function() { + var view = this.view, + range = view.high - view.low, + tickDistance = Math.floor( Math.pow( 10, Math.floor( Math.log( range ) / Math.log( 10 ) ) ) ), + position = Math.floor( view.low / tickDistance ) * tickDistance, + width = this.content_div.width(), + new_div = $("<div style='position: relative; height: 1.3em;'></div>"); + while ( position < view.high ) { + var screenPosition = ( position - view.low ) / range * width; + new_div.append( $("<div class='label'>" + position + "</div>").css( { + position: "absolute", + // Reduce by one to account for border + left: screenPosition - 1 + })); + position += tickDistance; + } + this.content_div.children( ":first" ).remove(); + this.content_div.append( new_div ); } }); @@ -209,24 +230,27 @@ this.track_type = "line"; this.height_px = (height ? height : 100); this.container_div.addClass( "line-track" ); + this.content_div.css( "height", this.height_px + "px" ); this.dataset_id = dataset_id; this.cache = new DataCache( "", this ); }; $.extend( LineTrack.prototype, TiledTrack.prototype, { - make_container: function () { - Track.prototype.make_container.call(this); - // console.log("height:", this.height_px); - this.content_div.css( "height", this.height_px ); - }, init: function() { - track = this; - $.getJSON( data_url, { stats: true, track_type: track.track_type, chrom: this.view.chrom, low: null, high: null, dataset_id: this.dataset_id }, function ( data ) { - // console.log(data); + var track = this; + $.getJSON( data_url, { stats: true, track_type: track.track_type, chrom: track.view.chrom, + low: null, high: null, dataset_id: track.dataset_id }, function ( data ) { if (data) { - track.min_value = data['min']; - track.max_value = data['max']; - track.vertical_range = track.max_value - track.min_value; - track.view.redraw(); + if (data == "error") { + track.content_div.addClass("error").text("There was an error in indexing this dataset."); + } else if (data == "no data") { + // console.log(track.content_div); + track.content_div.addClass("nodata").text("No data for this chrom/contig."); + } else { + track.min_value = data['min']; + track.max_value = data['max']; + track.vertical_range = track.max_value - track.min_value; + track.view.redraw(); + } } }); }, @@ -287,43 +311,14 @@ } }); -var LabelTrack = function ( parent_element ) { - Track.call( this, null, parent_element ); - this.container_div.addClass( "label-track" ); -}; -$.extend( LabelTrack.prototype, Track.prototype, { - draw: function() { - var view = this.view, - range = view.high - view.low, - tickDistance = Math.floor( Math.pow( 10, Math.floor( Math.log( range ) / Math.log( 10 ) ) ) ), - position = Math.floor( view.low / tickDistance ) * tickDistance, - width = this.content_div.width(), - new_div = $("<div style='position: relative; height: 1.3em;'></div>"); - while ( position < view.high ) { - var screenPosition = ( position - view.low ) / range * width; - new_div.append( $("<div class='label'>" + position + "</div>").css( { - position: "absolute", - // Reduce by one to account for border - left: screenPosition - 1 - })); - position += tickDistance; - } - this.content_div.children( ":first" ).remove(); - this.content_div.append( new_div ); - } -}); - -var itemHeight = 13, - itemPad = 3, - thinHeight = 7, - thinOffset = 3; - -var FeatureTrack = function ( name, dataset_id ) { +var FeatureTrack = function ( name, dataset_id, height ) { Track.call( this, name, $("#viewport") ); this.track_type = "feature"; + this.height_px = (height ? height : 100); this.container_div.addClass( "feature-track" ); + this.content_div.css( "height", this.height_px + "px" ); this.dataset_id = dataset_id; - this.zo_slots = new Object(); + this.zo_slots = {}; this.show_labels_scale = 0.01; this.showing_labels = false; }; @@ -331,7 +326,7 @@ calc_slots: function( include_labels ) { // console.log("num vals: " + this.values.length); - end_ary = new Array(); + var end_ary = new Array(); var scale = this.container_div.width() / (this.view.high - this.view.low); // console.log(scale); if (include_labels) this.zi_slots = new Object(); diff -r e7b9d15e8e93 -r 62b897ca35ca static/trackster.css --- a/static/trackster.css Tue Sep 29 17:57:45 2009 -0400 +++ b/static/trackster.css Tue Sep 29 21:50:44 2009 -0400 @@ -83,10 +83,18 @@ overflow: hidden; } +.track-content.error { + text-align: center; + padding-top: 30px; + background-color: #600; +} +.track-content.nodata { + text-align: center; + padding-top: 30px; + background-color: #ddd; +} + .loading { - background-image: url("/static/images/loading_large_white_bg.gif"); - background-position: center center; - background-repeat: no-repeat; min-height: 100px; } diff -r e7b9d15e8e93 -r 62b897ca35ca templates/dataset/edit_attributes.mako --- a/templates/dataset/edit_attributes.mako Tue Sep 29 17:57:45 2009 -0400 +++ b/templates/dataset/edit_attributes.mako Tue Sep 29 21:50:44 2009 -0400 @@ -101,13 +101,10 @@ <form name="convert_data" action="${h.url_for( controller='root', action='edit' )}" method="post"> <input type="hidden" name="id" value="${data.id}"/> <div class="form-row"> - <label> - ${_('Convert to')}: - </label> <div style="float: left; width: 250px; margin-right: 10px;"> <select name="target_type"> %for key, value in converters.items(): - <option value="${key}">${value.name[8:]}</option> + <option value="${key}">${value.name}</option> %endfor </select> </div> diff -r e7b9d15e8e93 -r 62b897ca35ca templates/tracks/new_browser.mako --- a/templates/tracks/new_browser.mako Tue Sep 29 17:57:45 2009 -0400 +++ b/templates/tracks/new_browser.mako Tue Sep 29 21:50:44 2009 -0400 @@ -32,10 +32,10 @@ </div> <div class="form-row"> <label for="dataset_ids">Datasets to include: </label> - %for dataset_id, dataset_name in datasets.items(): + %for dataset_id, (dataset_ext, dataset_name) in datasets.iteritems(): <div> <input type="checkbox" id="${dataset_id}" name="dataset_ids" value="${dataset_id}" /> - <label style="display:inline; font-weight: normal" for="${dataset_id}">${dataset_name}</label> + <label style="display:inline; font-weight: normal" for="${dataset_id}">[${dataset_ext}] ${dataset_name}</label> </div> %endfor