diff --git a/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py b/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py index 1c1426aab80..d36520e1d2b 100644 --- a/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py +++ b/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py @@ -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() diff --git a/lib/galaxy/visualization/tracks/data/array_tree.py b/lib/galaxy/visualization/tracks/data/array_tree.py index 54ea7e3c0fd..9a2ee8f76bc 100644 --- a/lib/galaxy/visualization/tracks/data/array_tree.py +++ b/lib/galaxy/visualization/tracks/data/array_tree.py @@ -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 \ No newline at end of file + v = chrom_array_tree.get_leaf( block_start ) + if v is not None: + results.extend( zip( indexes, map( float, v ) ) ) + + f.close() + return results diff --git a/lib/galaxy/visualization/tracks/data/interval_index.py b/lib/galaxy/visualization/tracks/data/interval_index.py index b6394c6d605..4c5284a468c 100644 --- a/lib/galaxy/visualization/tracks/data/interval_index.py +++ b/lib/galaxy/visualization/tracks/data/interval_index.py @@ -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 diff --git a/lib/galaxy/web/controllers/tracks.py b/lib/galaxy/web/controllers/tracks.py index 4a1be0845a9..1a92bdf761f 100644 --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -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 ): """ diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 7825f32c220..61e7c7115fa 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -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 = $("").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 = $("
"); 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) { diff --git a/static/trackster.css b/static/trackster.css index e7d2e095f1c..887232627a0 100644 --- a/static/trackster.css +++ b/static/trackster.css @@ -25,6 +25,7 @@ input { font-size: 100%; } +/*canvas{ border-right: 1px solid red; } /* debugging */ #nav { padding: 0.5em 0; background:#cccccc; diff --git a/templates/tracks/browser.mako b/templates/tracks/browser.mako index 6718d72379f..b01020a8719 100644 --- a/templates/tracks/browser.mako +++ b/templates/tracks/browser.mako @@ -122,6 +122,7 @@ ${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "trackster" )} + - +