trackster: caching goes by zoom levels, greatly improving zooming performance. revamped UI

This commit is contained in:
Kanwei Li
2009-10-20 16:46:12 -04:00
parent 1e69d3b5ad
commit d684b0d656
4 changed files with 110 additions and 129 deletions
File diff suppressed because one or more lines are too long
+53 -71
View File
@@ -5,17 +5,26 @@
var DENSITY = 1000,
DATA_ERROR = "There was an error in indexing this dataset.",
DATA_NONE = "No data for this chrom/contig.",
CACHED_TILES = 200,
CACHED_TILES = 50,
CACHED_DATA = 20;
var View = function( chrom, max_length ) {
function commatize( number ) {
number += ''; // Convert to string
var rgx = /(\d+)(\d{3})/;
while (rgx.test(number)) {
number = number.replace(rgx, '$1' + ',' + '$2');
}
return number;
}
var View = function( chrom, max_high ) {
this.chrom = chrom;
this.tracks = [];
this.max_low = 0;
this.max_high = max_length;
this.low = this.max_low;
this.high = this.max_high;
this.length = this.max_high - this.max_low;
this.max_high = max_high;
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 ( track ) {
@@ -24,82 +33,55 @@ $.extend( View.prototype, {
if (track.init) { track.init(); }
},
redraw: function () {
var span = this.span / Math.pow(this.zoom_factor, this.zoom_level),
low = this.center - (span / 2),
high = low + span;
if (low < 0) {
low = 0;
high = low + span;
} else if (high > this.max_high) {
high = this.max_high;
low = high - span;
}
this.low = Math.floor(low);
this.high = Math.ceil(high);
this.center = Math.round( this.low + (this.high - this.low) / 2 );
// Overview
$("#overview-box").css( {
left: ( this.low / this.length ) * $("#overview-viewport").width(),
width: Math.max( 4, ( ( this.high - this.low ) / this.length ) * $("#overview-viewport").width() )
left: ( this.low / this.span ) * $("#overview-viewport").width(),
// Minimum width for usability
width: Math.max( 12, ( ( this.high - this.low ) / this.span ) * $("#overview-viewport").width() )
}).show();
$("#low").text( this.low );
$("#high").text( this.high );
$("#low").text( commatize(this.low) );
$("#high").text( commatize(this.high) );
for ( var i = 0, len = this.tracks.length; i < len; i++ ) {
this.tracks[i].draw();
}
$("#bottom-spacer").remove();
$("#viewport").append('<div id="bottom-spacer" style="height: 200px;"></div>');
},
move: function ( new_low, new_high ) {
this.low = Math.max( this.max_low, Math.floor( new_low ) );
this.high = Math.min( this.length, Math.ceil( new_high ) );
},
zoom_in: function ( factor, point ) {
if (this.max_high === 0) {
zoom_in: function ( point ) {
if (this.max_high === 0 || this.high - this.low < 5) {
return;
}
var range = this.high - this.low;
var diff = range / factor / 2;
var center;
if (point === undefined) {
center = ( this.low + this.high ) / 2;
} else {
center = this.low + range * point / $(document).width();
}
this.low = Math.floor( center - diff );
this.high = Math.ceil( center + diff );
if (this.low < this.max_low) {
this.low = this.max_low;
this.high = range / factor;
} else if (this.high > this.max_high) {
this.high = this.max_high;
this.low = this.max_high - range / factor;
// console.log(this.high, this.low);
}
if (this.high - this.low < 1 ) {
this.high = this.low + 1;
if ( point ) {
this.center = point / $(document).width() * (this.high - this.low) + this.low;
}
this.zoom_level += 1;
},
zoom_out: function ( factor ) {
zoom_out: function () {
if (this.max_high === 0) {
return;
}
var center = ( this.low + this.high ) / 2;
var range = this.high - this.low;
var diff = range * factor / 2;
this.low = Math.floor( Math.max( 0, center - diff ) );
this.high = Math.ceil( Math.min( this.length, center + diff ) );
},
left: function( factor ) {
var range = this.high - this.low;
var diff = Math.floor( range / factor );
if ( this.low - diff < 0 ) {
this.low = 0;
this.high = this.low + range;
} else {
this.low -= diff;
this.high -= diff;
}
},
right: function ( factor ) {
var range = this.high - this.low;
var diff = Math.floor( range / factor );
if ( this.high + diff > this.length ) {
this.high = this.length;
this.low = this.high - range;
} else {
this.low += diff;
this.high += diff;
if (this.zoom_level <= 0) {
this.zoom_level = 0;
return;
}
this.zoom_level -= 1;
}
});
@@ -143,7 +125,7 @@ $.extend( TiledTrack.prototype, Track.prototype, {
var tile_index = Math.floor( low / resolution / DENSITY );
while ( ( tile_index * DENSITY * resolution ) < high ) {
// Check in cache
var key = w_scale + "_" + tile_index;
var key = this.view.zoom_level + "_" + tile_index;
if ( this.tile_cache[key] ) {
// console.log("cached tile");
tile_element = this.tile_cache[key];
@@ -178,7 +160,7 @@ $.extend( LabelTrack.prototype, Track.prototype, {
new_div = $("<div style='position: relative; height: 1.3em;'></div>");
while ( position < view.high ) {
var screenPosition = ( position - view.low ) / range * width;
new_div.append( $("<div class='label'>" + position + "</div>").css( {
new_div.append( $("<div class='label'>" + commatize( position ) + "</div>").css( {
position: "absolute",
// Reduce by one to account for border
left: screenPosition - 1
@@ -231,7 +213,7 @@ $.extend( LineTrack.prototype, TiledTrack.prototype, {
$(document).trigger( "redraw" );
});
},
draw_tile: function( resolution, tile_index, parent_element, w_scale, h_scale ) {
draw_tile: function( resolution, tile_index, parent_element, w_scale ) {
if (!this.vertical_range) { // We don't have the necessary information yet
return;
}
@@ -354,11 +336,11 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
this.height_px = end_ary.length * this.vertical_gap + 15;
this.content_div.css( "height", this.height_px + "px" );
},
draw_tile: function( resolution, tile_index, parent_element, w_scale, h_scale ) {
draw_tile: function( resolution, tile_index, parent_element, w_scale ) {
if (!this.values) { // Still loading
return null;
}
// Once we zoom in enough, show name labels
// Once we zoom in enough, show name labels
if (w_scale > this.show_labels_scale && !this.showing_labels) {
this.showing_labels = true;
if (!this.zi_slots) {
@@ -373,9 +355,9 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
var tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_length = DENSITY * resolution;
tile_span = DENSITY * resolution;
// console.log(tile_low, tile_high, tile_length, w_scale);
var width = Math.ceil( tile_length * w_scale ),
var width = Math.ceil( tile_span * w_scale ),
height = this.height_px,
new_canvas = $("<canvas class='tile'></canvas>");
+26 -10
View File
@@ -2,16 +2,17 @@ body {
margin: 0 0;
padding: 0;
font-family: verdana;
font-size: 75%;
font-size: 12px;
overflow-y: hidden;
}
#content {
width: 100%;
height: 100%;
}
#center {
overflow: auto;
#center {
overflow: auto;
}
#nav {
@@ -36,26 +37,35 @@ body {
-webkit-border-radius: 1em;
-moz-border-radius: 1em;
}
#low {
margin-left: 0.1em;
float: left;
}
#high {
margin-right: 0.1em;
float: right;
}
#overview {
width: 100%;
padding: 10px 0 0 0;
margin: 0px;
background: #333;
color: white;
font-weight: bold;
}
#overview-viewport {
height: 10px;
border-top: solid #666 1px;
border-bottom: solid #666 1px;
height: 20px;
/* border-top: solid #666 1px;*/
/* border-bottom: solid #aaa 1px;*/
background: #888;
}
#overview-box {
position: absolute;
height: 10px;
background: #ddd;
margin-top: 1px;
height: 16px;
background: #ddd url(/static/images/draggable_horizontal.png) center center no-repeat;
border-style: outset;
border-width: 1px;
}
#viewport {
@@ -100,6 +110,12 @@ body {
min-height: 100px;
}
.label-track {
font-weight: bold;
font-size: 10px;
background-color: #333;
color: white;
}
.label-track .label {
border-left: solid gray 1px;
padding-left: 2px;
+30 -47
View File
@@ -16,7 +16,7 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "lrucache", "trackst
$(function() {
view.add_track( new LabelTrack( $("#overview" ) ) );
view.add_track( new LabelTrack( $("#viewport" ) ) );
view.add_track( new LabelTrack( $("#nav-labeltrack" ) ) );
%for track in tracks:
@@ -27,41 +27,35 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "lrucache", "trackst
view.redraw();
});
$(document).bind("mousewheel", function(e, delta) {
$(document).bind("mousewheel", function( e, delta ) {
if (delta > 0) {
view.zoom_in(2, e.pageX);
view.zoom_in(e.pageX);
view.redraw();
} else {
view.zoom_out(2);
view.zoom_out();
view.redraw();
}
});
$(document).bind("dblclick", function(e) {
view.zoom_in(2, e.pageX);
$(document).bind("dblclick", function( e ) {
view.zoom_in(e.pageX);
view.redraw();
});
$("#overview-box").bind("dragstart", function(e) {
// To let the overview box be draggable
$("#overview-box").bind("dragstart", function( e ) {
this.current_x = e.offsetX;
}).bind("drag", function(e) {
}).bind("drag", function( e ) {
var delta = e.offsetX - this.current_x;
this.current_x = e.offsetX;
var delta_chrom = Math.round(delta / $(document).width() * (view.max_high - view.max_low));
var view_range = view.high - view.low;
var new_low = view.low += delta_chrom;
var new_high = view.high += delta_chrom;
if (new_low < view.max_low) {
new_low = 0;
new_high = view_range;
} else if (new_high > view.max_high) {
new_high = view.max_high;
new_low = view.max_high - view_range;
var delta_chrom = Math.round(delta / $(document).width() * view.span);
view.center += delta_chrom;
if (view.center < 0) {
view.center = 0;
} else if (view.center > view.max_high) {
view.center = view.max_high;
}
view.low = new_low;
view.high = new_high;
view.redraw();
});
@@ -70,11 +64,12 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "lrucache", "trackst
view.redraw();
});
$("#viewport").bind( "dragstart", function ( e ) {
$("#viewport").bind( "dragstart", function( e ) {
this.original_low = view.low;
this.current_height = e.clientY;
this.current_x = e.offsetX;
}).bind( "drag", function( e ) {
var move_amount = ( e.offsetX - this.offsetLeft ) / this.offsetWidth;
var delta = e.offsetX - this.current_x;
var new_scroll = $(this).scrollTop() - (e.clientY - this.current_height);
if ( new_scroll < $(this).get(0).scrollHeight - $(this).height() - 200) {
@@ -82,19 +77,10 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "lrucache", "trackst
}
this.current_height = e.clientY;
var range = view.high - view.low;
var move_bases = Math.round( range * move_amount );
var new_low = this.original_low - move_bases;
if ( new_low < 0 ) {
new_low = 0;
}
var new_high = new_low + range;
if ( new_high > view.length ) {
new_high = view.length;
new_low = new_high - range;
}
view.low = new_low;
view.high = new_high;
this.current_x = e.offsetX;
var delta_chrom = Math.round(delta / $(document).width() * (view.max_high - view.max_low));
view.center += delta_chrom;
view.redraw();
});
(function () {
@@ -122,28 +108,25 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "lrucache", "trackst
</%def>
<div id="content">
<div id="viewport"></div>
</div>
<div id="nav">
<div id="nav-labeltrack"></div>
<div id="overview">
<div id="overview-viewport">
<div id="overview-box"></div>
</div>
</div>
<div id="viewport"></div>
</div>
<div id="nav">
<div id="nav-labeltrack"></div>
<span id="low"></span>
<span id="high"></span>
<div id="nav-controls">
<form name="chr" id="chr" method="get">
<select id="chrom" name="chrom">
<option value="">Loading</option>
</select>
<input type="hidden" name="dataset_ids" value="${dataset_ids}" />
<a href="#" onclick="javascript:view.left(5);view.redraw();">&lt;&lt;</a>
<a href="#" onclick="javascript:view.left(2);view.redraw();">&lt;</a>
<span id="low">0</span>&mdash;<span id="high">${LEN}</span>
<a href="#" onclick="javascript:view.zoom_in(2);view.redraw();">+</a>
<a href="#" onclick="javascript:view.zoom_out(2);view.redraw();">-</a>
<a href="#" onclick="javascript:view.right(2);view.redraw();">&gt;</a>
<a href="#" onclick="javascript:view.right(5);view.redraw();">&gt;&gt;</a>
<a href="#" onclick="javascript:view.zoom_in();view.redraw();">+</a>
<a href="#" onclick="javascript:view.zoom_out();view.redraw();">-</a>
</form>
</div>
</div>