details: http://www.bx.psu.edu/hg/galaxy/rev/afa7946a126c changeset: 2752:afa7946a126c user: Kanwei Li kanwei@gmail.com date: Tue Sep 22 21:51:11 2009 -0400 description: new trackster lands
10 file(s) affected in this change:
lib/galaxy/visualization/tracks/data/array_tree.py lib/galaxy/web/controllers/tracks.py static/scripts/jquery.mousewheel.js static/scripts/packed/autocomplete_tagging.js static/scripts/packed/jquery.mousewheel.js static/scripts/packed/trackster.js static/scripts/trackster.js static/trackster.css templates/tracks/browser.mako templates/tracks/new_browser.mako
diffs (1182 lines):
diff -r 9118054983c3 -r afa7946a126c lib/galaxy/visualization/tracks/data/array_tree.py --- a/lib/galaxy/visualization/tracks/data/array_tree.py Tue Sep 22 14:47:47 2009 -0400 +++ b/lib/galaxy/visualization/tracks/data/array_tree.py Tue Sep 22 21:51:11 2009 -0400 @@ -18,6 +18,17 @@ class ArrayTreeDataProvider( object ): def __init__( self, dataset ): self.dataset = dataset + + def get_stats( self, chrom ): + d = FileArrayTreeDict( open( self.dataset.file_name ) ) + try: + chrom_array_tree = d[chrom] + except KeyError: + return None + + 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) ) } + def get_data( self, chrom, start, end ): start = int( start ) end = int( end ) diff -r 9118054983c3 -r afa7946a126c lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py Tue Sep 22 14:47:47 2009 -0400 +++ b/lib/galaxy/web/controllers/tracks.py Tue Sep 22 21:51:11 2009 -0400 @@ -15,7 +15,8 @@ need to support that, but need to make user defined build support better) """
-import math +import math, logging +log = logging.getLogger(__name__)
from galaxy.util.json import to_json_string from galaxy.web.base.controller import * @@ -100,7 +101,7 @@ tracks.append( { "type": dataset.datatype.get_track_type(), "name": dataset.name, - "id": dataset.id + "dataset_id": dataset.id } ) dbkey = dataset.dbkey chrom_lengths = self._chroms( trans, dbkey ) @@ -111,18 +112,20 @@ tracks=tracks, chrom=chrom, dbkey=dbkey, - LEN=chrom_lengths.get( chrom, 0 ) ) + LEN=chrom_lengths.get(chrom, 0) )
@web.json def chroms(self, trans, dbkey=None ): - return self._chroms( trans, dbkey ) + chroms = self._chroms( trans, dbkey ) + unsorted = [{ 'chrom': chrom, 'len': length } for chrom, length in chroms.iteritems()] + unsorted.sort( lambda a,b: cmp(a['chrom'], b['chrom']) ) + return unsorted
def _chroms( self, trans, dbkey ): """ Called by the browser to get a list of valid chromosomes and lengths """ - # If there is any dataset in the history of extension `len`, this will - # use it + # If there is any dataset in the history of extension `len`, this will use it db_manifest = trans.db_dataset_for( dbkey ) if not db_manifest: db_manifest = os.path.join( trans.app.config.tool_data_path, 'shared','ucsc','chrom', "%s.len" % dbkey ) @@ -139,7 +142,7 @@ return manifest
@web.json - def data( self, trans, dataset_id, track_type, chrom, low, high ): + def data( self, trans, dataset_id, track_type, chrom, low, high, stats=False ): """ Called by the browser to request a block of data """ @@ -169,6 +172,10 @@ # 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 ) + + # 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 diff -r 9118054983c3 -r afa7946a126c static/scripts/jquery.mousewheel.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/jquery.mousewheel.js Tue Sep 22 21:51:11 2009 -0400 @@ -0,0 +1,60 @@ +/*! Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * + * Version: 3.0.2 + * + * Requires: 1.2.2+ + */ + +(function($) { + +var types = ['DOMMouseScroll', 'mousewheel']; + +$.event.special.mousewheel = { + setup: function() { + if ( this.addEventListener ) + for ( var i=types.length; i; ) + this.addEventListener( types[--i], handler, false ); + else + this.onmousewheel = handler; + }, + + teardown: function() { + if ( this.removeEventListener ) + for ( var i=types.length; i; ) + this.removeEventListener( types[--i], handler, false ); + else + this.onmousewheel = null; + } +}; + +$.fn.extend({ + mousewheel: function(fn) { + return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); + }, + + unmousewheel: function(fn) { + return this.unbind("mousewheel", fn); + } +}); + + +function handler(event) { + var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true; + + event = $.event.fix(event || window.event); + event.type = "mousewheel"; + + if ( event.wheelDelta ) delta = event.wheelDelta/120; + if ( event.detail ) delta = -event.detail/3; + + // Add events and delta to the front of the arguments + args.unshift(event, delta); + + return $.event.handle.apply(this, args); +} + +})(jQuery); \ No newline at end of file diff -r 9118054983c3 -r afa7946a126c static/scripts/packed/autocomplete_tagging.js --- a/static/scripts/packed/autocomplete_tagging.js Tue Sep 22 14:47:47 2009 -0400 +++ b/static/scripts/packed/autocomplete_tagging.js Tue Sep 22 21:51:11 2009 -0400 @@ -1,1 +1,1 @@ -var ac_tag_area_id_gen=1;jQuery.fn.autocomplete_tagging=function(c){var e={get_toggle_link_text_fn:function(u){var w="";var v=o(u);if(v!=0){w=v+(v!=0?" Tags":" Tag")}else{w="Add tags"}return w},tag_click_fn:function(u){},input_size:20,in_form:false,tags:{},use_toggle_link:true,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""};var p=jQuery.extend(e,c);var k="tag-area-"+(ac_tag_area_id_gen)++;var m=$("<div>").attr("id",k).addClass("tag-area");this.append(m);var o=function(u){if(u.length){return u.length}var v=0;for(element in u){v++}return v};var b=function(){var u=p.get_toggle_link_text_fn(p.tags);var v=$("<a href='/history/tags'>").text(u).addClass("toggle-link");v.click(function(){var w=(m.css("display")=="none");var x;if(w){x=function(){var y=o(p.tags);if(y==0){m.click()}}}else{x=function(){m.blur()}}m.slideToggle("fast",x);return false});return v};var s=b();if(p.us e_toggle_link){this.prepend(s)}var t=function(u){var v=new Array();for(key in u){v[v.length]=key+"-->"+u[key]}return"{"+v.join(",")+"}"};var a=function(v,u){return v+((u!=""&&u)?":"+u:"")};var h=function(u){return u.split(":")};var i=function(u){var v=$("<img src='"+p.add_tag_img+"' rollover='"+p.add_tag_img_rollover+"'/>").addClass("add-tag-button");v.click(function(){$(this).hide();m.click();return false});return v};var j=function(u){var v=$("<img src='"+p.delete_tag_img+"'/>").addClass("delete-tag-img");v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)});v.click(function(){var D=$(this).parent();var C=D.find(".tag-name").eq(0);var B=C.text();var z=h(B);var F=z[0];var y=z[1];var E=D.prev();D.remove();delete p.tags[F];var A=p.get_toggle_link_text_fn(p.tags);s.text(A);$.ajax({url:p.ajax_delete_tag_url,data:{tag_name:F},error:function(){p.tags[F]=y;if(E.hasClass("tag-button")){E.after(D)}else{m .prepend(D)}var G=p.get_toggle_link_text_fn(p.tags);alert("Remove tag failed");s.text(G);v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)})},success:function(){}});return true});var w=$("<span>").text(u).addClass("tag-name");w.click(function(){p.tag_click_fn(u);return true});var x=$("<span></span>").addClass("tag-button");x.append(w);x.append(v);return x};var d=function(v){var u;if(p.in_form){u=$("<textarea id='history-tag-input' rows='1' cols='"+p.input_size+"' value='"+escape(v)+"'></textarea>")}else{u=$("<input id='history-tag-input' type='text' size='"+p.input_size+"' value='"+escape(v)+"'></input>")}u.keyup(function(D){if(D.keyCode==27){$(this).trigger("blur")}else{if((D.keyCode==13)||(D.keyCode==188)||(D.keyCode==32)){new_value=this.value;if(return_key_pressed_for_autocomplete==true){return_key_pressed_for_autocomplete=false;return false}if(new_value.indexOf(": ",new_value.length-2)!=-1 ){this.value=new_value.substring(0,new_value.length-1);return false}if((D.keyCode==188)||(D.keyCode==32)){new_value=new_value.substring(0,new_value.length-1)}new_value=new_value.replace(/^\s+|\s+$/g,"");if(new_value.length<3){return false}this.value="";var A=j(new_value);var z=m.children(".tag-button");if(z.length!=0){var E=z.slice(z.length-1);E.after(A)}else{m.prepend(A)}var y=new_value.split(":");p.tags[y[0]]=y[1];var B=p.get_toggle_link_text_fn(p.tags);s.text(B);var C=$(this);$.ajax({url:p.ajax_add_tag_url,data:{new_tag:new_value},error:function(){A.remove();delete p.tags[y[0]];var F=p.get_toggle_link_text_fn(p.tags);s.text(F);alert("Add tag failed")},success:function(){C.flushCache()}});return false}}});var w=function(A,z,y,C,B){tag_name_and_value=C.split(":");return(tag_name_and_value.length==1?tag_name_and_value[0]:tag_name_and_value[1])};var x={selectFirst:false,formatItem:w,autoFill:false,highlight:false};u.autocomplete(p.ajax_autocomplete_tag_url,x);u.addClass("tag- input");return u};for(tag_name in p.tags){var q=p.tags[tag_name];var l=a(tag_name,q);var g=j(l,s,p.tags);m.append(g)}var n=d("");var f=i(n);m.blur(function(u){r=o(p.tags);if(r!=0){f.show();n.hide();m.removeClass("active-tag-area")}else{}});m.append(f);m.append(n);n.hide();m.click(function(w){var v=$(this).hasClass("active-tag-area");if($(w.target).hasClass("delete-tag-img")&&!v){return false}if($(w.target).hasClass("tag-name")&&!v){return false}$(this).addClass("active-tag-area");f.hide();n.show();n.focus();var u=function(y){var x=m.attr("id");if(($(y.target).attr("id")!=x)&&($(y.target).parents().filter(x).length==0)){m.blur();$(document).unbind("click",u)}};$(window).click(u);return false});if(p.use_toggle_link){m.hide()}else{var r=o(p.tags);if(r==0){f.hide();n.show()}}return this.addClass("tag-element")}; \ No newline at end of file +var ac_tag_area_id_gen=1;jQuery.fn.autocomplete_tagging=function(c){var e={get_toggle_link_text_fn:function(u){var w="";var v=o(u);if(v!=0){w=v+(v!=0?" Tags":" Tag")}else{w="Add tags"}return w},tag_click_fn:function(u,v){},input_size:20,in_form:false,tags:{},use_toggle_link:true,item_id:"",add_tag_img:"",add_tag_img_rollover:"",delete_tag_img:"",ajax_autocomplete_tag_url:"",ajax_retag_url:"",ajax_delete_tag_url:"",ajax_add_tag_url:""};var p=jQuery.extend(e,c);var k="tag-area-"+(ac_tag_area_id_gen)++;var m=$("<div>").attr("id",k).addClass("tag-area");this.append(m);var o=function(u){if(u.length){return u.length}var v=0;for(element in u){v++}return v};var b=function(){var u=p.get_toggle_link_text_fn(p.tags);var v=$("<a href='/history/tags'>").text(u).addClass("toggle-link");v.click(function(){var w=(m.css("display")=="none");var x;if(w){x=function(){var y=o(p.tags);if(y==0){m.click()}}}else{x=function(){m.blur()}}m.slideToggle("fast",x);return false});return v};var s=b();if(p. use_toggle_link){this.prepend(s)}var t=function(u){var v=new Array();for(key in u){v[v.length]=key+"-->"+u[key]}return"{"+v.join(",")+"}"};var a=function(v,u){return v+((u!=""&&u)?":"+u:"")};var h=function(u){return u.split(":")};var i=function(u){var v=$("<img src='"+p.add_tag_img+"' rollover='"+p.add_tag_img_rollover+"'/>").addClass("add-tag-button");v.click(function(){$(this).hide();m.click();return false});return v};var j=function(u){var v=$("<img src='"+p.delete_tag_img+"'/>").addClass("delete-tag-img");v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)});v.click(function(){var D=$(this).parent();var C=D.find(".tag-name").eq(0);var B=C.text();var z=h(B);var F=z[0];var y=z[1];var E=D.prev();D.remove();delete p.tags[F];var A=p.get_toggle_link_text_fn(p.tags);s.text(A);$.ajax({url:p.ajax_delete_tag_url,data:{tag_name:F},error:function(){p.tags[F]=y;if(E.hasClass("tag-button")){E.after(D)}else {m.prepend(D)}var G=p.get_toggle_link_text_fn(p.tags);alert("Remove tag failed");s.text(G);v.mouseenter(function(){$(this).attr("src",p.delete_tag_img_rollover)});v.mouseleave(function(){$(this).attr("src",p.delete_tag_img)})},success:function(){}});return true});var w=$("<span>").text(u).addClass("tag-name");w.click(function(){tag_name_and_value=u.split(":");p.tag_click_fn(tag_name_and_value[0],tag_name_and_value[1]);return true});var x=$("<span></span>").addClass("tag-button");x.append(w);x.append(v);return x};var d=function(v){var u;if(p.in_form){u=$("<textarea id='history-tag-input' rows='1' cols='"+p.input_size+"' value='"+escape(v)+"'></textarea>")}else{u=$("<input id='history-tag-input' type='text' size='"+p.input_size+"' value='"+escape(v)+"'></input>")}u.keyup(function(D){if(D.keyCode==27){$(this).trigger("blur")}else{if((D.keyCode==13)||(D.keyCode==188)||(D.keyCode==32)){new_value=this.value;if(return_key_pressed_for_autocomplete==true){return_key_pressed_for_autoc omplete=false;return false}if(new_value.indexOf(": ",new_value.length-2)!=-1){this.value=new_value.substring(0,new_value.length-1);return false}if((D.keyCode==188)||(D.keyCode==32)){new_value=new_value.substring(0,new_value.length-1)}new_value=new_value.replace(/^\s+|\s+$/g,"");if(new_value.length<3){return false}this.value="";var A=j(new_value);var z=m.children(".tag-button");if(z.length!=0){var E=z.slice(z.length-1);E.after(A)}else{m.prepend(A)}var y=new_value.split(":");p.tags[y[0]]=y[1];var B=p.get_toggle_link_text_fn(p.tags);s.text(B);var C=$(this);$.ajax({url:p.ajax_add_tag_url,data:{new_tag:new_value},error:function(){A.remove();delete p.tags[y[0]];var F=p.get_toggle_link_text_fn(p.tags);s.text(F);alert("Add tag failed")},success:function(){C.flushCache()}});return false}}});var w=function(A,z,y,C,B){tag_name_and_value=C.split(":");return(tag_name_and_value.length==1?tag_name_and_value[0]:tag_name_and_value[1])};var x={selectFirst:false,formatItem:w,autoFill:false,hig hlight:false};u.autocomplete(p.ajax_autocomplete_tag_url,x);u.addClass("tag-input");return u};for(tag_name in p.tags){var q=p.tags[tag_name];var l=a(tag_name,q);var g=j(l,s,p.tags);m.append(g)}var n=d("");var f=i(n);m.blur(function(u){r=o(p.tags);if(r!=0){f.show();n.hide();m.removeClass("active-tag-area")}else{}});m.append(f);m.append(n);n.hide();m.click(function(w){var v=$(this).hasClass("active-tag-area");if($(w.target).hasClass("delete-tag-img")&&!v){return false}if($(w.target).hasClass("tag-name")&&!v){return false}$(this).addClass("active-tag-area");f.hide();n.show();n.focus();var u=function(y){var x=m.attr("id");if(($(y.target).attr("id")!=x)&&($(y.target).parents().filter(x).length==0)){m.blur();$(document).unbind("click",u)}};$(window).click(u);return false});if(p.use_toggle_link){m.hide()}else{var r=o(p.tags);if(r==0){f.hide();n.show()}}return this.addClass("tag-element")}; \ No newline at end of file diff -r 9118054983c3 -r afa7946a126c static/scripts/packed/jquery.mousewheel.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/packed/jquery.mousewheel.js Tue Sep 22 21:51:11 2009 -0400 @@ -0,0 +1,11 @@ +/* Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. + * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. + * + * Version: 3.0.2 + * + * Requires: 1.2.2+ + */ +(function(c){var a=["DOMMouseScroll","mousewheel"];c.event.special.mousewheel={setup:function(){if(this.addEventListener){for(var d=a.length;d;){this.addEventListener(a[--d],b,false)}}else{this.onmousewheel=b}},teardown:function(){if(this.removeEventListener){for(var d=a.length;d;){this.removeEventListener(a[--d],b,false)}}else{this.onmousewheel=null}}};c.fn.extend({mousewheel:function(d){return d?this.bind("mousewheel",d):this.trigger("mousewheel")},unmousewheel:function(d){return this.unbind("mousewheel",d)}});function b(f){var d=[].slice.call(arguments,1),g=0,e=true;f=c.event.fix(f||window.event);f.type="mousewheel";if(f.wheelDelta){g=f.wheelDelta/120}if(f.detail){g=-f.detail/3}d.unshift(f,g);return c.event.handle.apply(this,d)}})(jQuery); \ No newline at end of file diff -r 9118054983c3 -r afa7946a126c static/scripts/packed/trackster.js --- a/static/scripts/packed/trackster.js Tue Sep 22 14:47:47 2009 -0400 +++ b/static/scripts/packed/trackster.js Tue Sep 22 21:51:11 2009 -0400 @@ -1,1 +1,1 @@ -var DENSITY=1000;var View=function(b,c,a,d){this.chr=b;this.length=c;this.low=a;this.high=d};$.extend(View.prototype,{move:function(b,a){this.low=Math.max(0,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(c){var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b/c/2;this.low=Math.floor(a-d);this.high=Math.ceil(a+d);if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){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(b,a,c){this.name=b;this.view=a;this.parent_element=c;this.make_container()};$.ext end(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>");this.header_div.text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>");this.container_div.append(this.header_div);this.container_div.append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(b,a,c){Track.call(this,b,a,c);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/DEN SITY);var j=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;j=Math.max(j,g.height())}a+=1}o.css("height",j);this.last_resolution=b;this.last_w_scale=p;this.tile_cache=n}});var DataCache=function(c,b,a){this.type=c;this.track=b;this.view=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 f=(b+1)*DENSITY*d;c[d][b]={state:"loading"};var e=function(g){return function(){$.getJSON(TRACKSTER_DATA_URL+g.type,{chrom:g.view.chr,low:a,high:f,dataset_id:g.track.dataset_id},function(h){if(h=="pending"){setTimeout(e,5000)}else{c[d][b]={state:"loaded",values:h}}$(document).trigger("redraw")})}}(this);e()}return c[d][b]}});var LineTrack=function(c,b,d,a){Track.call(this,c,b,d);this.container_div.addClass("line-track");this.dataset_id=a;this.cache=new DataCache("",this,b)};$.extend(LineTra ck.prototype,TiledTrack.prototype,{make_container:function(){Track.prototype.make_container.call(this);this.content_div.css("height",100)},draw_tile:function(n,q,g,j,h){var s=q*DENSITY*n,d=(q+1)*DENSITY*n,a=DENSITY*n;var k=this.cache.get(n,q);var b;if(k.state=="loading"){b=$("<div class='loading tile'></div>")}else{b=$("<canvas class='tile'></canvas>")}b.css({position:"absolute",top:0,left:(s-this.view.low)*j,width:Math.ceil(a*j),height:100});g.append(b);if(k.state=="loading"){l=false;return null}var f=b;f.get(0).width=f.width();f.get(0).height=f.height();var m=f.get(0).getContext("2d");var l=false;m.beginPath();var t=k.values;for(var o=0;o<t.length-1;o++){var r=t[o][0]-s;var e=t[o][1];var p=t[o+1][0]-s;var c=t[o+1][1];console.log(r,e,p,c);if(isNaN(e)||isNaN(c)){l=false}else{r=r*j;p=p*j;e=h-e*(h);c=h-c*(h);if(l){m.lineTo(r,e,p,c)}else{m.moveTo(r,e,p,c);l=true}}}m.stroke();return b}});var LabelTrack=function(a,b){Track.call(this,null,a,b);this.container_div.addClass("label-tr ack")};$.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(c,b,d,a){Track.call(this,c,b,d);this.container_div.addClass("feature-track");this.dataset_id=a;this.cache=new DataCache("",this,b)};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{get_data_async:function(){var a=this;$.getJSON("data",{chr:this.view.chr,dataset_id:this.dataset_id},function(b){a.values=b;a.draw()})},draw_tile:function(x,y,h,m,l){var z=y*DENSITY*x,c=(y+1)*DENSITY*x,a=DENSITY*x;var q=this.view,s=q.high-q.low,u= this.content_div.width(),p=[],A=$("<div class='tile' style='position: relative;'></div>");var o=this.cache.get(x,y);if(o.state=="loading"){h.addClass("loading");return null}else{h.removeClass("loading")}var b=o.values;for(var k in b){var v=b[k];var f=v[1],e=v[2],t=v[5];var g=(f-z)*m;var n=(e-z)*m;var w=n-g;var j=g;var d=p.length;for(i in p){if(p[i]<j){d=i;break}}p[d]=Math.ceil(n);var r=$("<div class='feature'></div>").css({position:"absolute",left:g,top:(d*(itemHeight+itemPad)),height:itemHeight,width:Math.max(w,1)});A.append(r)}A.css({position:"absolute",top:0,left:(z-this.view.low)*m,width:Math.ceil(a*m),height:p.length*(itemHeight+itemPad)+itemPad});h.append(A);return A}});var TrackLayout=function(a){this.view=a;this.tracks=[]};$.extend(TrackLayout.prototype,{add:function(a){this.tracks.push(a)},redraw:function(){for(var a in this.tracks){this.tracks[a].draw()}$("#overview-box").css({left:(this.view.low/this.view.length)*$("#overview-viewport").width(),width:Math.max(1,(( this.view.high-this.view.low)/this.view.length)*$("#overview-viewport").width())}).show();$("#low").text(this.view.low);$("#high").text(this.view.high)}}); \ 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){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){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_di v.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:true,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();return h}});var LabelTrack=function(a){Track.call(this,nul l,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.width()/(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("getfeature",{start:this.view.max_low,end:this.view.max_high,dataset_id:this.dataset_id,chrom:this.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.show_labels_scale&&this.showing_labels){this.showing_labels=fals e;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,"border-right":"1px solid #ddd"});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.blocks[s];block_start=Math.floor(Mat h.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 9118054983c3 -r afa7946a126c static/scripts/trackster.js --- a/static/scripts/trackster.js Tue Sep 22 14:47:47 2009 -0400 +++ b/static/scripts/trackster.js Tue Sep 22 21:51:11 2009 -0400 @@ -1,26 +1,92 @@ +/* Trackster + 2009, James Taylor, Kanwei Li +*/ + var DENSITY = 1000;
-var BLOCK_SIZE = 1000; +var DataCache = function( type, track ) { + this.type = type; + this.track = track; + this.cache = Object(); +}; +$.extend( DataCache.prototype, { + get: function( resolution, position ) { + var cache = this.cache; + if ( !( cache[resolution] && cache[resolution][position] ) ) { + if ( !cache[resolution] ) { + cache[resolution] = Object(); + } + var low = position * DENSITY * resolution; + var high = ( position + 1 ) * DENSITY * resolution; + cache[resolution][position] = { state: "loading" }; + + $.getJSON( data_url, { track_type: this.track.track_type, chrom: this.track.view.chrom, low: low, high: high, dataset_id: this.track.dataset_id }, function ( data ) { + if( data == "pending" ) { + setTimeout( fetcher, 5000 ); + } else { + cache[resolution][position] = { state: "loaded", values: data }; + } + $(document).trigger( "redraw" ); + }); + } + return cache[resolution][position]; + } +});
-var log = function( x, b ) { return Math.log( x ) / Math.log( b ) } - -var View = function( chr, length, low, high ) { - this.chr = chr; - this.length = length; - this.low = low; - this.high = high; +var View = function( chrom, max_length ) { + this.chrom = chrom; + this.tracks = []; + this.max_low = 0; + this.max_high = max_length; + this.low = this.max_low; + this.high = this.max_high; + this.length = this.max_high - this.max_low; }; $.extend( View.prototype, { + add_track: function ( track ) { + track.view = this; + this.tracks.push( track ); + if (track.init) { track.init(); } + }, + redraw: function () { + // Overview + $("#overview-box").css( { + left: ( this.low / this.length ) * $("#overview-viewport").width(), + width: Math.max( 4, ( ( this.high - this.low ) / this.length ) * $("#overview-viewport").width() ) + }).show(); + $("#low").text( this.low ); + $("#high").text( this.high ); + for ( var i in this.tracks ) { + this.tracks[i].draw(); + } + $("#bottom-spacer").remove(); + $("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>'); + }, move: function ( new_low, new_high ) { - this.low = Math.max( 0, Math.floor( new_low ) ); + this.low = Math.max( this.max_low, Math.floor( new_low ) ); this.high = Math.min( this.length, Math.ceil( new_high ) ); }, - zoom_in: function ( factor ) { - var center = ( this.low + this.high ) / 2; + zoom_in: function ( factor, point ) { var range = this.high - this.low; var diff = range / factor / 2; + + if (point == undefined) { + var center = ( this.low + this.high ) / 2; + } else { + // console.log(100*point/$(document).width()); + var center = this.low + range * point / $(document).width(); + } + // console.log(center); this.low = Math.floor( center - diff ); this.high = Math.ceil( center + diff ); + if (this.low < this.max_low) { + this.low = this.max_low; + this.high = range / factor; + } else if (this.high > this.max_high) { + this.high = this.max_high; + this.low = this.max_high - range / factor; + // console.log(this.high, this.low); + } if (this.high - this.low < 1 ) { this.high = this.low + 1; } @@ -56,27 +122,21 @@ } });
-var Track = function ( name, view, parent_element ) { +var Track = function ( name, parent_element ) { this.name = name; - this.view = view; this.parent_element = parent_element; this.make_container(); }; $.extend( Track.prototype, { - make_container : function () { - this.header_div = $("<div class='track-header'>"); - this.header_div.text( this.name ); + 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>"); - this.container_div.append( this.header_div ); - this.container_div.append( this.content_div ); + 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( name, view, parent_element ) { - Track.call( this, name, view, parent_element ); - // For caching +var TiledTrack = function() { this.last_resolution = null; this.last_w_scale = null; this.tile_cache = {}; @@ -87,23 +147,20 @@ high = this.view.high, range = high - low;
- var resolution = Math.pow( BLOCK_SIZE, Math.floor( log( range, BLOCK_SIZE ) ) ); - // Math.pow( 10, Math.ceil( Math.log( range / DENSITY ) / Math.log( 10 ) ) ); - - console//.log( "resolution:", resolution ); + var resolution = Math.pow( 10, Math.ceil( Math.log( range / DENSITY ) / Math.log( 10 ) ) ); resolution = Math.max( resolution, 1 ); - resolution = Math.min( resolution, 1000000 ); + resolution = Math.min( resolution, 100000 );
- var parent_element = $("<div style='position: relative;'></div>"); - this.content_div.children( ":first" ).remove(); - this.content_div.append( parent_element ); + var parent_element = $("<div style='position: relative;'></div>"); + this.content_div.children( ":first" ).remove(); + this.content_div.append( parent_element );
var w = this.content_div.width(), h = this.content_div.height(), - w_scale = w / range, - old_tiles = {}, + w_scale = w / range, + old_tiles = {}, new_tiles = {}; - + // If resolution and scale are unchanged, try to reuse tiles if ( this.last_resolution == resolution && this.last_w_scale == w_scale ) { old_tiles = this.tile_cache; @@ -125,10 +182,10 @@ // Our responsibility to move the element to the new parent parent_element.append( tile_element ); } else { - // console.log( "new tile" ); tile_element = this.draw_tile( resolution, tile_index, parent_element, w_scale, h ); } if ( tile_element ) { + // console.log( typeof(tile_element) ); new_tiles[tile_index] = tile_element; max_height = Math.max( max_height, tile_element.height() ); } @@ -143,53 +200,37 @@ } });
-var DataCache = function( type, track, view ) { - this.type = type; - this.track = track; - this.view = view; - this.cache = Object(); -}; -$.extend( DataCache.prototype, { - get: function( resolution, position ) { - var cache = this.cache; - if ( ! ( cache[resolution] && cache[resolution][position] ) ) { - if ( ! cache[resolution] ) { - cache[resolution] = Object(); - } - var low = position * DENSITY * resolution; - var high = ( position + 1 ) * DENSITY * resolution; - cache[resolution][position] = { state: "loading" }; - // use closure to preserve this and parameters for getJSON - var fetcher = function (ref) { - return function () { - $.getJSON( TRACKSTER_DATA_URL, { track_type: ref.type, chrom: ref.view.chr, low: low, high: high, dataset_id: ref.track.dataset_id }, function ( data ) { - if( data == "pending" ) { - setTimeout( fetcher, 5000 ); - } else { - cache[resolution][position] = { state: "loaded", values: data }; - } - $(document).trigger( "redraw" ); - }); - }; - }(this); - fetcher(); - } - return cache[resolution][position]; - } -}); - -var LineTrack = function ( name, view, parent_element, dataset_id ) { - Track.call( this, name, view, parent_element ); +var LineTrack = function ( name, dataset_id, height ) { + Track.call( this, name, $("#viewport") ); + + this.track_type = "line"; + this.height_px = (height ? height : 100); this.container_div.addClass( "line-track" ); this.dataset_id = dataset_id; - this.cache = new DataCache( "line", this, view ); + this.cache = new DataCache( "", this ); }; $.extend( LineTrack.prototype, TiledTrack.prototype, { make_container: function () { - Track.prototype.make_container.call( this ); - this.content_div.css( "height", 100 ); + 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); + if (data) { + track.min_value = data['min']; + track.max_value = data['max']; + track.vertical_range = track.max_value - track.min_value; + track.view.redraw(); + } + }); }, draw_tile: function( resolution, tile_index, parent_element, w_scale, h_scale ) { + if (!this.vertical_range) // We don't have the necessary information yet + return; + var tile_low = tile_index * DENSITY * resolution, tile_high = ( tile_index + 1 ) * DENSITY * resolution, tile_length = DENSITY * resolution; @@ -204,71 +245,64 @@ position: "absolute", top: 0, left: ( tile_low - this.view.low ) * w_scale, - width: Math.ceil( tile_length * w_scale ), - height: 100 }); parent_element.append( element ); - // Chunk is still loading, do noting + // Chunk is still loading, do nothing if ( chunk.state == "loading" ) { in_path = false; return null; } var canvas = element; - canvas.get(0).width = canvas.width(); - canvas.get(0).height = canvas.height(); - var data = chunk.values; - if ( data ) { - var ctx = canvas.get(0).getContext("2d"); - var in_path = false; - ctx.beginPath(); - // console.log( "Drawing tile" ); - for ( var i = 0; i < data.length - 1; i++ ) { - var x1 = data[i][0] - tile_low; - var y1 = data[i][1]; - var x2 = data[i+1][0] - tile_low; - var y2 = data[i+1][1]; - // Missing data causes us to stop drawing - if ( isNaN( y1 ) || isNaN( y2 ) ) { - in_path = false; - } else { - // Translate - x1 = x1 * w_scale; - x2 = x2 * w_scale; - y1 = h_scale - y1 * ( h_scale ); - y2 = h_scale - y2 * ( h_scale ); - if ( in_path ) { - ctx.lineTo( x1, y1, x2, y2 ); - } else { - ctx.moveTo( x1, y1, x2, y2 ); - in_path = true; - } - } - } - ctx.stroke(); - } - return element; + canvas.get(0).width = Math.ceil( tile_length * w_scale ); + canvas.get(0).height = this.height_px; + var ctx = canvas.get(0).getContext("2d"); + var in_path = false; + ctx.beginPath(); + var data = chunk.values; + if (!data) return; + for ( var i = 0; i < data.length - 1; i++ ) { + var x = data[i][0] - tile_low; + var y = data[i][1]; + // Missing data causes us to stop drawing + if ( isNaN( y ) ) { + in_path = false; + } else { + // Translate + x = x * w_scale; + y_above_min = y - this.min_value; + y = y_above_min / this.vertical_range * this.height_px; + if ( in_path ) { + ctx.lineTo( x, y ); + } else { + ctx.moveTo( x, y ); + in_path = true; + } + } + } + ctx.stroke(); + return element; } });
-var LabelTrack = function ( view, parent_element ) { - Track.call( this, null, view, parent_element ); +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>"); + 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(); @@ -281,96 +315,137 @@ thinHeight = 7, thinOffset = 3;
-var FeatureTrack = function ( name, view, parent_element,dataset_id ) { - Track.call( this, name, view, parent_element ); +var FeatureTrack = function ( name, dataset_id ) { + Track.call( this, name, $("#viewport") ); + this.track_type = "feature"; this.container_div.addClass( "feature-track" ); this.dataset_id = dataset_id; - this.cache = new DataCache( "", this, view ); + this.zo_slots = new Object(); + this.show_labels_scale = 0.01; + this.showing_labels = false; }; $.extend( FeatureTrack.prototype, TiledTrack.prototype, { - get_data_async: function() { + + calc_slots: function( include_labels ) { + // console.log("num vals: " + this.values.length); + 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(); + var dummy_canvas = $("<canvas></canvas>").get(0).getContext("2d"); + for (var i in this.values) { + + feature = this.values[i]; + f_start = Math.floor( Math.max(this.view.max_low, (feature.start - this.view.max_low) * scale) ); + if (include_labels) { + f_start -= dummy_canvas.measureText(feature.name).width; + } + + f_end = Math.ceil( Math.min(this.view.max_high, (feature.end - this.view.max_low) * scale) ); + // if (include_labels) { console.log(f_start, f_end); } + j = 0; + while (true) { + if (end_ary[j] == undefined || end_ary[j] < f_start) { + end_ary[j] = f_end; + if (include_labels) { + this.zi_slots[feature.name] = j; + } else { + this.zo_slots[feature.name] = j; + } + break; + } + j++; + } + } + }, + + init: function() { var track = this; - $.getJSON( "data", { chr: this.view.chr, dataset_id: this.dataset_id }, function ( data ) { + $.getJSON( "getfeature", { 'start': this.view.max_low, 'end': this.view.max_high, 'dataset_id': this.dataset_id, 'chrom': this.view.chrom }, function ( data ) { track.values = data; + track.calc_slots(); + track.slots = track.zo_slots; + // console.log(track.zo_slots); track.draw(); }); }, + draw_tile: function( resolution, tile_index, parent_element, w_scale, h_scale ) { + if (!this.values) // Still loading + return null; + + if (w_scale > this.show_labels_scale && !this.showing_labels) { + this.showing_labels = true; + if (!this.zi_slots) this.calc_slots(true); // Once we zoom in enough, show name labels + this.slots = this.zi_slots; + } else if (w_scale <= this.show_labels_scale && this.showing_labels) { + this.showing_labels = false; + this.slots = this.zo_slots; + } + // console.log(this.slots); + var tile_low = tile_index * DENSITY * resolution, tile_high = ( tile_index + 1 ) * DENSITY * resolution, tile_length = DENSITY * resolution; - + // console.log(tile_low, tile_high, tile_length, w_scale); var view = this.view, range = view.high - view.low, - width = this.content_div.width(), - slots = [], - new_div = $("<div class='tile' style='position: relative;'></div>"); - - var chunk = this.cache.get( resolution, tile_index ); - if ( chunk.state == "loading" ) { - parent_element.addClass("loading"); - return null; - } else { - parent_element.removeClass("loading"); - } - var values = chunk.values; - - for ( var index in values ) { - var value = values[index]; - var start = value[1], end = value[2], strand = value[5]; - // Determine slot based on entire feature and label - var screenStart = ( start - tile_low ) * w_scale; - var screenEnd = ( end - tile_low ) * w_scale; - var screenWidth = screenEnd - screenStart; - var screenStartWithLabel = screenStart; - // Determine slot - var slot = slots.length; - for ( i in slots ) { - if ( slots[i] < screenStartWithLabel ) { - slot = i; - break; - } - } - slots[slot] = Math.ceil( screenEnd ); - var feature_div = $("<div class='feature'></div>").css( { - position: 'absolute', - left: screenStart, - top: (slot*(itemHeight+itemPad)), - height: itemHeight, - width: Math.max( screenWidth, 1 ) - }); - new_div.append( feature_div ); - } - new_div.css( { + width = Math.ceil( tile_length * w_scale ), + slots = new Array(), + height = 200, + new_canvas = $("<canvas class='tile'></canvas>"); + + new_canvas.css({ position: "absolute", top: 0, left: ( tile_low - this.view.low ) * w_scale, - width: Math.ceil( tile_length * w_scale ), - height: slots.length * ( itemHeight + itemPad ) + itemPad + "border-right": "1px solid #ddd" }); - parent_element.append( new_div ); - return new_div; - } + new_canvas.get(0).width = width; + new_canvas.get(0).height = height; + // console.log(( tile_low - this.view.low ) * w_scale, tile_index, w_scale); + var ctx = new_canvas.get(0).getContext("2d"); + + var j = 0; + for (var i in this.values) { + feature = this.values[i]; + if (feature.start <= tile_high && feature.end >= tile_low) { + f_start = Math.floor( Math.max(0, (feature.start - tile_low) * w_scale) ); + f_end = Math.ceil( Math.min(width, (feature.end - tile_low) * w_scale) ); + // console.log(feature.start, feature.end, f_start, f_end, j); + ctx.fillStyle = "#000"; + ctx.fillRect(f_start, this.slots[feature.name] * 10 + 5, f_end - f_start, 1); + + if (this.showing_labels && ctx.fillText) { + ctx.font = "10px monospace"; + ctx.textAlign = "right"; + ctx.fillText(feature.name, f_start, this.slots[feature.name] * 10 + 8); + } + + if (feature.exon_start && feature.exon_end) { + var exon_start = Math.floor( Math.max(0, (feature.exon_start - tile_low) * w_scale) ); + var exon_end = Math.ceil( Math.min(width, (feature.exon_end - tile_low) * w_scale) ); + // ctx.fillRect(exon_start, j * 10 + 3, exon_end - exon_start, 5); + // ctx.fillRect(exon_start, this.slots[feature.name] * 10 + 3, exon_end - exon_start, 3); + } + + for (var i in feature.blocks) { + block = feature.blocks[i]; + block_start = Math.floor( Math.max(0, (block[0] - tile_low) * w_scale) ); + block_end = Math.ceil( Math.min(width, (block[1] - tile_low) * w_scale) ); + var thickness = 3, y_start = 4; + if (exon_start && block_start >= exon_start && block_end <= exon_end) { + thickness = 5, y_start = 3; + } + ctx.fillRect(exon_start, this.slots[feature.name] * 10 + y_start, block_end - block_start, thickness); + // console.log(block_start, block_end); + } + + j++; + } + } + + parent_element.append( new_canvas ); + return new_canvas; + }, }); - -var TrackLayout = function ( view ) { - this.view = view; - this.tracks = []; -}; -$.extend( TrackLayout.prototype, { - add: function ( track ) { - this.tracks.push( track ); - }, - redraw : function () { - for ( var index in this.tracks ) { - this.tracks[index].draw(); - } - // Overview - $("#overview-box").css( { - left: ( this.view.low / this.view.length ) * $("#overview-viewport").width(), - width: Math.max( 1, ( ( this.view.high - this.view.low ) / this.view.length ) * $("#overview-viewport").width() ) - }).show(); - $("#low").text( this.view.low ); - $("#high").text( this.view.high ); - } -}); \ No newline at end of file diff -r 9118054983c3 -r afa7946a126c static/trackster.css --- a/static/trackster.css Tue Sep 22 14:47:47 2009 -0400 +++ b/static/trackster.css Tue Sep 22 21:51:11 2009 -0400 @@ -3,7 +3,7 @@ padding: 0; font-family: verdana; font-size: 75%; - overflow-y: scroll; + overflow-y: hidden; }
#content { @@ -15,21 +15,18 @@ }
#nav { - padding: 1em; position: fixed; bottom: 0; width: 100%; - background: rgb( 64, 64, 64 ); + background: #333; color: white; font-weight: bold; text-align: center; }
-#nav > div -{ - margin: 0 0; +#nav-controls { + padding: 15px 0; } - #nav-controls a { color: white; padding: 0.1em 0.4em; @@ -42,26 +39,30 @@
#overview { width: 100%; - padding: 1em 0; - background: rgb( 64, 64, 64 ); + padding: 10px 0 0 0; + margin: 0px; + background: #333; color: white; font-weight: bold; }
#overview-viewport { - position: relative; height: 10px; - border-top: solid gray 1px; - border-bottom: solid gray 1px; + border-top: solid #666 1px; + border-bottom: solid #666 1px; + background: #888; } #overview-box { position: absolute; height: 10px; - background: white; + background: #ddd; }
#viewport { - min-height: 100%; + overflow: hidden; + background-color: #fff; +/* overflow: scroll;*/ +/* border-bottom: 2px solid black;*/ }
#viewport-canvas { @@ -70,14 +71,12 @@ }
.track { - border-top: solid gray 1px; +/* border-top: solid gray 1px;*/ border-bottom: solid gray 1px; - margin: 5px 0; }
.track-header { text-align: center; - padding: 0.1 0 0.3em 0; }
.track-content { @@ -92,12 +91,12 @@ }
.label-track .label { - border-left: solid grey 1px; + border-left: solid gray 1px; padding-left: 2px; }
.feature-track .feature { - background: brown; + }
.feature-track .feature .forward { diff -r 9118054983c3 -r afa7946a126c templates/tracks/browser.mako --- a/templates/tracks/browser.mako Tue Sep 22 14:47:47 2009 -0400 +++ b/templates/tracks/browser.mako Tue Sep 22 21:51:11 2009 -0400 @@ -7,36 +7,81 @@
<%def name="javascripts()"> ${parent.javascripts()} -<script type="text/javascript" src="/static/scripts/jquery.event.drag.js"></script> -<script type="text/javascript" src="/static/scripts/trackster.js"></script> +${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "trackster" )} + <script type="text/javascript">
- ## HACK - TRACKSTER_DATA_URL = "${h.url_for( action='data' )}"; - - var view = new View( "${chrom}", ${LEN}, 0, ${max(LEN,1)} ); - var tracks = new TrackLayout( view ); - var dbkey = "${dbkey}"; + var data_url = "${h.url_for( action='data' )}"; + var view = new View( "${chrom}", ${LEN} );
$(function() {
- tracks.add( new LabelTrack( view, $("#viewport" ) ) ); - %for track in tracks: - tracks.add( new ${track["type"]}( "${track["name"]}", view, $("#viewport" ), ${track["id"]} ) ); - %endfor + view.add_track( new LabelTrack( $("#overview" ) ) ); + view.add_track( new LabelTrack( $("#nav-labeltrack" ) ) ); + + %for track in tracks: + view.add_track( new ${track["type"]}( "${track['name']}", ${track['dataset_id']} ) ); + %endfor
$(document).bind( "redraw", function( e ) { - tracks.redraw(); - }); - - $(window).resize( function( e ) { - tracks.redraw(); + view.redraw(); });
+ $(document).bind("mousewheel", function(e, delta) { + if (delta > 0) { + view.zoom_in(2, e.pageX); + view.redraw(); + } else { + view.zoom_out(2); + view.redraw(); + } + }); + + $(document).bind("dblclick", function(e) { + view.zoom_in(2, e.pageX); + view.redraw(); + }); + + $("#overview-box").bind("dragstart", function(e) { + this.current_x = e.offsetX; + }).bind("drag", function(e) { + var delta = e.offsetX - this.current_x; + this.current_x = e.offsetX; + + var delta_chrom = Math.round(delta / $(document).width() * (view.max_high - view.max_low)); + var view_range = view.high - view.low; + + var new_low = view.low += delta_chrom; + var new_high = view.high += delta_chrom; + if (new_low < view.max_low) { + new_low = 0; + new_high = view_range; + } else if (new_high > view.max_high) { + new_high = view.max_high; + new_low = view.max_high - view_range; + } + view.low = new_low; + view.high = new_high; + view.redraw(); + }); + + $(window).resize( function( e ) { + $("#viewport").height( $(window).height() - 120 ); + view.redraw(); + }); + $("#viewport").bind( "dragstart", function ( e ) { this.original_low = view.low; + this.current_height = e.clientY; }).bind( "drag", function( e ) { var move_amount = ( e.offsetX - this.offsetLeft ) / this.offsetWidth; + var new_scroll = $(this).scrollTop() - (e.clientY - this.current_height); + + if ( new_scroll < $(this).get(0).scrollHeight - $(this).height() - 200) { + $(this).scrollTop(new_scroll); + + } + this.current_height = e.clientY; var range = view.high - view.low; var move_bases = Math.round( range * move_amount ); var new_low = this.original_low - move_bases; @@ -50,83 +95,58 @@ } view.low = new_low; view.high = new_high; - tracks.redraw(); + view.redraw(); }); - tracks.redraw(); - load_chroms(); + (function () { + $.getJSON( "${h.url_for( action='chroms' )}", { dbkey: "${dbkey}" }, function ( data ) { + var chrom_options = '<option value=""></option>'; + for (i in data) { + chrom = data[i]['chrom'] + if( chrom == view.chrom ) { + chrom_options += '<option value="' + chrom + '" selected="true">' + chrom + '</option>'; + } else { + chrom_options += '<option value="' + chrom + '">' + chrom + '</option>'; + } + } + $("#chrom").html(chrom_options); + $("#chrom").bind( "change", function () { + $("#chr").submit(); + }); + }); + })(); + view.redraw(); + $(window).trigger("resize"); }); - - var load_chroms = function () { - var fetcher = function (ref) { - return function () { - $.getJSON( "${h.url_for( action='chroms' )}", { dbkey: dbkey }, function ( data ) { - // Hacky - check length of "object" - var chrom_length = 0; - for (key in data) chrom_length++; - if( chrom_length == 0 ) { - setTimeout( fetcher, 5000 ); - } else { - var chrom_options = ''; - for (key in data) { - if( key == view.chr ) { - chrom_options += '<option value="' + key + '" selected="true">' + key + '</option>'; - } else { - chrom_options += '<option value="' + key + '">' + key + '</option>'; - } - } - $("#chrom").html(chrom_options); - $("#chrom").bind( "change", function ( e ) { - $("#chr").submit(); - }); - if( view.chr == "" ) { - $("#chrom option:first").attr("selected", true); - $("#chrom").trigger( "change" ); - } - } - }); - }; - }(this); - fetcher(); - };
</script> </%def>
+<div id="content"> + <div id="overview"> + <div id="overview-viewport"> + <div id="overview-box"></div> + </div> + </div> + <div id="viewport"></div> +</div> +<div id="nav"> + <div id="nav-labeltrack"></div> + <div id="nav-controls"> + <form name="chr" id="chr" method="get"> + <input type="hidden" name="dataset_ids" value="${dataset_ids}" /> + <a href="#" onclick="javascript:view.left(5);view.redraw();"><<</a> + <a href="#" onclick="javascript:view.left(2);view.redraw();"><</a> + <select id="chrom" name="chrom"> + <option value="">Loading</option> + </select> + <span id="low">0</span>—<span id="high">${LEN}</span> + <span style="display: inline-block; width: 10em;"> + <a href="#" onclick="javascript:view.zoom_in(2);view.redraw();">+</a> + <a href="#" onclick="javascript:view.zoom_out(2);view.redraw();">-</a> + </span>
-<div id="content"> - - <div id="overview"> - <div id="overview-viewport"> - <div id="overview-box"></div> - </div> + <a href="#" onclick="javascript:view.right(2);view.redraw();">></a> + <a href="#" onclick="javascript:view.right(5);view.redraw();">>></a> + </form> </div> - - - <div id="viewport"> - </div> - </div> - <div id="nav"> - - <div id="nav-controls"> - <form name="chr" id="chr" method="GET"> - <input type="hidden" name="dataset_ids" value="${dataset_ids}" /> - <a href="#" onclick="javascript:view.left(5);tracks.redraw();"><<</a> - <a href="#" onclick="javascript:view.left(2);tracks.redraw();"><</a> - <span style="display: inline-block; width: 30em; text-align: center;">Viewing - <select id="chrom" name="chrom"> - <option value="">loading</option> - </select> - <span id="low">0</span>-<span id="high">180857866</span></span> - <span style="display: inline-block; width: 10em;"> - <a href="#" onclick="javascript:view.zoom_in(2);tracks.redraw();">+</a> - <a href="#" onclick="javascript:view.zoom_out(2);tracks.redraw();">-</a> - </span> - - <a href="#" onclick="javascript:view.right(2);tracks.redraw();">></a> - <a href="#" onclick="javascript:view.right(5);tracks.redraw();">>></a> - </form> - </div> - - </div> - diff -r 9118054983c3 -r afa7946a126c templates/tracks/new_browser.mako --- a/templates/tracks/new_browser.mako Tue Sep 22 14:47:47 2009 -0400 +++ b/templates/tracks/new_browser.mako Tue Sep 22 21:51:11 2009 -0400 @@ -12,39 +12,38 @@ </%def>
<div class="form"> - <div class="form-title">Select datasets to include in browser</div> - <div id="dbkey" class="form-body"> - <form id="form" method="POST"> - <div class="form-row"> - <label for="dbkey">Reference genome build (dbkey): </label> - <div class="form-row-input"> - <select name="dbkey" id="dbkey" refresh_on_change="true"> - %for tmp_dbkey in dbkey_set: - <option value="${tmp_dbkey}" - %if tmp_dbkey == dbkey: - selected="true" - %endif - >${tmp_dbkey}</option> - %endfor - </select> - </div> - <div style="clear: both;"></div> - </div> - <div class="form-row"> - <label for="dataset_ids">Datasets to include: </label> - %for key,value in datasets.items(): - <div> - <input type="checkbox" name="dataset_ids" value="${key}" /> - ${value} - </div> - %endfor - - <div style="clear: both;"></div> - </div> - </div> - <div class="form-row"> - <input type="submit" name="browse" value="Browse"/> - </div> + <div class="form-title">Select datasets to include in browser</div> + <div id="dbkey" class="form-body"> + <form id="form" method="POST"> + <div class="form-row"> + <label for="dbkey">Reference genome build (dbkey): </label> + <div class="form-row-input"> + <select name="dbkey" id="dbkey" refresh_on_change="true"> + %for tmp_dbkey in dbkey_set: + <option value="${tmp_dbkey}" + %if tmp_dbkey == dbkey: + selected="true" + %endif + >${tmp_dbkey}</option> + %endfor + </select> + </div> + <div style="clear: both;"></div> + </div> + <div class="form-row"> + <label for="dataset_ids">Datasets to include: </label> + %for key,value in datasets.items(): + <div> + <input type="checkbox" name="dataset_ids" value="${key}" /> + ${value} + </div> + %endfor + + <div style="clear: both;"></div> + </div> + </div> + <div class="form-row"> + <input type="submit" name="browse" value="Browse"/> + </div> </form> - </div> </div>
galaxy-dev@lists.galaxyproject.org