details: http://www.bx.psu.edu/hg/galaxy/rev/137d93848139 changeset: 3541:137d93848139 user: Kanwei Li <kanwei@gmail.com> date: Tue Mar 16 18:54:23 2010 -0400 description: trackster: - Use new array_tree summary structure. Feature tracks now display as intensity graphs at higher levels, and switch to detail levels when they fit the screen well - Create array_tree indices for bed format to support the above (bam still in progress) - Other fixes and improvements, including new icons diffstat: datatypes_conf.xml.sample | 2 + lib/galaxy/datatypes/binary.py | 2 +- lib/galaxy/datatypes/converters/bam_to_array_tree_converter.py | 45 + lib/galaxy/datatypes/converters/bam_to_array_tree_converter.xml | 15 + lib/galaxy/datatypes/converters/bed_to_array_tree_converter.py | 29 + lib/galaxy/datatypes/converters/bed_to_array_tree_converter.xml | 14 + lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py | 12 +- lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.xml | 2 +- lib/galaxy/datatypes/interval.py | 4 +- lib/galaxy/visualization/tracks/data/array_tree.py | 85 ++- lib/galaxy/web/controllers/tracks.py | 73 +- static/scripts/trackster.js | 242 ++++++--- static/trackster.css | 8 +- templates/tracks/browser.mako | 27 +- 14 files changed, 401 insertions(+), 159 deletions(-) diffs (1036 lines): diff -r 861756e85b16 -r 137d93848139 datatypes_conf.xml.sample --- a/datatypes_conf.xml.sample Tue Mar 16 16:03:28 2010 -0400 +++ b/datatypes_conf.xml.sample Tue Mar 16 18:54:23 2010 -0400 @@ -5,12 +5,14 @@ <datatype extension="axt" type="galaxy.datatypes.sequence:Axt" display_in_upload="true"/> <datatype extension="bam" type="galaxy.datatypes.binary:Bam" mimetype="application/octet-stream" display_in_upload="true"> <converter file="bam_to_bai.xml" target_datatype="bai"/> + <converter file="bam_to_array_tree_converter.xml" target_datatype="array_tree"/> <display file="ucsc/bam.xml" /> </datatype> <datatype extension="bed" type="galaxy.datatypes.interval:Bed" display_in_upload="true"> <converter file="bed_to_gff_converter.xml" target_datatype="gff"/> <converter file="interval_to_coverage.xml" target_datatype="coverage"/> <converter file="bed_to_interval_index_converter.xml" target_datatype="interval_index"/> + <converter file="bed_to_array_tree_converter.xml" target_datatype="array_tree"/> <converter file="bed_to_genetrack_converter.xml" target_datatype="genetrack"/> <!-- <display file="ucsc/interval_as_bed.xml" /> --> <display file="genetrack.xml" /> diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/binary.py --- a/lib/galaxy/datatypes/binary.py Tue Mar 16 16:03:28 2010 -0400 +++ b/lib/galaxy/datatypes/binary.py Tue Mar 16 18:54:23 2010 -0400 @@ -139,7 +139,7 @@ except: return "Binary bam alignments file (%s)" % ( data.nice_size( dataset.get_size() ) ) def get_track_type( self ): - return "ReadTrack", "bai" + return "ReadTrack", ["bai", "array_tree"] class Binseq( Binary ): """Class describing a zip archive of binary sequence files""" diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/converters/bam_to_array_tree_converter.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/datatypes/converters/bam_to_array_tree_converter.py Tue Mar 16 18:54:23 2010 -0400 @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +from __future__ import division + +import sys +from galaxy import eggs +import pkg_resources; pkg_resources.require( "bx-python" ); pkg_resources.require( "pysam" ) + +from pysam import csamtools +from bx.arrays.array_tree import * + +BLOCK_SIZE = 1000 + +class BamReader: + def __init__( self, input_fname, index_fname ): + self.bamfile = csamtools.Samfile( filename=input_fname, mode='rb', index_filename=index_fname ) + self.iterator = self.bamfile.fetch() + + def __iter__( self ): + return self + + def __next__( self ): + while True: + read = self.iterator.next() + return read.rname, read.mpos, read.pos + read.rlen, None, mapq + + +def main(): + + input_fname = sys.argv[1] + index_fname = sys.argv[2] + out_fname = sys.argv[3] + + reader = BamReader( input_fname, index_fname ) + + # Fill array from reader + d = array_tree_dict_from_reader( reader, {}, block_size = BLOCK_SIZE ) + + for array_tree in d.itervalues(): + array_tree.root.build_summary() + + FileArrayTreeDict.dict_to_file( d, open( out_fname, "w" ), no_leaves=True ) + +if __name__ == "__main__": + main() \ No newline at end of file diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/converters/bam_to_array_tree_converter.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/datatypes/converters/bam_to_array_tree_converter.xml Tue Mar 16 18:54:23 2010 -0400 @@ -0,0 +1,15 @@ +<tool id="CONVERTER_bam_to_array_tree_0" name="Convert BAM to Array Tree" version="1.0.0"> +<!-- <description>__NOT_USED_CURRENTLY_FOR_CONVERTERS__</description> --> + <command interpreter="python">bam_to_array_tree_converter.py $input1 $output1</command> + <inputs> + <page> + <param format="bam" name="input1" type="data" label="Choose BAM file"/> + <param format="bai" name="index" type="data" label="BAM index file"/> + </page> + </inputs> + <outputs> + <data format="array_tree" name="output1"/> + </outputs> + <help> + </help> +</tool> diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/converters/bed_to_array_tree_converter.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/datatypes/converters/bed_to_array_tree_converter.py Tue Mar 16 18:54:23 2010 -0400 @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +from __future__ import division + +import sys +from galaxy import eggs +import pkg_resources; pkg_resources.require( "bx-python" ) +from bx.arrays.array_tree import * +from bx.arrays.bed import BedReader + +BLOCK_SIZE = 1000 + +def main(): + + input_fname = sys.argv[1] + out_fname = sys.argv[2] + + reader = BedReader( open( input_fname ) ) + + # Fill array from reader + d = array_tree_dict_from_reader( reader, {}, block_size = BLOCK_SIZE ) + + for array_tree in d.itervalues(): + array_tree.root.build_summary() + + FileArrayTreeDict.dict_to_file( d, open( out_fname, "w" ), no_leaves=True ) + +if __name__ == "__main__": + main() \ No newline at end of file diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/converters/bed_to_array_tree_converter.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/datatypes/converters/bed_to_array_tree_converter.xml Tue Mar 16 18:54:23 2010 -0400 @@ -0,0 +1,14 @@ +<tool id="CONVERTER_bed_to_array_tree_0" name="Convert BED to Array Tree" version="1.0.0"> +<!-- <description>__NOT_USED_CURRENTLY_FOR_CONVERTERS__</description> --> + <command interpreter="python">bed_to_array_tree_converter.py $input1 $output1</command> + <inputs> + <page> + <param format="bed" name="input1" type="data" label="Choose BED file"/> + </page> + </inputs> + <outputs> + <data format="array_tree" name="output1"/> + </outputs> + <help> + </help> +</tool> diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py --- a/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py Tue Mar 16 16:03:28 2010 -0400 +++ b/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.py Tue Mar 16 18:54:23 2010 -0400 @@ -6,7 +6,7 @@ from galaxy import eggs import pkg_resources; pkg_resources.require( "bx-python" ) from bx.arrays.array_tree import * -from bx.arrays.wiggle import IntervalReader +from bx.arrays.wiggle import WiggleReader BLOCK_SIZE = 100 @@ -15,17 +15,15 @@ input_fname = sys.argv[1] out_fname = sys.argv[2] - reader = IntervalReader( open( input_fname ) ) + reader = WiggleReader( open( input_fname ) ) - # Fill array from wiggle - d = array_tree_dict_from_wiggle_reader( reader, {}, block_size = BLOCK_SIZE ) + # Fill array from reader + d = array_tree_dict_from_reader( reader, {}, block_size = BLOCK_SIZE ) for array_tree in d.itervalues(): array_tree.root.build_summary() - f = open( out_fname, "w" ) - FileArrayTreeDict.dict_to_file( d, f ) - f.close() + FileArrayTreeDict.dict_to_file( d, open( out_fname, "w" ) ) if __name__ == "__main__": main() \ No newline at end of file diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.xml --- a/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.xml Tue Mar 16 16:03:28 2010 -0400 +++ b/lib/galaxy/datatypes/converters/wiggle_to_array_tree_converter.xml Tue Mar 16 18:54:23 2010 -0400 @@ -1,4 +1,4 @@ -<tool id="INDEXER_Wiggle_0" name="Index Wiggle for Track Viewer"> +<tool id="CONVERTER_Wiggle_0" name="Index Wiggle for Track Viewer"> <!-- Used internally to generate track indexes --> <command interpreter="python">wiggle_to_array_tree_converter.py $input $output</command> <inputs> diff -r 861756e85b16 -r 137d93848139 lib/galaxy/datatypes/interval.py --- a/lib/galaxy/datatypes/interval.py Tue Mar 16 16:03:28 2010 -0400 +++ b/lib/galaxy/datatypes/interval.py Tue Mar 16 18:54:23 2010 -0400 @@ -508,7 +508,7 @@ except: return False def get_track_type( self ): - return "FeatureTrack", "interval_index" + return "FeatureTrack", ["interval_index", "array_tree"] class BedStrict( Bed ): """Tab delimited data in strict BED format - no non-standard columns allowed""" @@ -959,7 +959,7 @@ resolution = max( resolution, 1 ) return resolution def get_track_type( self ): - return "LineTrack", "array_tree" + return "LineTrack", ["array_tree"] class CustomTrack ( Tabular ): """UCSC CustomTrack""" diff -r 861756e85b16 -r 137d93848139 lib/galaxy/visualization/tracks/data/array_tree.py --- a/lib/galaxy/visualization/tracks/data/array_tree.py Tue Mar 16 16:03:28 2010 -0400 +++ b/lib/galaxy/visualization/tracks/data/array_tree.py Tue Mar 16 18:54:23 2010 -0400 @@ -7,14 +7,21 @@ from bx.arrays.array_tree import FileArrayTreeDict except: pass -from math import floor, ceil, log +from math import floor, ceil, log, pow +import logging +logger = logging.getLogger(__name__) # Maybe this should be included in the datatype itself, so users can add their # own types to the browser as long as they return the right format of data? +SUMMARIZE_N = 200 + class ArrayTreeDataProvider( object ): def __init__( self, dataset, original_dataset ): self.dataset = dataset + + # def calc_resolution(self, start, end, density): + # return pow( 10, ceil( log( (end - start) / density , 10 ) ) ) def get_stats( self, chrom ): f = open( self.dataset.file_name ) @@ -26,8 +33,26 @@ return "no data" root_summary = chrom_array_tree.get_summary( 0, chrom_array_tree.levels ) + + level = chrom_array_tree.levels - 1 + desired_summary = chrom_array_tree.get_summary( 0, level ) + bs = chrom_array_tree.block_size ** level + + frequencies = map(int, desired_summary.frequencies) + out = [ (i * bs, freq) for i, freq in enumerate(frequencies) ] + f.close() - return { 'max': float( max(root_summary.maxs) ), 'min': float( min(root_summary.mins) ) } + return { 'max': float( max(root_summary.maxs) ), \ + 'min': float( min(root_summary.mins) ), \ + 'frequencies': out, \ + 'total_frequency': sum(root_summary.frequencies) } + + # Return None instead of NaN to pass jQuery 1.4's strict JSON + def float_nan(self, n): + if n != n: # NaN != NaN + return None + else: + return float(n) def get_data( self, chrom, start, end, **kwargs ): f = open( self.dataset.file_name ) @@ -44,28 +69,54 @@ start = int( start ) end = int( end ) resolution = max(1, ceil(float(kwargs['resolution']))) - - level = int( floor( log( resolution, block_size ) ) ) + + level = int( ceil( log( resolution, block_size ) ) ) level = max( level, 0 ) stepsize = block_size ** level - step1 = stepsize * block_size # Is the requested level valid? assert 0 <= level <= chrom_array_tree.levels - 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 ) ) ) + if "frequencies" in kwargs: + if level <= 0: + # Low level enough to always display features + f.close() + return None else: - v = chrom_array_tree.get_leaf( block_start ) - if v is not None: - results.extend( zip( indexes, map( float, v ) ) ) + # Round to nearest bin + bin_start = start // (stepsize * block_size) * (stepsize * block_size) + + indexes = range( bin_start, (bin_start + stepsize * block_size), stepsize ) + summary = chrom_array_tree.get_summary( bin_start, level ) + if summary: + results = zip( indexes, map( int, summary.frequencies ) ) + filtered = filter(lambda tup: tup[0] >= start and tup[0] <= end, results) + sums = 0 + max_f = 0 + for tup in filtered: + sums += tup[1] + max_f = max(max_f, tup[1]) + + if max_f > 10000: + f.close() + return filtered, int(sums), float(sums)/len(filtered) + f.close() + return None + + else: + 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: + results.extend( zip( indexes, map( self.float_nan, s.sums / s.counts ) ) ) + else: + l = chrom_array_tree.get_leaf( block_start ) + if l: + results.extend( zip( indexes, map( self.float_nan, l ) ) ) f.close() return results diff -r 861756e85b16 -r 137d93848139 lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py Tue Mar 16 16:03:28 2010 -0400 +++ b/lib/galaxy/web/controllers/tracks.py Tue Mar 16 18:54:23 2010 -0400 @@ -98,11 +98,10 @@ hda_query = trans.sa_session.query( model.HistoryDatasetAssociation ) dataset = hda_query.get( dataset_id ) - track_type, indexer = dataset.datatype.get_track_type() + track_type, _ = dataset.datatype.get_track_type() track = { "track_type": track_type, - "indexer": indexer, "name": dataset.name, "dataset_id": dataset.id, "prefs": {}, @@ -134,10 +133,9 @@ except KeyError: prefs = {} dataset = hda_query.get( dataset_id ) - track_type, indexer = dataset.datatype.get_track_type() + track_type, _ = dataset.datatype.get_track_type() tracks.append( { "track_type": track_type, - "indexer": indexer, "name": dataset.name, "dataset_id": dataset.id, "prefs": simplejson.dumps(prefs), @@ -187,41 +185,50 @@ return manifest @web.json - def data( self, trans, dataset_id, indexer, chrom, low, high, **kwargs ): + def data( self, trans, dataset_id, chrom, low, high, **kwargs ): """ Called by the browser to request a block of data """ - # Load the requested dataset dataset = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( dataset_id ) - # No dataset for that id if not dataset or not chrom: return messages.NO_DATA - # Dataset is in error state, can't display if dataset.state == trans.app.model.Job.states.ERROR: return messages.ERROR - # Dataset is still being generated if dataset.state != trans.app.model.Job.states.OK: return messages.PENDING - # Determine what to return based on the type of track being drawn. - converted_dataset_type = indexer - converted_dataset = self.__dataset_as_type( trans, dataset, converted_dataset_type ) - if not converted_dataset: - # No converter - return messages.NO_CONVERTER - # Need to check states again for the converted version - if converted_dataset.state == model.Dataset.states.ERROR: - return messages.ERROR - if converted_dataset.state != model.Dataset.states.OK: - return messages.PENDING - # We have a dataset in the right format that is ready to use, wrap in - # a data provider that knows how to access it - data_provider = dataset_type_to_data_provider[ converted_dataset_type ]( converted_dataset, dataset ) + + track_type, indexes = dataset.datatype.get_track_type() + converted = dict([ (index, self.__dataset_as_type( trans, dataset, index )) for index in indexes ]) - # Return stats if we need them - if 'stats' in kwargs: return data_provider.get_stats( chrom ) + for index, converted_dataset in converted.iteritems(): + if not converted_dataset: + return messages.NO_CONVERTER - # Get the requested chunk of data - return data_provider.get_data( chrom, low, high, **kwargs ) + # Need to check states again for the converted version + if converted_dataset.state == model.Dataset.states.ERROR: + return messages.ERROR + if converted_dataset.state != model.Dataset.states.OK: + return messages.PENDING + + if len(converted) > 1: + # Have to choose between array_tree and other provider + array_tree = ArrayTreeDataProvider( converted['array_tree'], dataset ) + freqs = array_tree.get_data( chrom, low, high, frequencies=True, **kwargs ) + if freqs is not None: + frequencies, sums, avg_f = freqs + return { "dataset_type": "array_tree", "data": frequencies, "sums": sums, "avg_f": avg_f } + dataset_type = "interval_index" + else: + dataset_type = converted.keys()[0] + + data_provider = dataset_type_to_data_provider[ dataset_type ]( converted[dataset_type], dataset ) + + if 'stats' in kwargs: + data = data_provider.get_stats( chrom ) + else: + data = data_provider.get_data( chrom, low, high, **kwargs ) + + return { "dataset_type": dataset_type, "data": data } def __dataset_as_type( self, trans, dataset, type ): """ @@ -240,12 +247,11 @@ # See if converted dataset already exists converted_datasets = [c for c in dataset.get_converted_files_by_type( type ) if c != None] if converted_datasets: - for d in converted_datasets: - if d.state != 'error': - return d - else: - return None - + if converted_datasets[0].state != 'error': + return converted_datasets[0] + else: + return None + # Conversion is possible but hasn't been done yet, run converter here # FIXME: this is largely duplicated from DefaultToolAction assoc = model.ImplicitlyConvertedDatasetAssociation( parent = dataset, file_type = type, metadata_safe = False ) @@ -285,7 +291,6 @@ for track in decoded_payload: tracks.append( { "dataset_id": str(track['dataset_id']), "name": track['name'], - "indexer": track['indexer'], "track_type": track['track_type'], "prefs": track['prefs'] } ) diff -r 861756e85b16 -r 137d93848139 static/scripts/trackster.js --- a/static/scripts/trackster.js Tue Mar 16 16:03:28 2010 -0400 +++ b/static/scripts/trackster.js Tue Mar 16 18:54:23 2010 -0400 @@ -6,6 +6,7 @@ var DENSITY = 1000, FEATURE_LEVELS = 10, DATA_ERROR = "There was an error in indexing this dataset.", + DATA_NOCONVERTER = "A converter for this dataset is not installed. Please check your datatypes_conf.xml file.", DATA_NONE = "No data for this chrom/contig.", DATA_PENDING = "Currently indexing... please wait", DATA_LOADING = "Loading data...", @@ -77,6 +78,18 @@ } }); +var Drawer = function() {}; +$.extend( Drawer.prototype, { + intensity: function(ctx, max, data) { + + + }, + +}); + +drawer = new Drawer(); + + var View = function( chrom, title, vis_id, dbkey ) { this.vis_id = vis_id; this.dbkey = dbkey; @@ -124,6 +137,13 @@ } } }, + reset: function() { + this.low = this.max_low; + this.high = this.max_high; + this.center = this.center = (this.max_high - this.max_low) / 2; + this.zoom_level = 0; + $(".yaxislabel").remove(); + }, redraw: function(nodraw) { this.span = this.max_high - this.max_low; var span = this.span / Math.pow(this.zoom_factor, this.zoom_level), @@ -156,20 +176,22 @@ $("#high").val( commatize(this.high) ); if (!nodraw) { for ( var i = 0, len = this.tracks.length; i < len; i++ ) { - this.tracks[i].draw(); + if (this.tracks[i].enabled) { + this.tracks[i].draw(); + } } for ( var i = 0, len = this.label_tracks.length; i < len; i++ ) { this.label_tracks[i].draw(); } } }, - zoom_in: function ( point ) { + zoom_in: function ( point, container ) { if (this.max_high === 0 || this.high - this.low < 30) { return; } if ( point ) { - this.center = point / $(document).width() * (this.high - this.low) + this.low; + this.center = point / container.width() * (this.high - this.low) + this.low; } this.zoom_level += 1; this.redraw(); @@ -201,6 +223,7 @@ }, init_each: function(params, success_fn) { var track = this; + track.enabled = false; track.data_queue = {}; track.tile_cache.clear(); track.data_cache.clear(); @@ -209,21 +232,25 @@ track.container_div.removeClass("nodata error pending"); if (track.view.chrom) { - $.getJSON( data_url, params, function ( data ) { - if (!data || data == "error") { + $.getJSON( data_url, params, function (result) { + if (!result || result === "error") { track.container_div.addClass("error"); track.content_div.text(DATA_ERROR); - } else if (data.length === 0 || data == "no data") { + } else if (result === "no converter") { + track.container_div.addClass("error"); + track.content_div.text(DATA_NOCONVERTER); + } else if ( (result.data && result.data.length === 0) || result === "no data") { track.container_div.addClass("nodata"); track.content_div.text(DATA_NONE); - } else if (data == "pending") { + } else if (result === "pending") { track.container_div.addClass("pending"); track.content_div.text(DATA_PENDING); setTimeout(function() { track.init(); }, 5000); } else { track.content_div.text(""); track.content_div.css( "height", track.height_px + "px" ); - success_fn(data); + track.enabled = true; + success_fn(result); track.draw(); } }); @@ -318,12 +345,11 @@ } }); -var LineTrack = function ( name, dataset_id, indexer, prefs ) { +var LineTrack = function ( name, dataset_id, prefs ) { this.track_type = "LineTrack"; Track.call( this, name, $("#viewport") ); TiledTrack.call( this ); - this.indexer = indexer; this.height_px = 100; this.container_div.addClass( "line-track" ); this.dataset_id = dataset_id; @@ -339,9 +365,9 @@ track_id = track.view.tracks.indexOf(track); track.vertical_range = undefined; - this.init_each({ stats: true, indexer: track.indexer, - chrom: track.view.chrom, low: null, high: null, - dataset_id: track.dataset_id }, function(data) { + this.init_each({ stats: true, chrom: track.view.chrom, low: null, high: null, + dataset_id: track.dataset_id }, function(result) { + data = result.data; if ( isNaN(parseFloat(track.prefs.min_value)) || isNaN(parseFloat(track.prefs.max_value)) ) { track.prefs.min_value = data.min; track.prefs.max_value = data.max; @@ -350,6 +376,7 @@ $('#track_' + track_id + '_maxval').val(track.prefs.max_value); } track.vertical_range = track.prefs.max_value - track.prefs.min_value; + track.total_frequency = data.total_frequency; // Draw y-axis labels if necessary $('#linetrack_' + track_id + '_minval').remove(); @@ -373,17 +400,29 @@ if (!track.data_queue[key]) { track.data_queue[key] = true; - $.getJSON( data_url, { "indexer": this.indexer, "chrom": this.view.chrom, + /*$.getJSON( data_url, { "chrom": this.view.chrom, "low": low, "high": high, "dataset_id": this.dataset_id, - "resolution": this.view.resolution, }, function (data) { + "resolution": this.view.resolution }, function (data) { track.data_cache.set(key, data); delete track.data_queue[key]; track.draw(); + });*/ + $.ajax({ 'url': data_url, 'dataType': 'json', 'data': { "chrom": this.view.chrom, + "low": low, "high": high, "dataset_id": this.dataset_id, + "resolution": this.view.resolution }, + success: function (result) { + data = result.data; + track.data_cache.set(key, data); + delete track.data_queue[key]; + track.draw(); + }, error: function(r, t, e) { + console.log(r, t, e); + } }); } }, draw_tile: function( resolution, tile_index, parent_element, w_scale ) { - if (this.vertical_range === undefined) { // We don't have the necessary information yet + if (this.vertical_range === undefined) { return; } @@ -398,6 +437,7 @@ } var data = this.data_cache.get(key); + canvas.css( { position: "absolute", top: 0, @@ -411,31 +451,56 @@ min_value = this.prefs.min_value, max_value = this.prefs.max_value, vertical_range = this.vertical_range, + total_frequency = this.total_frequency, height_px = this.height_px; ctx.beginPath(); - for ( var i = 0; i < data.length - 1; i++ ) { + + // for intensity, calculate delta x in pixels to for width of box + var delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale); + var mode = "line"; + + for ( var i = 0; i < data.length; i++ ) { var x = data[i][0] - tile_low; var y = data[i][1]; - // Missing data causes us to stop drawing - if ( isNaN( y ) ) { - in_path = false; - } else { - // Translate + + if ( mode == "intensity" ) { + // DRAW INTENSITY + if (y === null) { + continue; + } x = x * w_scale; - // console.log(y, this.min_value, this.vertical_range, (y - this.min_value) / this.vertical_range * this.height_px); if (y <= min_value) { y = min_value; } else if (y >= max_value) { y = max_value; } - y = Math.round( height_px - (y - min_value) / vertical_range * height_px ); - // console.log(canvas.get(0).height, canvas.get(0).width); - if ( in_path ) { - ctx.lineTo( x, y ); + y = Math.floor( (y - min_value) / vertical_range * 255 ); + ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")"; + ctx.fillRect(x, 0, delta_x_px, 30); + } + else { + // Missing data causes us to stop drawing + if (y === null) { + in_path = false; + continue; } else { - ctx.moveTo( x, y ); - in_path = true; + // Translate + x = x * w_scale; + // console.log(y, this.min_value, this.vertical_range, (y - this.min_value) / this.vertical_range * this.height_px); + if (y <= min_value) { + y = min_value; + } else if (y >= max_value) { + y = max_value; + } + y = Math.round( height_px - (y - min_value) / vertical_range * height_px ); + // console.log(canvas.get(0).height, canvas.get(0).width); + if ( in_path ) { + ctx.lineTo( x, y ); + } else { + ctx.moveTo( x, y ); + in_path = true; + } } } } @@ -471,12 +536,11 @@ } }); -var FeatureTrack = function ( name, dataset_id, indexer, prefs ) { +var FeatureTrack = function ( name, dataset_id, prefs ) { this.track_type = "FeatureTrack"; Track.call( this, name, $("#viewport") ); TiledTrack.call( this ); - this.indexer = indexer; this.height_px = 100; this.container_div.addClass( "feature-track" ); this.dataset_id = dataset_id; @@ -500,13 +564,16 @@ }; $.extend( FeatureTrack.prototype, TiledTrack.prototype, { init: function() { - var track = this; - this.init_each({ indexer: track.indexer, low: track.view.max_low, + var track = this, + key = track.view.max_low + '_' + track.view.max_high; + this.init_each({ low: track.view.max_low, high: track.view.max_high, dataset_id: track.dataset_id, - chrom: track.view.chrom }, function (data) { - track.values = data; - track.calc_slots(); - track.slots = track.zo_slots; + chrom: track.view.chrom, resolution: this.view.resolution }, function (result) { + track.data_cache.set(key, result); + // track.values = result; + // track.calc_slots(); + // track.slots = track.zo_slots; + track.draw(); }); }, get_data: function( low, high ) { @@ -515,10 +582,10 @@ if (!track.data_queue[key]) { track.data_queue[key] = true; - $.getJSON( data_url, { indexer: track.indexer, chrom: track.view.chrom, + $.getJSON( data_url, { chrom: track.view.chrom, low: low, high: high, dataset_id: track.dataset_id, - include_blocks: true }, function (data) { - track.data_cache.set(key, data); + include_blocks: true, resolution: this.view.resolution }, function (result) { + track.data_cache.set(key, result); // console.log("datacache", track.data_cache.get(key)); delete track.data_queue[key]; track.draw(); @@ -612,52 +679,48 @@ }, draw_tile: function( resolution, tile_index, parent_element, w_scale ) { - if (!this.values) { - return; - } var tile_low = tile_index * DENSITY * resolution, tile_high = ( tile_index + 1 ) * DENSITY * resolution, tile_span = DENSITY * resolution; // console.log("drawing " + tile_index); - // Once we zoom in enough, show name labels var data, slots, required_height; - if (w_scale > this.show_labels_scale) { - if (!this.showing_details) { - this.showing_details = true; + + /*for (var k in this.data_cache.obj_cache) { + var k_split = k.split("_"), k_low = k_split[0], k_high = k_split[1]; + if (k_low <= tile_low && k_high >= tile_high) { + data = this.data_cache.get(k); + break; } - for (var k in this.data_cache.obj_cache) { - var k_split = k.split("_"), k_low = k_split[0], k_high = k_split[1]; - if (k_low <= tile_low && k_high >= tile_high) { - data = this.data_cache.get(k); - break; - } - } - if (!data) { - this.data_queue[ [tile_low, tile_high] ] = true; - this.get_data(tile_low, tile_high); - return; - } - // Calculate new slots incrementally for this new chunk of data and update height if necessary - required_height = this.incremental_slots( this.view.zoom_res, data ) * this.vertical_detail_px + 15; + }*/ + + // var k = this.view.low + '_' + this.view.high; + var k = tile_low + '_' + tile_high; + var data = this.data_cache.get(k); + + if (!data) { + this.data_queue[ [tile_low, tile_high] ] = true; + this.get_data(tile_low, tile_high); + return; + } + + if (data.dataset_type == "array_tree") { + required_height = 30; + // Blah + } else { + // Calculate new slots incrementally for this new chunk of data and update height if necessary + required_height = this.incremental_slots( this.view.zoom_res, data.data ) * this.vertical_detail_px + 15; // console.log(required_height); slots = this.inc_slots[this.view.zoom_res]; - } else { - if (this.showing_details) { - this.showing_details = false; - } - required_height = this.height_px; - slots = this.zo_slots; - data = this.values; } - + // console.log(tile_low, tile_high, tile_length, w_scale); var width = Math.ceil( tile_span * w_scale ), new_canvas = $("<canvas class='tile'></canvas>"), label_color = this.prefs.label_color, block_color = this.prefs.block_color, left_offset = this.left_offset, - showing_details = this.showing_details, - y_scale = (this.showing_details ? this.vertical_detail_px : this.vertical_nodetail_px); + // showing_details = this.showing_details, + y_scale = this.vertical_detail_px; new_canvas.css({ position: "absolute", @@ -671,7 +734,30 @@ ctx.fillStyle = this.prefs.block_color; ctx.font = this.default_font; ctx.textAlign = "right"; - + var min_color = 150; + + if (data.dataset_type == "array_tree") { + var points = data.data; + var sums = data.sums; + var avg_f = data.avg_f; + var delta_x_px = Math.ceil((points[1][0] - points[0][0]) * w_scale); + + for ( var i = 0, len = points.length; i < len; i++ ) { + var x = Math.ceil( (points[i][0] - tile_low) * w_scale ); + var y = points[i][1]; + + if (!y) { + continue; + } + y = Math.floor( min_color + (y - avg_f)/sums * min_color ); + ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")"; + ctx.fillRect(x + left_offset, 0, delta_x_px, 20); + } + parent_element.append( new_canvas ); + return new_canvas; + } + + var data = data.data; var j = 0; for (var i = 0, len = data.length; i < len; i++) { var feature = data[i]; @@ -685,10 +771,10 @@ thick_start = Math.floor( Math.max(0, (feature.thick_start - tile_low) * w_scale) ); thick_end = Math.ceil( Math.min(width, (feature.thick_end - tile_low) * w_scale) ); } - if (!showing_details) { + // if (!showing_details) { // Non-detail levels - ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, 1); - } else { + // ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, 1); + // } else { // Showing labels, blocks, details if (feature.start > tile_low) { ctx.fillStyle = label_color; @@ -743,7 +829,7 @@ ctx.fillStyle = prefs.block_color; } } - } + // } j++; } } @@ -772,12 +858,12 @@ } }); -var ReadTrack = function ( name, dataset_id, indexer, prefs ) { +var ReadTrack = function ( name, dataset_id, prefs ) { this.track_type = "ReadTrack"; this.tile_cache = new Cache(CACHED_TILES_FEATURE); Track.call( this, name, $("#viewport") ); TiledTrack.call( this ); - FeatureTrack.call( this, name, dataset_id, indexer, prefs ); + FeatureTrack.call( this, name, dataset_id, prefs ); }; $.extend( ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, { diff -r 861756e85b16 -r 137d93848139 static/trackster.css --- a/static/trackster.css Tue Mar 16 16:03:28 2010 -0400 +++ b/static/trackster.css Tue Mar 16 18:54:23 2010 -0400 @@ -30,13 +30,7 @@ } #nav-controls a { - color: white; - padding: 0.1em 0.4em; - margin: 0 0; - text-decoration: none; - background: black; - -webkit-border-radius: 1em; - -moz-border-radius: 1em; + padding: 5px 0.4em; } #overview { diff -r 861756e85b16 -r 137d93848139 templates/tracks/browser.mako --- a/templates/tracks/browser.mako Tue Mar 16 16:03:28 2010 -0400 +++ b/templates/tracks/browser.mako Tue Mar 16 18:54:23 2010 -0400 @@ -51,8 +51,12 @@ </select> <input id="low" size="12" />:<input id="high" size="12" /> <input type="hidden" name="id" value="${config.get('vis_id', '')}" /> - <a href="#" onclick="javascript:view.zoom_in();view.redraw();">+</a> - <a href="#" onclick="javascript:view.zoom_out();view.redraw();">-</a> + <a href="#" onclick="javascript:view.zoom_in();view.redraw();"> + <img src="${h.url_for('/static/images/fugue/magnifier-zoom.png')}" /> + </a> + <a href="#" onclick="javascript:view.zoom_out();view.redraw();"> + <img src="${h.url_for('/static/images/fugue/magnifier-zoom-out.png')}" /> + </a> </form> <div id="debug" style="float: right"></div> </div> @@ -91,7 +95,7 @@ view = new View( "${config.get('chrom')}", "${config.get('title') | h}", "${config.get('vis_id')}", "${config.get('dbkey')}" ); %for track in config.get('tracks'): view.add_track( - new ${track["track_type"]}( "${track['name'] | h}", ${track['dataset_id']}, "${track['indexer']}", ${track['prefs']} ) + new ${track["track_type"]}( "${track['name'] | h}", ${track['dataset_id']}, ${track['prefs']} ) ); %endfor init(); @@ -131,7 +135,7 @@ $("#content").bind("mousewheel", function( e, delta ) { if (delta > 0) { - view.zoom_in(e.pageX); + view.zoom_in(e.pageX, $("#viewport-container")); } else { view.zoom_out(); } @@ -139,7 +143,7 @@ }); $("#content").bind("dblclick", function( e ) { - view.zoom_in(e.pageX); + view.zoom_in(e.pageX, $("#viewport-container")); }); // To let the overview box be draggable @@ -210,13 +214,13 @@ var td = track_data; switch(track_data.track_type) { case "LineTrack": - new_track = new LineTrack( track_data.name, track_data.dataset_id, track_data.indexer, track_data.prefs ); + new_track = new LineTrack( track_data.name, track_data.dataset_id, track_data.prefs ); break; case "FeatureTrack": - new_track = new FeatureTrack( track_data.name, track_data.dataset_id, track_data.indexer, track_data.prefs ); + new_track = new FeatureTrack( track_data.name, track_data.dataset_id, track_data.prefs ); break; case "ReadTrack": - new_track = new ReadTrack( track_data.name, track_data.dataset_id, track_data.indexer, track_data.prefs ); + new_track = new ReadTrack( track_data.name, track_data.dataset_id, track_data.prefs ); break; } view.add_track(new_track); @@ -245,7 +249,6 @@ payload.push( { "track_type": track.track_type, - "indexer": track.indexer, "name": track.name, "dataset_id": track.dataset_id, "prefs": track.prefs @@ -286,6 +289,7 @@ return v.chrom === view.chrom; })[0]; view.max_high = found.len; + view.reset(); view.redraw(true); for (var track_id in view.tracks) { @@ -307,13 +311,12 @@ del_icon = $('<a href="#" class="icon-button delete" />'), edit_icon = $('<a href="#" class="icon-button edit" />'), body = $('<div class="historyItemBody"></div>'), - checkbox = $('<input type="checkbox" checked="checked"></input>').attr("id", "track_" + track_id + "title"), li = $('<li class="sortable"></li>').attr("id", "track_" + track_id), div = $('<div class="historyItemContainer historyItem"></div>'), - editable = $('<div style="display:none"></div>'); + editable = $('<div style="display:none"></div>').attr("id", "track_" + track_id + "_editable"); edit_icon.bind("click", function() { - editable.toggle(); + $("#track_" + track_id + "_editable").toggle(); }); del_icon.bind("click", function() {