details: http://www.bx.psu.edu/hg/galaxy/rev/979534de254d changeset: 2934:979534de254d user: Kanwei Li <kanwei@gmail.com> date: Thu Oct 29 18:28:11 2009 -0400 description: trackster: fixed browser crashing (were drawing huge canvas tiles because of resolution restriction), implemented simple LRU diffstat: static/scripts/lrucache.js | 238 --------------------------------------- static/scripts/packed/lrucache.js | 1 - static/scripts/packed/trackster.js | 2 +- static/scripts/trackster.js | 46 ++++++- templates/tracks/browser.mako | 2 +- 5 files changed, 39 insertions(+), 250 deletions(-) diffs (359 lines): diff -r 28ec4708840f -r 979534de254d static/scripts/lrucache.js --- a/static/scripts/lrucache.js Thu Oct 29 17:57:30 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,238 +0,0 @@ -/* -MIT LICENSE -Copyright (c) 2007 Monsur Hossain (http://www.monsur.com) - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. -*/ - -// **************************************************************************** -// CachePriority ENUM -// An easier way to refer to the priority of a cache item -var CachePriority = { - Low: 1, - Normal: 2, - High: 4 -} - -// **************************************************************************** -// Cache constructor -// Creates a new cache object -// INPUT: maxSize (optional) - indicates how many items the cache can hold. -// default is -1, which means no limit on the -// number of items. -function Cache(maxSize) { - this.items = {}; - this.count = 0; - if (maxSize == null) - maxSize = -1; - this.maxSize = maxSize; - this.fillFactor = .75; - this.purgeSize = Math.round(this.maxSize * this.fillFactor); - - this.stats = {} - this.stats.hits = 0; - this.stats.misses = 0; -} - -// **************************************************************************** -// Cache.getItem -// retrieves an item from the cache, returns null if the item doesn't exist -// or it is expired. -// INPUT: key - the key to load from the cache -Cache.prototype.getItem = function(key) { - - // retrieve the item from the cache - var item = this.items[key]; - - if (item != null) { - if (!this._isExpired(item)) { - // if the item is not expired - // update its last accessed date - item.lastAccessed = new Date().getTime(); - } else { - // if the item is expired, remove it from the cache - this._removeItem(key); - item = null; - } - } - - // return the item value (if it exists), or null - var returnVal = null; - if (item != null) { - returnVal = item.value; - this.stats.hits++; - } else { - this.stats.misses++; - } - return returnVal; -} - -// **************************************************************************** -// Cache.setItem -// sets an item in the cache -// parameters: key - the key to refer to the object -// value - the object to cache -// options - an optional parameter described below -// the last parameter accepts an object which controls various caching options: -// expirationAbsolute: the datetime when the item should expire -// expirationSliding: an integer representing the seconds since -// the last cache access after which the item -// should expire -// priority: How important it is to leave this item in the cache. -// You can use the values CachePriority.Low, .Normal, or -// .High, or you can just use an integer. Note that -// placing a priority on an item does not guarantee -// it will remain in cache. It can still be purged if -// an expiration is hit, or if the cache is full. -// callback: A function that gets called when the item is purged -// from cache. The key and value of the removed item -// are passed as parameters to the callback function. -Cache.prototype.setItem = function(key, value, options) { - - function CacheItem(k, v, o) { - if ((k == null) || (k == '')) - throw new Error("key cannot be null or empty"); - this.key = k; - this.value = v; - if (o == null) - o = {}; - if (o.expirationAbsolute != null) - o.expirationAbsolute = o.expirationAbsolute.getTime(); - if (o.priority == null) - o.priority = CachePriority.Normal; - this.options = o; - this.lastAccessed = new Date().getTime(); - } - - // add a new cache item to the cache - if (this.items[key] != null) - this._removeItem(key); - this._addItem(new CacheItem(key, value, options)); - - // if the cache is full, purge it - if ((this.maxSize > 0) && (this.count > this.maxSize)) { - this._purge(); - } -} - -// **************************************************************************** -// Cache.clear -// Remove all items from the cache -Cache.prototype.clear = function() { - - // loop through each item in the cache and remove it - for (var key in this.items) { - this._removeItem(key); - } -} - -// **************************************************************************** -// Cache._purge (PRIVATE FUNCTION) -// remove old elements from the cache -Cache.prototype._purge = function() { - - var tmparray = new Array(); - - // loop through the cache, expire items that should be expired - // otherwise, add the item to an array - for (var key in this.items) { - var item = this.items[key]; - if (this._isExpired(item)) { - this._removeItem(key); - } else { - tmparray.push(item); - } - } - - if (tmparray.length > this.purgeSize) { - - // sort this array based on cache priority and the last accessed date - tmparray = tmparray.sort(function(a, b) { - if (a.options.priority != b.options.priority) { - return b.options.priority - a.options.priority; - } else { - return b.lastAccessed - a.lastAccessed; - } - }); - - // remove items from the end of the array - while (tmparray.length > this.purgeSize) { - var ritem = tmparray.pop(); - this._removeItem(ritem.key); - } - } -} - -// **************************************************************************** -// Cache._addItem (PRIVATE FUNCTION) -// add an item to the cache -Cache.prototype._addItem = function(item) { - this.items[item.key] = item; - this.count++; -} - -// **************************************************************************** -// Cache._removeItem (PRIVATE FUNCTION) -// Remove an item from the cache, call the callback function (if necessary) -Cache.prototype._removeItem = function(key) { - var item = this.items[key]; - delete this.items[key]; - this.count--; - - // if there is a callback function, call it at the end of execution - if (item.options.callback != null) { - var callback = function() { - item.options.callback(item.key, item.value); - } - setTimeout(callback, 0); - } -} - -// **************************************************************************** -// Cache._isExpired (PRIVATE FUNCTION) -// Returns true if the item should be expired based on its expiration options -Cache.prototype._isExpired = function(item) { - var now = new Date().getTime(); - var expired = false; - if ((item.options.expirationAbsolute) && (item.options.expirationAbsolute < now)) { - // if the absolute expiration has passed, expire the item - expired = true; - } - if ((expired == false) && (item.options.expirationSliding)) { - // if the sliding expiration has passed, expire the item - var lastAccess = item.lastAccessed + (item.options.expirationSliding * 1000); - if (lastAccess < now) { - expired = true; - } - } - return expired; -} - -Cache.prototype.toHtmlString = function() { - var returnStr = this.count + " item(s) in cache<br /><ul>"; - for (var key in this.items) { - var item = this.items[key]; - returnStr = returnStr + "<li>" + item.key.toString() + " = " + item.value.toString() + "</li>"; - } - returnStr = returnStr + "</ul>"; - return returnStr; -} diff -r 28ec4708840f -r 979534de254d static/scripts/packed/lrucache.js --- a/static/scripts/packed/lrucache.js Thu Oct 29 17:57:30 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -var CachePriority={Low:1,Normal:2,High:4};function Cache(a){this.items={};this.count=0;if(a==null){a=-1}this.maxSize=a;this.fillFactor=0.75;this.purgeSize=Math.round(this.maxSize*this.fillFactor);this.stats={};this.stats.hits=0;this.stats.misses=0}Cache.prototype.getItem=function(a){var c=this.items[a];if(c!=null){if(!this._isExpired(c)){c.lastAccessed=new Date().getTime()}else{this._removeItem(a);c=null}}var b=null;if(c!=null){b=c.value;this.stats.hits++}else{this.stats.misses++}return b};Cache.prototype.setItem=function(c,d,b){function a(f,e,g){if((f==null)||(f=="")){throw new Error("key cannot be null or empty")}this.key=f;this.value=e;if(g==null){g={}}if(g.expirationAbsolute!=null){g.expirationAbsolute=g.expirationAbsolute.getTime()}if(g.priority==null){g.priority=CachePriority.Normal}this.options=g;this.lastAccessed=new Date().getTime()}if(this.items[c]!=null){this._removeItem(c)}this._addItem(new a(c,d,b));if((this.maxSize>0)&&(this.count>this.maxSize)){this._purge()}} ;Cache.prototype.clear=function(){for(var a in this.items){this._removeItem(a)}};Cache.prototype._purge=function(){var d=new Array();for(var a in this.items){var b=this.items[a];if(this._isExpired(b)){this._removeItem(a)}else{d.push(b)}}if(d.length>this.purgeSize){d=d.sort(function(f,e){if(f.options.priority!=e.options.priority){return e.options.priority-f.options.priority}else{return e.lastAccessed-f.lastAccessed}});while(d.length>this.purgeSize){var c=d.pop();this._removeItem(c.key)}}};Cache.prototype._addItem=function(a){this.items[a.key]=a;this.count++};Cache.prototype._removeItem=function(a){var b=this.items[a];delete this.items[a];this.count--;if(b.options.callback!=null){var c=function(){b.options.callback(b.key,b.value)};setTimeout(c,0)}};Cache.prototype._isExpired=function(c){var a=new Date().getTime();var b=false;if((c.options.expirationAbsolute)&&(c.options.expirationAbsolute<a)){b=true}if((b==false)&&(c.options.expirationSliding)){var d=c.lastAccessed+(c.options. expirationSliding*1000);if(d<a){b=true}}return b};Cache.prototype.toHtmlString=function(){var b=this.count+" item(s) in cache<br /><ul>";for(var a in this.items){var c=this.items[a];b=b+"<li>"+c.key.toString()+" = "+c.value.toString()+"</li>"}b=b+"</ul>";return b}; \ No newline at end of file diff -r 28ec4708840f -r 979534de254d static/scripts/packed/trackster.js --- a/static/scripts/packed/trackster.js Thu Oct 29 17:57:30 2009 -0400 +++ b/static/scripts/packed/trackster.js Thu Oct 29 18:28:11 2009 -0400 @@ -1,1 +1,1 @@ -var DENSITY=1000,DATA_ERROR="There was an error in indexing this dataset.",DATA_NONE="No data for this chrom/contig.",CACHED_TILES=50,CACHED_DATA=20;function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}var View=function(b,a){this.chrom=b;this.tracks=[];this.max_low=0;this.max_high=a;this.center=(this.max_high-this.max_low)/2;this.span=this.max_high-this.max_low;this.zoom_factor=2;this.zoom_level=0};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){var d=this.span/Math.pow(this.zoom_factor,this.zoom_level),b=this.center-(d/2),e=b+d;if(b<0){b=0;e=b+d}else{if(e>this.max_high){e=this.max_high;b=e-d}}this.low=Math.floor(b);this.high=Math.ceil(e);this.center=Math.round(this.low+(this.high-this.low)/2);$("#overview-box").css({left:(this.low/this.span)*$("#overview-viewport").width(),width:Math.max(12,((this.high-this.low)/this.span)*$("#overview-viewport").width())}).sh ow();$("#low").text(commatize(this.low));$("#high").text(commatize(this.high));for(var c=0,a=this.tracks.length;c<a;c++){this.tracks[c].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},zoom_in:function(a){if(this.max_high===0||this.high-this.low<5){return}if(a){this.center=a/$(document).width()*(this.high-this.low)+this.low}this.zoom_level+=1},zoom_out:function(){if(this.max_high===0){return}if(this.zoom_level<=0){this.zoom_level=0;return}this.zoom_level-=1}});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.tile_cache=new Cache(CACHED_TI LES)};$.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 l=$("<div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(l);var j=this.content_div.width(),d=this.content_div.height(),m=j/e;var g;var a=Math.floor(i/b/DENSITY);while((a*DENSITY*b)<c){var k=this.view.zoom_level+"_"+a;if(this.tile_cache[k]){g=this.tile_cache[k];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*m});l.append(g)}else{g=this.draw_tile(b,a,l,m,d)}if(g){this.tile_cache[k]=g}a+=1}}});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'>"+commatize(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"));TiledTrack.call(this);this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.dataset_id=b;this.cache=new Cache(CACHED_DATA)};$.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||b=="error"){a.content_div.addClass("error").text(DATA_ERROR)}else{if(b=="no data"){a.content_div.addClass("nodata").text(DATA_NONE)}else{a.content_div.css("height",a.height_px+"px");a.min_value=b.min;a.max_value=b.max;a.vertical_range=a.max_value-a.min_value;a.view.redraw()}}})},get_data:f unction(d,b){var c=this,a=b*DENSITY*d,f=(b+1)*DENSITY*d,e=d+"_"+b;$.getJSON(data_url,{track_type:this.track_type,chrom:this.view.chrom,low:a,high:f,dataset_id:this.dataset_id},function(g){c.cache[e]=g;$(document).trigger("redraw")})},draw_tile:function(d,a,m,o){if(!this.vertical_range){return}var h=a*DENSITY*d,b=DENSITY*d,c=$("<canvas class='tile'></canvas>"),l=d+"_"+a;if(!this.cache[l]){this.get_data(d,a);return}var g=this.cache[l];c.css({position:"absolute",top:0,left:(h-this.view.low)*o});c.get(0).width=Math.ceil(b*o);c.get(0).height=this.height_px;var n=c.get(0).getContext("2d");var e=false;n.beginPath();for(var f=0;f<g.length-1;f++){var k=g[f][0]-h;var j=g[f][1];if(isNaN(j)){e=false}else{k=k*o;j=(j-this.min_value)/this.vertical_range*this.height_px;if(e){n.lineTo(k,j)}else{n.moveTo(k,j);e=true}}}n.stroke();m.append(c);return c}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));TiledTrack.call(this);this.track_type="feature";this.height_px=(a?a:100);th is.container_div.addClass("feature-track");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.001;this.showing_labels=false;this.vertical_gap=10};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{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){if(b=="error"){a.content_div.addClass("error").text(DATA_ERROR)}else{if(b.length===0||b=="no data"){a.content_div.addClass("nodata").text(DATA_NONE)}else{a.content_div.css("height",a.height_px+"px");a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()}}})},calc_slots:function(o){var c=[],b=this.container_div.width()/(this.view.high-this.view.low),g=this.show_labels_scale,a=this.view.max_high,e=this.view.max_low;if(o){this.zi_slots={}}var m=$("<canvas></canvas>").get(0).getContext("2d");for(var f=0,h=this.values.length;f<h;f++){var k,l,n=this.values[f];if(o){k=Math.floor(Math.max(e,(n.start-e)*g));k-=m.mea sureText(n.name).width;l=Math.ceil(Math.min(a,(n.end-e)*g))}else{k=Math.floor(Math.max(e,(n.start-e)*b));l=Math.ceil(Math.min(a,(n.end-e)*b))}var d=0;while(true){if(c[d]===undefined||c[d]<k){c[d]=l;if(o){this.zi_slots[n.name]=d}else{this.zo_slots[n.name]=d}break}d++}}this.height_px=c.length*this.vertical_gap+15;this.content_div.css("height",this.height_px+"px")},draw_tile:function(t,y,g,n){if(!this.values){return null}if(n>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(n<=this.show_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var z=y*DENSITY*t,b=(y+1)*DENSITY*t,o=DENSITY*t;var r=Math.ceil(o*n),q=this.height_px,p=$("<canvas class='tile'></canvas>");p.css({position:"absolute",top:0,left:(z-this.view.low)*n});p.get(0).width=r;p.get(0).height=q;var s=p.get(0).getContext("2d");s.fillStyle="#000";s.font="10px monospace";s.textAlign="right";var v=0;for(var w=0,x=this.values.length;w<x;w++){var f=this.values[w];if(f.start<=b&&f.end>=z){var e=Math.floor(Math.max(0,(f.start-z)*n)),h=Math.ceil(Math.min(r,(f.end-z)*n)),d=this.slots[f.name]*this.vertical_gap;s.fillRect(e,d+5,h-e,1);if(this.showing_labels&&s.fillText){s.fillText(f.name,e,d+8)}var c,D;if(f.exon_start&&f.exon_end){c=Math.floor(Math.max(0,(f.exon_start-z)*n));D=Math.ceil(Math.min(r,(f.exon_end-z)*n));s.fillRect(c,d+4,D-c,3)}if(f.blocks){for(var u=0,B=f.blocks.length;u<B;u++){var m=f.blocks[u],l=Math.floor(Math.max(0,(m[0]-z)*n)),A=Math.ceil(Math.min(r,(m[1]-z)*n));var a=3,C=4;if(c&&l>=c&&A<=D){a=5;C=3}s.fillRect(l,d+C,A-l,a)}}v++}}g.append(p);return p}}); \ No newline at end of file +var DENSITY=1000,DATA_ERROR="There was an error in indexing this dataset.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_LOADING="Loading data...",CACHED_TILES=5,CACHED_DATA=20,CONTEXT=$("<canvas></canvas>").get(0).getContext("2d"),RIGHT_STRAND,LEFT_STRAND;var right_img=new Image();right_img.src="../images/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src="../images/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src="../images/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src="../images/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(l eft_img_inv,"repeat")};function commatize(b){b+="";var a=/(\d+)(\d{3})/;while(a.test(b)){b=b.replace(a,"$1,$2")}return b}var Cache=function(a){this.num_elements=a;this.obj_cache={};this.key_ary=[]};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!=-1){this.key_ary.splice(a,1);this.key_ary.push(b)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c}});var View=function(b,a){this.chrom=b;this.tracks=[];this.max_low=0;this.max_high=a;this.center=(this.max_high-this.max_low)/2;this.span=this.max_high-this.max_low;this.zoom_factor=2;this.zoom_level=0};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){var d=this.span/Math.pow(this.zoom_factor,this.zoom_level),b=this.center-(d/2),e=b+d;if(b<0){b=0;e=b+d}else{if(e>this.max_high){e =this.max_high;b=e-d}}this.low=Math.floor(b);this.high=Math.ceil(e);this.center=Math.round(this.low+(this.high-this.low)/2);$("#overview-box").css({left:(this.low/this.span)*$("#overview-viewport").width(),width:Math.max(12,((this.high-this.low)/this.span)*$("#overview-viewport").width())}).show();$("#low").val(commatize(this.low));$("#high").val(commatize(this.high));for(var c=0,a=this.tracks.length;c<a;c++){this.tracks[c].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},zoom_in:function(a){if(this.max_high===0||this.high-this.low<30){return}if(a){this.center=a/$(document).width()*(this.high-this.low)+this.low}this.zoom_level+=1;this.redraw()},zoom_out:function(){if(this.max_high===0){return}if(this.zoom_level<=0){this.zoom_level=0;return}this.zoom_level-=1;this.redraw()}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){thi s.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.tile_cache=new Cache(CACHED_TILES)};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var h=this.view.low,d=this.view.high,e=d-h;var c=Math.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));c=Math.max(c,0.1);c=Math.min(c,1000000);var j=$("<div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(j);var k=this.content_div.width()/e;var g;var a=Math.floor(h/c/DENSITY);while((a*DENSITY*c)<d){var i=this.view.zoom_level+"_"+a;var b=this.tile_cache.get(i);if(b){var f=a*DENSITY*c;b.css({left:(f-this.view.low)*k});j.append(b)}else{g=this.draw_tile(c,a,j,k)}if(g){this.tile_cache.set(i,g)}a+=1}}});var LabelTrack=function(a){Track.ca ll(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'>"+commatize(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"));TiledTrack.call(this);this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.dataset_id=b;this.cache=new Cache(CACHED_DATA)};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this;a.content_div.text(DATA_LOADING);$.getJSON(data_url,{stats:true,track_type:a.track_type,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dat aset_id},function(c){if(!c||c=="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR)}else{if(c=="no data"){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(c=="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},5000)}else{a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.min_value=c.min;a.max_value=c.max;a.vertical_range=a.max_value-a.min_value;var d=$("<div class='yaxislabel'>"+a.min_value+"</div>");var b=$("<div class='yaxislabel'>"+a.max_value+"</div>");b.css({position:"relative",top:"35px"});b.prependTo(a.container_div);d.css({position:"relative",top:a.height_px+32+"px",});d.prependTo(a.container_div);a.draw()}}}})},get_data:function(d,b){var c=this,a=b*DENSITY*d,f=(b+1)*DENSITY*d,e=d+"_"+b;$.getJSON(data_url,{track_type:this.track_type,chrom:this.view.chrom,low:a,high:f,dataset_id:this.dataset_id},function(g){c.cache[e]=g;$(document).trigger("redra w")})},draw_tile:function(d,a,m,o){if(!this.vertical_range){return}var h=a*DENSITY*d,b=DENSITY*d,c=$("<canvas class='tile'></canvas>"),l=d+"_"+a;if(!this.cache[l]){this.get_data(d,a);return}var g=this.cache[l];c.css({position:"absolute",top:0,left:(h-this.view.low)*o});c.get(0).width=Math.ceil(b*o);c.get(0).height=this.height_px;var n=c.get(0).getContext("2d");var e=false;n.beginPath();for(var f=0;f<g.length-1;f++){var k=g[f][0]-h;var j=g[f][1];if(isNaN(j)){e=false}else{k=k*o;j=(j-this.min_value)/this.vertical_range*this.height_px;if(e){n.lineTo(k,j)}else{n.moveTo(k,j);e=true}}}n.stroke();m.append(c);return c}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));TiledTrack.call(this);this.track_type="feature";this.height_px=(a?a:100);this.container_div.addClass("feature-track");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.001;this.showing_labels=false;this.vertical_gap=10;this.base_color="#2C3143"};$.extend(FeatureTrack.prototype,TiledTrack.pro totype,{init:function(){var a=this;a.content_div.text(DATA_LOADING);$.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){if(b=="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR)}else{if(b.length===0||b=="no data"){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(b=="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},5000)}else{a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()}}}})},calc_slots:function(o){var c=[],b=this.container_div.width()/(this.view.high-this.view.low),g=this.show_labels_scale,a=this.view.max_high,e=this.view.max_low;if(o){this.zi_slots={}}var m=$("<canvas></canvas>").get(0).getContext("2d");for(var f=0,h=this.values.length;f<h;f++){var k,l,n=this.values[f];if(o){k=Math.floor(Math.max(e,(n.star t-e)*g));k-=m.measureText(n.name).width;l=Math.ceil(Math.min(a,(n.end-e)*g))}else{k=Math.floor(Math.max(e,(n.start-e)*b));l=Math.ceil(Math.min(a,(n.end-e)*b))}var d=0;while(true){if(c[d]===undefined||c[d]<k){c[d]=l;if(o){this.zi_slots[n.name]=d}else{this.zo_slots[n.name]=d}break}d++}}this.height_px=c.length*this.vertical_gap+15;this.content_div.css("height",this.height_px+"px")},draw_tile:function(w,B,g,n){if(!this.values){return null}if(n>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(n<=this.show_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var C=B*DENSITY*w,c=(B+1)*DENSITY*w,q=DENSITY*w;var u=Math.ceil(q*n),t=this.height_px,s=$("<canvas class='tile'></canvas>");s.css({position:"absolute",top:0,left:(C-this.view.low)*n});s.get(0).width=u;s.get(0).height=t;var v=s.get(0).getContext("2d");v.fillStyle=this.base_color;v.font="10px monospac e";v.textAlign="right";var y=0;for(var z=0,A=this.values.length;z<A;z++){var f=this.values[z];if(f.start<=c&&f.end>=C){var e=Math.floor(Math.max(0,(f.start-C)*n)),h=Math.ceil(Math.min(u,(f.end-C)*n)),d=this.slots[f.name]*this.vertical_gap;var a,G,b=null,o=null;if(f.thick_start&&f.thick_end){b=Math.floor(Math.max(0,(f.thick_start-C)*n));o=Math.ceil(Math.min(u,(f.thick_end-C)*n))}if(!this.showing_labels){v.fillRect(e,d+5,h-e,1)}else{if(v.fillText){v.fillText(f.name,e-1,d+8)}var E=f.blocks;if(E){if(f.strand){if(f.strand=="+"){v.fillStyle=RIGHT_STRAND}else{if(f.strand=="-"){v.fillStyle=LEFT_STRAND}}v.fillRect(e,d,h-e,10);v.fillStyle=this.base_color}for(var x=0,F=E.length;x<F;x++){var m=E[x],l=Math.floor(Math.max(0,(m[0]-C)*n)),D=Math.ceil(Math.min(u,(m[1]-C)*n));a=5;G=3;v.fillRect(l,d+G,D-l,a);if(b&&(l<o||D>b)){a=9;G=1;var r=Math.max(l,b),p=Math.min(D,o);v.fillRect(r,d+G,p-r,a)}}}else{a=9;G=1;v.fillRect(e,d+G,h-e,a);if(f.strand){if(f.strand=="+"){v.fillStyle=RIGHT_STRAND_INV}els e{if(f.strand=="-"){v.fillStyle=LEFT_STRAND_INV}}v.fillRect(e,d,h-e,10);v.fillStyle=this.base_color}}}y++}}g.append(s);return s}}); \ No newline at end of file diff -r 28ec4708840f -r 979534de254d static/scripts/trackster.js --- a/static/scripts/trackster.js Thu Oct 29 17:57:30 2009 -0400 +++ b/static/scripts/trackster.js Thu Oct 29 18:28:11 2009 -0400 @@ -7,7 +7,7 @@ DATA_NONE = "No data for this chrom/contig.", DATA_PENDING = "Currently indexing... please wait", DATA_LOADING = "Loading data...", - CACHED_TILES = 50, + CACHED_TILES = 5, CACHED_DATA = 20, CONTEXT = $("<canvas></canvas>").get(0).getContext("2d"), RIGHT_STRAND, LEFT_STRAND; @@ -42,6 +42,36 @@ } return number; } + +var Cache = function( num_elements ) { + this.num_elements = num_elements; + this.obj_cache = {}; + this.key_ary = []; +} +$.extend( Cache.prototype, { + get: function( key ) { + var index = this.key_ary.indexOf(key); + if (index != -1) { + // Move to the end + this.key_ary.splice(index, 1); + this.key_ary.push(key); + } + return this.obj_cache[key]; + }, + set: function( key, value ) { + if (!this.obj_cache[key]) { + if (this.key_ary.length >= this.num_elements) { + // Remove first element + var deleted_key = this.key_ary.shift(); + delete this.obj_cache[deleted_key]; + } + this.key_ary.push(key); + } + this.obj_cache[key] = value; + return value; + } +}); + var View = function( chrom, max_high ) { this.chrom = chrom; this.tracks = []; @@ -138,16 +168,14 @@ range = high - low; var resolution = Math.pow( 10, Math.ceil( Math.log( range / DENSITY ) / Math.log( 10 ) ) ); - resolution = Math.max( resolution, 1 ); - resolution = Math.min( resolution, 100000 ); + resolution = Math.max( resolution, 0.1 ); + resolution = Math.min( resolution, 1000000 ); 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; + var w_scale = this.content_div.width() / range; var tile_element; // Index of first tile that overlaps visible region @@ -155,7 +183,7 @@ while ( ( tile_index * DENSITY * resolution ) < high ) { // Check in cache var key = this.view.zoom_level + "_" + tile_index; - var cached = this.tile_cache.getItem(key); + var cached = this.tile_cache.get(key); if ( cached ) { // console.log("cached tile"); var tile_low = tile_index * DENSITY * resolution; @@ -165,10 +193,10 @@ // Our responsibility to move the element to the new parent parent_element.append( cached ); } else { - tile_element = this.draw_tile( resolution, tile_index, parent_element, w_scale, h ); + tile_element = this.draw_tile( resolution, tile_index, parent_element, w_scale ); } if ( tile_element ) { - this.tile_cache.setItem(key, tile_element); + this.tile_cache.set(key, tile_element); } tile_index += 1; } diff -r 28ec4708840f -r 979534de254d templates/tracks/browser.mako --- a/templates/tracks/browser.mako Thu Oct 29 17:57:30 2009 -0400 +++ b/templates/tracks/browser.mako Thu Oct 29 18:28:11 2009 -0400 @@ -7,7 +7,7 @@ <%def name="javascripts()"> ${parent.javascripts()} -${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "lrucache", "trackster" )} +${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "trackster" )} <script type="text/javascript">