mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
trackster: fixed browser crashing (were drawing huge canvas tiles because of resolution restriction), implemented simple LRU
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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};
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@ var DENSITY = 1000,
|
||||
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 @@ function commatize( number ) {
|
||||
}
|
||||
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 @@ $.extend( TiledTrack.prototype, Track.prototype, {
|
||||
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 @@ $.extend( TiledTrack.prototype, Track.prototype, {
|
||||
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 @@ $.extend( TiledTrack.prototype, Track.prototype, {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ ${parent.stylesheets()}
|
||||
|
||||
<%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">
|
||||
|
||||
|
||||
Reference in New Issue
Block a user