details: http://www.bx.psu.edu/hg/galaxy/rev/b45a5a51c7d1 changeset: 2814:b45a5a51c7d1 user: Kanwei Li <kanwei@gmail.com> date: Thu Oct 01 14:01:53 2009 -0400 description: trackster: feature tracks auto-resize to fit data, prepare for LRU cache 5 file(s) affected in this change: static/scripts/lrucache.js static/scripts/packed/lrucache.js static/scripts/packed/trackster.js static/scripts/trackster.js static/trackster.css diffs (407 lines): diff -r 695f28311b36 -r b45a5a51c7d1 static/scripts/lrucache.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/lrucache.js Thu Oct 01 14:01:53 2009 -0400 @@ -0,0 +1,238 @@ +/* +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 695f28311b36 -r b45a5a51c7d1 static/scripts/packed/lrucache.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/scripts/packed/lrucache.js Thu Oct 01 14:01:53 2009 -0400 @@ -0,0 +1,1 @@ +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 695f28311b36 -r b45a5a51c7d1 static/scripts/packed/trackster.js --- a/static/scripts/packed/trackster.js Thu Oct 01 13:16:43 2009 -0400 +++ b/static/scripts/packed/trackster.js Thu Oct 01 14:01:53 2009 -0400 @@ -1,1 +1,1 @@ -var DENSITY=1000;var DataCache=function(b,a){this.type=b;this.track=a;this.cache=Object()};$.extend(DataCache.prototype,{get:function(d,b){var c=this.cache;if(!(c[d]&&c[d][b])){if(!c[d]){c[d]=Object()}var a=b*DENSITY*d;var e=(b+1)*DENSITY*d;c[d][b]={state:"loading"};$.getJSON(data_url,{track_type:this.track.track_type,chrom:this.track.view.chrom,low:a,high:e,dataset_id:this.track.dataset_id},function(f){if(f=="pending"){setTimeout(fetcher,5000)}else{c[d][b]={state:"loaded",values:f}}$(document).trigger("redraw")})}return c[d][b]}});var View=function(a,b){this.chrom=a;this.tracks=[];this.max_low=0;this.max_high=b;this.low=this.max_low;this.high=this.max_high;this.length=this.max_high-this.max_low};$.extend(View.prototype,{add_track:function(a){a.view=this;this.tracks.push(a);if(a.init){a.init()}},redraw:function(){$("#overview-box").css({left:(this.low/this.length)*$("#overview-viewport").width(),width:Math.max(4,((this.high-this.low)/this.length)*$("#overview-viewport").widt h())}).show();$("#low").text(this.low);$("#high").text(this.high);for(var a in this.tracks){this.tracks[a].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},move:function(b,a){this.low=Math.max(this.max_low,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(d,b){if(this.max_high==0){return}var c=this.high-this.low;var e=c/d/2;if(b==undefined){var a=(this.low+this.high)/2}else{var a=this.low+c*b/$(document).width()}this.low=Math.floor(a-e);this.high=Math.ceil(a+e);if(this.low<this.max_low){this.low=this.max_low;this.high=c/d}else{if(this.high>this.max_high){this.high=this.max_high;this.low=this.max_high-c/d}}if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){if(this.max_high==0){return}var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b*c/2;this.low=Math.floor(Math.max(0,a-d));this.high=Math.ceil(Math.min(this.length,a+d))},left:function(b){var a=this.high- this.low;var c=Math.floor(a/b);if(this.low-c<0){this.low=0;this.high=this.low+a}else{this.low-=c;this.high-=c}},right:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.high+c>this.length){this.high=this.length;this.low=this.high-a}else{this.low+=c;this.high+=c}}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(){this.last_resolution=null;this.last_w_scale=null;this.tile_cache={}};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var i=this.view.low,c=this.view.high,e=c-i;var b=Math.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));b=Math.max(b,1);b=Math.min(b,100000);var n=$("< div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(n);var l=this.content_div.width(),d=this.content_div.height(),o=l/e,k={},m={};if(this.last_resolution==b&&this.last_w_scale==o){k=this.tile_cache}var g;var a=Math.floor(i/b/DENSITY);while((a*1000*b)<c){if(a in k){g=k[a];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*o});n.append(g)}else{g=this.draw_tile(b,a,n,o,d)}if(g){m[a]=g}a+=1}this.last_resolution=b;this.last_w_scale=o;this.tile_cache=m}});var LabelTrack=function(a){Track.call(this,null,a);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.content_div.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+a+"</div>").css({position:"absolute",left:f-1 }));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var LineTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.cache=new DataCache("",this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this;$.getJSON(data_url,{stats:true,track_type:a.track_type,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(b){if(b){if(b=="error"){a.content_div.addClass("error").text("There was an error in indexing this dataset.")}else{if(b=="no data"){a.content_div.addClass("nodata").text("No data for this chrom/contig.")}else{a.min_value=b.min;a.max_value=b.max;a.vertical_range=a.max_value-a.min_value;a.view.redraw()}}}})},draw_tile:function(d,a,o,s,p){if(!this.vertical_range){return}var k=a*DENSITY*d,r=(a+1)*DENSITY*d,c=DENSITY*d;var n=this.cache.get(d,a) ;var h;if(n.state=="loading"){h=$("<div class='loading tile'></div>")}else{h=$("<canvas class='tile'></canvas>")}h.css({position:"absolute",top:0,left:(k-this.view.low)*s,});o.append(h);if(n.state=="loading"){e=false;return null}var b=h;b.get(0).width=Math.ceil(c*s);b.get(0).height=this.height_px;var q=b.get(0).getContext("2d");var e=false;q.beginPath();var g=n.values;if(!g){return}for(var f=0;f<g.length-1;f++){var m=g[f][0]-k;var l=g[f][1];if(isNaN(l)){e=false}else{m=m*s;y_above_min=l-this.min_value;l=y_above_min/this.vertical_range*this.height_px;if(e){q.lineTo(m,l)}else{q.moveTo(m,l);e=true}}}q.stroke();return h}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="feature";this.height_px=(a?a:100);this.container_div.addClass("feature-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.01;this.showing_labels=false};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{calc_ slots:function(e){var a=new Array();var d=this.container_div.width()/(this.view.high-this.view.low);if(e){this.zi_slots=new Object()}var c=$("<canvas></canvas>").get(0).getContext("2d");for(var b in this.values){feature=this.values[b];f_start=Math.floor(Math.max(this.view.max_low,(feature.start-this.view.max_low)*d));if(e){f_start-=c.measureText(feature.name).width}f_end=Math.ceil(Math.min(this.view.max_high,(feature.end-this.view.max_low)*d));j=0;while(true){if(a[j]==undefined||a[j]<f_start){a[j]=f_end;if(e){this.zi_slots[feature.name]=j}else{this.zo_slots[feature.name]=j}break}j++}}},init:function(){var a=this;$.getJSON(data_url,{track_type:a.track_type,low:a.view.max_low,high:a.view.max_high,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){a.values=b;a.calc_slots();a.slots=a.zo_slots;a.draw()})},draw_tile:function(q,t,e,g,f){if(!this.values){return null}if(g>this.show_labels_scale&&!this.showing_labels){this.showing_labels=true;if(!this.zi_slots){this.calc_slots(tr ue)}this.slots=this.zi_slots}else{if(g<=this.show_labels_scale&&this.showing_labels){this.showing_labels=false;this.slots=this.zo_slots}}var u=t*DENSITY*q,c=(t+1)*DENSITY*q,b=DENSITY*q;var k=this.view,m=k.high-k.low,o=Math.ceil(b*g),h=new Array(),n=200,l=$("<canvas class='tile'></canvas>");l.css({position:"absolute",top:0,left:(u-this.view.low)*g,});l.get(0).width=o;l.get(0).height=n;var p=l.get(0).getContext("2d");var r=0;for(var s in this.values){feature=this.values[s];if(feature.start<=c&&feature.end>=u){f_start=Math.floor(Math.max(0,(feature.start-u)*g));f_end=Math.ceil(Math.min(o,(feature.end-u)*g));p.fillStyle="#000";p.fillRect(f_start,this.slots[feature.name]*10+5,f_end-f_start,1);if(this.showing_labels&&p.fillText){p.font="10px monospace";p.textAlign="right";p.fillText(feature.name,f_start,this.slots[feature.name]*10+8)}if(feature.exon_start&&feature.exon_end){var d=Math.floor(Math.max(0,(feature.exon_start-u)*g));var w=Math.ceil(Math.min(o,(feature.exon_end-u)*g))}f or(var s in feature.blocks){block=feature.blocks[s];block_start=Math.floor(Math.max(0,(block[0]-u)*g));block_end=Math.ceil(Math.min(o,(block[1]-u)*g));var a=3,v=4;if(d&&block_start>=d&&block_end<=w){a=5,v=3}p.fillRect(d,this.slots[feature.name]*10+v,block_end-block_start,a)}r++}}e.append(l);return l},}); \ No newline at end of file +var DENSITY=1000,DATA_ERROR="There was an error in indexing this dataset.",DATA_NONE="No data for this chrom/contig.";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)*$("#overvie w-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 a in this.tracks){this.tracks[a].draw()}$("#bottom-spacer").remove();$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>')},move:function(b,a){this.low=Math.max(this.max_low,Math.floor(b));this.high=Math.min(this.length,Math.ceil(a))},zoom_in:function(d,b){if(this.max_high==0){return}var c=this.high-this.low;var e=c/d/2;if(b==undefined){var a=(this.low+this.high)/2}else{var a=this.low+c*b/$(document).width()}this.low=Math.floor(a-e);this.high=Math.ceil(a+e);if(this.low<this.max_low){this.low=this.max_low;this.high=c/d}else{if(this.high>this.max_high){this.high=this.max_high;this.low=this.max_high-c/d}}if(this.high-this.low<1){this.high=this.low+1}},zoom_out:function(c){if(this.max_high==0){return}var a=(this.low+this.high)/2;var b=this.high-this.low;var d=b*c/2;this.low=Math.flo or(Math.max(0,a-d));this.high=Math.ceil(Math.min(this.length,a+d))},left:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.low-c<0){this.low=0;this.high=this.low+a}else{this.low-=c;this.high-=c}},right:function(b){var a=this.high-this.low;var c=Math.floor(a/b);if(this.high+c>this.length){this.high=this.length;this.low=this.high-a}else{this.low+=c;this.high+=c}}});var Track=function(a,b){this.name=a;this.parent_element=b;this.make_container()};$.extend(Track.prototype,{make_container:function(){this.header_div=$("<div class='track-header'>").text(this.name);this.content_div=$("<div class='track-content'>");this.container_div=$("<div class='track'></div>").append(this.header_div).append(this.content_div);this.parent_element.append(this.container_div)}});var TiledTrack=function(){this.last_resolution=null;this.last_w_scale=null;this.tile_cache={}};$.extend(TiledTrack.prototype,Track.prototype,{draw:function(){var i=this.view.low,c=this.view.high,e=c-i;var b=Mat h.pow(10,Math.ceil(Math.log(e/DENSITY)/Math.log(10)));b=Math.max(b,1);b=Math.min(b,100000);var m=$("<div style='position: relative;'></div>");this.content_div.children(":first").remove();this.content_div.append(m);var k=this.content_div.width(),d=this.content_div.height(),n=k/e,j={},l={};if(this.last_resolution==b&&this.last_w_scale==n){j=this.tile_cache}var g;var a=Math.floor(i/b/DENSITY);while((a*1000*b)<c){if(a in j){g=j[a];var f=a*DENSITY*b;g.css({left:(f-this.view.low)*n});m.append(g)}else{g=this.draw_tile(b,a,m,n,d)}if(g){l[a]=g}a+=1}this.last_resolution=b;this.last_w_scale=n;this.tile_cache=l}});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.hig h){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+a+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var LineTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="line";this.height_px=(a?a:100);this.container_div.addClass("line-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.cache=new DataCache("",this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{init:function(){var a=this;$.getJSON(data_url,{stats:true,track_type:a.track_type,chrom:a.view.chrom,low:null,high:null,dataset_id:a.dataset_id},function(b){if(!b||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.min_value=b.min;a.max_value=b.max;a.vertical_range=a.max_value-a.min_value;a.view.redraw()}}})},draw_tile:function(d,a,n,r,o){if(!this.vertical_range){return}var j=a*DENSITY*d,q=(a+1)*DENSITY* d,c=DENSITY*d;var m=this.cache.get(d,a);var h;if(m.state=="loading"){h=$("<div class='loading tile'></div>")}else{h=$("<canvas class='tile'></canvas>")}h.css({position:"absolute",top:0,left:(j-this.view.low)*r,});n.append(h);if(m.state=="loading"){e=false;return null}var b=h;b.get(0).width=Math.ceil(c*r);b.get(0).height=this.height_px;var p=b.get(0).getContext("2d");var e=false;p.beginPath();var g=m.values;if(!g){return}for(var f=0;f<g.length-1;f++){var l=g[f][0]-j;var k=g[f][1];if(isNaN(k)){e=false}else{l=l*r;y_above_min=k-this.min_value;k=y_above_min/this.vertical_range*this.height_px;if(e){p.lineTo(l,k)}else{p.moveTo(l,k);e=true}}}p.stroke();return h}});var FeatureTrack=function(c,b,a){Track.call(this,c,$("#viewport"));this.track_type="feature";this.height_px=(a?a:100);this.container_div.addClass("feature-track");this.content_div.css("height",this.height_px+"px");this.dataset_id=b;this.zo_slots={};this.show_labels_scale=0.01;this.showing_labels=false;this.vertical_gap=10} ;$.extend(FeatureTrack.prototype,TiledTrack.prototype,{calc_slots:function(h){var b=[];var a=this.container_div.width()/(this.view.high-this.view.low);if(h){this.zi_slots={}}var g=$("<canvas></canvas>").get(0).getContext("2d");for(var d in this.values){var k=this.values[d];var e=Math.floor(Math.max(this.view.max_low,(k.start-this.view.max_low)*a));if(h){e-=g.measureText(k.name).width;e-=10}var f=Math.ceil(Math.min(this.view.max_high,(k.end-this.view.max_low)*a));var c=0;while(true){if(b[c]==undefined||b[c]<e){b[c]=f;if(h){this.zi_slots[k.name]=c}else{this.zo_slots[k.name]=c}break}c++}}this.height_px=b.length*this.vertical_gap+15;this.content_div.css("height",this.height_px+"px")},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").t ext(DATA_NONE)}else{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=false;this.slots=this.zo_slots}}var u=t*DENSITY*q,c=(t+1)*DENSITY*q,b=DENSITY*q;var k=this.view,m=k.high-k.low,o=Math.ceil(b*g),h=new Array(),n=this.height_px,l=$("<canvas class='tile'></canvas>");l.css({position:"absolute",top:0,left:(u-this.view.low)*g,});l.get(0).width=o;l.get(0).height=n;var p=l.get(0).getContext("2d");var r=0;for(var s in this.values){feature=this.values[s];if(feature.start<=c&&feature.end>=u){f_start=Math.floor(Math.max(0,(feature.start-u)*g));f_end=Math.ceil(Math.min(o,(feature.end-u)*g));p.fillStyle="#000";p.fillRect(f_start,this.slots[feature.name]*this.vertical_gap+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(Math.max(0,(block[0]-u)*g));block_end=Math.ceil(Math.min(o,(block[1]-u)*g));var a=3,v=4;if(d&&block_start>=d&&block_end<=w){a=5,v=3}p.fillRect(d,this.slots[feature.name]*this.vertical_gap+v,block_end-block_start,a)}r++}}e.append(l);return l},}); \ No newline at end of file diff -r 695f28311b36 -r b45a5a51c7d1 static/scripts/trackster.js --- a/static/scripts/trackster.js Thu Oct 01 13:16:43 2009 -0400 +++ b/static/scripts/trackster.js Thu Oct 01 14:01:53 2009 -0400 @@ -2,7 +2,9 @@ 2009, James Taylor, Kanwei Li */ -var DENSITY = 1000; +var DENSITY = 1000, + DATA_ERROR = "There was an error in indexing this dataset.", + DATA_NONE = "No data for this chrom/contig."; var DataCache = function( type, track ) { this.type = type; @@ -239,18 +241,16 @@ var track = this; $.getJSON( data_url, { stats: true, track_type: track.track_type, chrom: track.view.chrom, low: null, high: null, dataset_id: track.dataset_id }, function ( data ) { - if (data) { - if (data == "error") { - track.content_div.addClass("error").text("There was an error in indexing this dataset."); - } else if (data == "no data") { - // console.log(track.content_div); - track.content_div.addClass("nodata").text("No data for this chrom/contig."); - } else { - track.min_value = data['min']; - track.max_value = data['max']; - track.vertical_range = track.max_value - track.min_value; - track.view.redraw(); - } + if (!data || data == "error") { + track.content_div.addClass("error").text(DATA_ERROR); + } else if (data == "no data") { + // console.log(track.content_div); + track.content_div.addClass("nodata").text(DATA_NONE); + } else { + track.min_value = data['min']; + track.max_value = data['max']; + track.vertical_range = track.max_value - track.min_value; + track.view.redraw(); } }); }, @@ -321,27 +321,28 @@ this.zo_slots = {}; this.show_labels_scale = 0.01; this.showing_labels = false; + this.vertical_gap = 10; }; $.extend( FeatureTrack.prototype, TiledTrack.prototype, { - calc_slots: function( include_labels ) { // console.log("num vals: " + this.values.length); - var end_ary = new Array(); + var end_ary = []; var scale = this.container_div.width() / (this.view.high - this.view.low); - // console.log(scale); - if (include_labels) this.zi_slots = new Object(); + // console.log(scale, this.view.high, this.view.low); + if (include_labels) this.zi_slots = {}; 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) ); + var feature = this.values[i]; + var 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_start -= 10; // Spacing between text and line } - f_end = Math.ceil( Math.min(this.view.max_high, (feature.end - this.view.max_low) * scale) ); + var 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; + var j = 0; while (true) { if (end_ary[j] == undefined || end_ary[j] < f_start) { end_ary[j] = f_end; @@ -355,17 +356,26 @@ j++; } } + this.height_px = end_ary.length * this.vertical_gap + 15; + this.content_div.css( "height", this.height_px + "px" ); }, init: function() { var track = this; $.getJSON( data_url, { track_type: track.track_type, low: track.view.max_low, high: track.view.max_high, dataset_id: track.dataset_id, chrom: track.view.chrom }, function ( data ) { - track.values = data; - track.calc_slots(); - track.slots = track.zo_slots; - // console.log(track.zo_slots); - track.draw(); + if (data == "error") { + track.content_div.addClass("error").text(DATA_ERROR); + } else if (data.length == 0 || data == "no data") { + // console.log(track.content_div); + track.content_div.addClass("nodata").text(DATA_NONE); + } else { + track.values = data; + track.calc_slots(); + track.slots = track.zo_slots; + // console.log(track.zo_slots); + track.draw(); + } }); }, @@ -391,7 +401,7 @@ range = view.high - view.low, width = Math.ceil( tile_length * w_scale ), slots = new Array(), - height = 200, + height = this.height_px, new_canvas = $("<canvas class='tile'></canvas>"); new_canvas.css({ @@ -412,7 +422,7 @@ 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); + ctx.fillRect(f_start, this.slots[feature.name] * this.vertical_gap + 5, f_end - f_start, 1); if (this.showing_labels && ctx.fillText) { ctx.font = "10px monospace"; @@ -435,7 +445,7 @@ 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); + ctx.fillRect(exon_start, this.slots[feature.name] * this.vertical_gap + y_start, block_end - block_start, thickness); // console.log(block_start, block_end); } diff -r 695f28311b36 -r b45a5a51c7d1 static/trackster.css --- a/static/trackster.css Thu Oct 01 13:16:43 2009 -0400 +++ b/static/trackster.css Thu Oct 01 14:01:53 2009 -0400 @@ -86,7 +86,7 @@ .track-content.error { text-align: center; padding-top: 30px; - background-color: #600; + background-color: #ECB4AF; } .track-content.nodata { text-align: center;