details:
http://www.bx.psu.edu/hg/galaxy/rev/979534de254d
changeset: 2934:979534de254d
user: Kanwei Li <kanwei(a)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">