trackster: much greater resolution for line tracks (might still need to tweak BLOCK_SIZE), fixed stacking issue in feature tracks for features sharing same name)

This commit is contained in:
Kanwei Li
2009-11-03 19:16:01 -05:00
parent 9f8a8ddd45
commit f3ab760b63
7 changed files with 65 additions and 44 deletions
@@ -8,6 +8,8 @@ import pkg_resources; pkg_resources.require( "bx-python" )
from bx.arrays.array_tree import *
from bx.arrays.wiggle import IntervalReader
BLOCK_SIZE = 1000
def main():
input_fname = sys.argv[1]
@@ -16,7 +18,7 @@ def main():
reader = IntervalReader( open( input_fname ) )
# Fill array from wiggle
d = array_tree_dict_from_wiggle_reader( reader, {} )
d = array_tree_dict_from_wiggle_reader( reader, {}, block_size = BLOCK_SIZE )
for value in d.itervalues():
value.root.build_summary()
@@ -31,11 +31,16 @@ class ArrayTreeDataProvider( object ):
f.close()
return { 'max': float( max(root_summary.maxs) ), 'min': float( min(root_summary.mins) ) }
def get_data( self, chrom, start, end ):
def get_data( self, chrom, start, end, **kwargs ):
start = int( start )
end = int( end )
level = int( ceil( log( end - start, BLOCK_SIZE ) ) ) - 1
resolution = max(1, ceil(float(kwargs['resolution'])))
level = int( floor( log( resolution, BLOCK_SIZE ) ) )
level = max( level, 0 )
stepsize = BLOCK_SIZE ** level
step1 = stepsize * BLOCK_SIZE
# Open the file
f = open( self.dataset.file_name )
d = FileArrayTreeDict( f )
@@ -47,22 +52,20 @@ class ArrayTreeDataProvider( object ):
# Is the requested level valid?
assert 0 <= level <= chrom_array_tree.levels
# Calculate the actual start/range/step of the block we're getting
size = BLOCK_SIZE ** (level+1)
block_start = ( start // BLOCK_SIZE ) * BLOCK_SIZE
block_step = size // BLOCK_SIZE
indexes = range( block_start, block_start + size, block_step )
# Return either data point or a summary depending on the level
if level > 0:
s = chrom_array_tree.get_summary( start, level )
f.close()
if s is not None:
return zip( indexes, map( float, s.sums / s.counts ) )
results = []
for block_start in range( start, end, stepsize * BLOCK_SIZE ):
# print block_start
# Return either data point or a summary depending on the level
indexes = range( block_start, block_start + stepsize * BLOCK_SIZE, stepsize )
if level > 0:
s = chrom_array_tree.get_summary( block_start, level )
if s is not None:
results.extend( zip( indexes, map( float, s.sums / s.counts ) ) )
else:
return None
else:
v = chrom_array_tree.get_leaf( start )
f.close()
if v is not None:
return zip( indexes, map( float, v ) )
else:
return None
v = chrom_array_tree.get_leaf( block_start )
if v is not None:
results.extend( zip( indexes, map( float, v ) ) )
f.close()
return results
@@ -11,17 +11,18 @@ class IntervalIndexDataProvider( object ):
self.original_dataset = original_dataset
self.converted_dataset = converted_dataset
def get_data( self, chrom, start, end ):
def get_data( self, chrom, start, end, **kwargs ):
start, end = int(start), int(end)
chrom = str(chrom)
source = open( self.original_dataset.file_name )
index = Indexes( self.converted_dataset.file_name )
results = []
uid = 0
for start, end, offset in index.find(chrom, start, end):
source.seek(offset)
feature = source.readline().split()
payload = { 'start': start, 'end': end, 'name': feature[3] }
payload = { 'uid': uid, 'start': start, 'end': end, 'name': feature[3] }
try:
payload['strand'] = feature[5]
except IndexError:
@@ -41,5 +42,6 @@ class IntervalIndexDataProvider( object ):
pass
results.append(payload)
uid += 1
return results
+3 -3
View File
@@ -184,7 +184,7 @@ class TracksController( BaseController ):
return manifest
@web.json
def data( self, trans, dataset_id, track_type, chrom, low, high, stats=False ):
def data( self, trans, dataset_id, track_type, chrom, low, high, **kwargs ):
"""
Called by the browser to request a block of data
"""
@@ -216,10 +216,10 @@ class TracksController( BaseController ):
data_provider = dataset_type_to_data_provider[ converted_dataset_type ]( converted_dataset, dataset )
# Return stats if we need them
if stats: return data_provider.get_stats( chrom )
if 'stats' in kwargs: return data_provider.get_stats( chrom )
# Get the requested chunk of data
return data_provider.get_data( chrom, low, high )
return data_provider.get_data( chrom, low, high, **kwargs )
def __dataset_as_type( self, trans, dataset, type ):
"""
+29 -17
View File
@@ -1,13 +1,15 @@
/* Trackster
2009, James Taylor, Kanwei Li
*/
var DEBUG = false;
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 = 10,
CACHED_TILES_FEATURE = 10,
CACHED_TILES_LINE = 30,
CACHED_DATA = 20,
CONTEXT = $("<canvas></canvas>").get(0).getContext("2d"),
RIGHT_STRAND, LEFT_STRAND;
@@ -104,6 +106,9 @@ $.extend( View.prototype, {
this.high = Math.ceil(high);
this.center = Math.round( this.low + (this.high - this.low) / 2 );
// 10^log10(range / DENSITY) Close approximation for browser window, assuming DENSITY = window width
this.resolution = Math.pow( 10, Math.ceil( Math.log( (this.high - this.low) / DENSITY ) / Math.LN10 ) );
// Overview
$("#overview-box").css( {
left: ( this.low / this.span ) * $("#overview-viewport").width(),
@@ -157,18 +162,16 @@ $.extend( Track.prototype, {
});
var TiledTrack = function() {
this.tile_cache = new Cache(CACHED_TILES);
// this.tile_cache = {};
};
$.extend( TiledTrack.prototype, Track.prototype, {
draw: function() {
var low = this.view.low,
high = this.view.high,
range = high - low;
var resolution = Math.pow( 10, Math.ceil( Math.log( range / DENSITY ) / Math.log( 10 ) ) );
resolution = Math.max( resolution, 0.1 );
resolution = Math.min( resolution, 1000000 );
range = high - low,
resolution = this.view.resolution;
if (DEBUG) { $("#debug").text(resolution); }
var parent_element = $("<div style='position: relative;'></div>");
this.content_div.children( ":first" ).remove();
@@ -187,7 +190,7 @@ $.extend( TiledTrack.prototype, Track.prototype, {
// console.log("cached tile " + tile_index);
var tile_low = tile_index * DENSITY * resolution;
cached.css( {
left: ( tile_low - this.view.low ) * w_scale
left: ( tile_low - low ) * w_scale
});
// Our responsibility to move the element to the new parent
parent_element.append( cached );
@@ -229,6 +232,7 @@ $.extend( LabelTrack.prototype, Track.prototype, {
});
var LineTrack = function ( name, dataset_id, height ) {
this.tile_cache = new Cache(CACHED_TILES_LINE);
Track.call( this, name, $("#viewport") );
TiledTrack.call( this );
@@ -236,6 +240,7 @@ var LineTrack = function ( name, dataset_id, height ) {
this.height_px = (height ? height : 100);
this.container_div.addClass( "line-track" );
this.dataset_id = dataset_id;
this.data_queue = {};
this.cache = new Cache(CACHED_DATA); // We need to cache some data because of
// asynchronous calls
};
@@ -282,11 +287,17 @@ $.extend( LineTrack.prototype, TiledTrack.prototype, {
low = position * DENSITY * resolution,
high = ( position + 1 ) * DENSITY * resolution,
key = resolution + "_" + position;
$.getJSON( data_url, { track_type: this.track_type, chrom: this.view.chrom, low: low, high: high, dataset_id: this.dataset_id }, function ( data ) {
track.cache[key] = data;
$(document).trigger( "redraw" );
});
if (!track.data_queue[key]) {
track.data_queue[key] = true;
$.getJSON( data_url, { track_type: this.track_type, chrom: this.view.chrom,
low: low, high: high, dataset_id: this.dataset_id,
resolution: this.view.resolution }, function ( data ) {
track.cache[key] = data;
delete track.data_queue[key];
track.draw();
});
}
},
draw_tile: function( resolution, tile_index, parent_element, w_scale ) {
if (!this.vertical_range) { // We don't have the necessary information yet
@@ -340,6 +351,7 @@ $.extend( LineTrack.prototype, TiledTrack.prototype, {
});
var FeatureTrack = function ( name, dataset_id, height ) {
this.tile_cache = new Cache(CACHED_TILES_FEATURE);
Track.call( this, name, $("#viewport") );
TiledTrack.call( this );
@@ -409,9 +421,9 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
if (end_ary[j] === undefined || end_ary[j] < f_start) {
end_ary[j] = f_end;
if (include_labels) {
this.zi_slots[feature.name] = j;
this.zi_slots[feature.uid] = j;
} else {
this.zo_slots[feature.name] = j;
this.zo_slots[feature.uid] = j;
}
break;
}
@@ -466,7 +478,7 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
if (feature.start <= tile_high && feature.end >= tile_low) {
var f_start = Math.floor( Math.max(0, (feature.start - tile_low) * w_scale) ),
f_end = Math.ceil( Math.min(width, (feature.end - tile_low) * w_scale) ),
y_center = this.slots[feature.name] * this.vertical_gap;
y_center = this.slots[feature.uid] * this.vertical_gap;
var thickness, y_start, thick_start = null, thick_end = null;
if (feature.thick_start && feature.thick_end) {
+1
View File
@@ -25,6 +25,7 @@ input {
font-size: 100%;
}
/*canvas{ border-right: 1px solid red; } /* debugging */
#nav {
padding: 0.5em 0;
background:#cccccc;
+1
View File
@@ -122,6 +122,7 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "trackster" )}
<a href="#" onclick="javascript:view.zoom_in();view.redraw();">+</a>
<a href="#" onclick="javascript:view.zoom_out();view.redraw();">-</a>
</form>
<div id="debug" style="float: right"></div>
</div>
</div>
</div>