commit/galaxy-central: 5 new changesets
5 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/dd200ac195bd/ changeset: dd200ac195bd user: james_taylor date: 2012-06-21 16:17:07 summary: trackster: diagonal heatmap track type, for chromatin interactions. currently disabled because the data indexing is inefficient affected #: 5 files diff -r 0b8c0fda4f852199d12e67cbd7bcdda9a99ac444 -r dd200ac195bd7ea45d3abd6307ae3785d8487950 lib/galaxy/datatypes/interval.py --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -1292,7 +1292,32 @@ return False def get_track_type( self ): - return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + +class ChromatinInteractions( Interval ): + ''' + Chromatin interactions obtained from 3C/5C/Hi-C experiments. + ''' + + file_ext = "chrint" + + column_names = [ 'Chrom', 'Start1', 'End1', 'Start2', 'End2', 'Value' ] + + """Add metadata elements""" + MetadataElement( name="chromCol", default=1, desc="Chrom column", param=metadata.ColumnParameter ) + MetadataElement( name="start1Col", default=2, desc="Start1 column", param=metadata.ColumnParameter ) + MetadataElement( name="end1Col", default=3, desc="End1 column", param=metadata.ColumnParameter ) + MetadataElement( name="start2Col", default=2, desc="Start2 column", param=metadata.ColumnParameter ) + MetadataElement( name="end2Col", default=3, desc="End2 column", param=metadata.ColumnParameter ) + MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True, visible=False ) + + def sniff( self, filename ): + return False + + def get_track_type( self ): + return "DiagonalHeatmapTrack", {"data": "tabix", "index": "summary_tree"} + + if __name__ == '__main__': import doctest, sys diff -r 0b8c0fda4f852199d12e67cbd7bcdda9a99ac444 -r dd200ac195bd7ea45d3abd6307ae3785d8487950 lib/galaxy/visualization/tracks/data_providers.py --- a/lib/galaxy/visualization/tracks/data_providers.py +++ b/lib/galaxy/visualization/tracks/data_providers.py @@ -19,7 +19,7 @@ from galaxy.visualization.tracks.summary import * import galaxy_utils.sequence.vcf from galaxy.datatypes.tabular import Vcf -from galaxy.datatypes.interval import Bed, Gff, Gtf, ENCODEPeak +from galaxy.datatypes.interval import Bed, Gff, Gtf, ENCODEPeak, ChromatinInteractions from pysam import csamtools, ctabix @@ -151,6 +151,9 @@ { 'name' : attrs[ 'name' ], 'type' : column_types[viz_col_index], \ 'index' : attrs[ 'index' ] } ) return filters + + def get_default_max_vals( self ): + return 5000; # # -- Base mixins and providers -- @@ -1104,6 +1107,67 @@ 'tool_id': 'Filter1', 'tool_exp_name': 'c9' } ) return filters + +# +# -- ChromatinInteraction data providers -- +# +class ChromatinInteractionsDataProvider( TracksDataProvider ): + def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ): + """ + Provides + """ + + rval = [] + message = None + for count, line in enumerate( iterator ): + if count < start_val: + continue + if max_vals and count-start_val >= max_vals: + message = ERROR_MAX_VALS % ( max_vals, "interactions" ) + break + + feature = line.split() + length = len( feature ) + + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3], + s2 = int( feature[4] ), + e2 = int( feature[5] ), + v = float( feature[6] ) + + # Feature initialization. + payload = [ + # GUID is just a hash of the line + hash( line ), + # Add start1, end1, chr2, start2, end2, value. + s1, e1, c, s2, e2, v + ] + + rval.append( payload ) + + return { 'data': rval, 'message': message } + + def get_default_max_vals( self ): + return 50000; + +class ChromatinInteractionsTabixDataProvider( TabixDataProvider, ChromatinInteractionsDataProvider ): + def get_iterator( self, chrom, start, end ): + """ + """ + # Modify start as needed to get earlier interactions with start region. + start = max( 0, int( start) - 1000000 ) + def filter( iter ): + for line in iter: + feature = line.split() + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3] + s2 = int( feature[4] ), + e2 = int( feature[5] ), + if ( ( c == chrom ) and ( s1 < end and e1 > start ) and ( s2 < end and e2 > start ) ): + yield line + return filter( TabixDataProvider.get_iterator( self, chrom, start, end ) ) # # -- Helper methods. -- @@ -1113,7 +1177,8 @@ # type. First key is converted dataset type; if result is another dict, second key # is original dataset type. TODO: This needs to be more flexible. dataset_type_name_to_data_provider = { - "tabix": { Vcf: VcfTabixDataProvider, Bed: BedTabixDataProvider, ENCODEPeak: ENCODEPeakTabixDataProvider, "default" : TabixDataProvider }, + "tabix": { Vcf: VcfTabixDataProvider, Bed: BedTabixDataProvider, ENCODEPeak: ENCODEPeakTabixDataProvider, "default" : TabixDataProvider, + ChromatinInteractions: ChromatinInteractionsTabixDataProvider }, "interval_index": IntervalIndexDataProvider, "bai": BamDataProvider, "bam": SamDataProvider, diff -r 0b8c0fda4f852199d12e67cbd7bcdda9a99ac444 -r dd200ac195bd7ea45d3abd6307ae3785d8487950 lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -559,7 +559,7 @@ return { "status": messages.DATA, "valid_chroms": valid_chroms } @web.json - def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=5000, **kwargs ): + def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=None, **kwargs ): """ Provides a block of data from a dataset. """ @@ -615,6 +615,10 @@ deps = dataset.get_converted_dataset_deps( trans, tracks_dataset_type ) data_provider = data_provider_class( converted_dataset=converted_dataset, original_dataset=dataset, dependencies=deps ) + # Allow max_vals top be data provider set if not passed + if max_vals is None: + max_vals = data_provider.get_default_max_vals() + # Get and return data from data_provider. result = data_provider.get_data( chrom, low, high, int( start_val ), int( max_vals ), **kwargs ) result.update( { 'dataset_type': tracks_dataset_type, 'extra_info': extra_info } ) diff -r 0b8c0fda4f852199d12e67cbd7bcdda9a99ac444 -r dd200ac195bd7ea45d3abd6307ae3785d8487950 static/scripts/trackster.js --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -4478,6 +4478,84 @@ }, }); +var DiagonalHeatmapTrack = function (view, container, obj_dict) { + var track = this; + this.display_modes = ["Heatmap"]; + this.mode = "Heatmap"; + TiledTrack.call(this, view, container, obj_dict); + + // This all seems to be duplicated + this.hda_ldda = obj_dict.hda_ldda; + this.dataset_id = obj_dict.dataset_id; + this.original_dataset_id = this.dataset_id; + this.left_offset = 0; + + // Define track configuration + this.config = new DrawableConfig( { + track: this, + params: [ + { key: 'name', label: 'Name', type: 'text', default_value: this.name }, + { key: 'pos_color', label: 'Positive Color', type: 'color', default_value: "4169E1" }, + { key: 'negative_color', label: 'Negative Color', type: 'color', default_value: "FF8C00" }, + { key: 'min_value', label: 'Min Value', type: 'float', default_value: 0 }, + { key: 'max_value', label: 'Max Value', type: 'float', default_value: 1 }, + { key: 'mode', type: 'string', default_value: this.mode, hidden: true }, + { key: 'height', type: 'int', default_value: 500, hidden: true } + ], + saved_values: obj_dict.prefs, + onchange: function() { + track.set_name(track.prefs.name); + track.vertical_range = track.prefs.max_value - track.prefs.min_value; + track.set_min_value(track.prefs.min_value); + track.set_max_value(track.prefs.max_value); + } + }); + + this.prefs = this.config.values; + this.visible_height_px = this.config.values.height; + this.vertical_range = this.config.values.max_value - this.config.values.min_value; +}; +extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype, { + /** + * Action to take during resize. + */ + on_resize: function() { + this.request_draw(true); + }, + /** + * Set track minimum value. + */ + set_min_value: function(new_val) { + this.prefs.min_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + /** + * Set track maximum value. + */ + set_max_value: function(new_val) { + this.prefs.max_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + + /** + * Draw LineTrack tile. + */ + draw_tile: function(result, ctx, mode, resolution, tile_index, w_scale) { + // Paint onto canvas. + var + canvas = ctx.canvas, + tile_bounds = this._get_tile_bounds(tile_index, resolution), + tile_low = tile_bounds[0], + tile_high = tile_bounds[1], + painter = new painters.DiagonalHeatmapPainter(result.data, tile_low, tile_high, this.prefs, mode); + painter.draw(ctx, canvas.width, canvas.height, w_scale); + + return new Tile(this, tile_index, resolution, canvas, result.data); + } +}); + var FeatureTrack = function(view, container, obj_dict) { // // Preinitialization: do things that need to be done before calling Track and TiledTrack @@ -5017,6 +5095,7 @@ exports.DrawableGroup = DrawableGroup; exports.LineTrack = LineTrack; exports.FeatureTrack = FeatureTrack; +exports.DiagonalHeatmapTrack = DiagonalHeatmapTrack; exports.ReadTrack = ReadTrack; exports.VcfTrack = VcfTrack; exports.CompositeTrack = CompositeTrack; @@ -6179,12 +6258,224 @@ } }); +// Color stuff from less.js + +var Color = function (rgb, a) { + /** + * The end goal here, is to parse the arguments + * into an integer triplet, such as `128, 255, 0` + * + * This facilitates operations and conversions. + */ + if (Array.isArray(rgb)) { + this.rgb = rgb; + } else if (rgb.length == 6) { + this.rgb = rgb.match(/.{2}/g).map(function (c) { + return parseInt(c, 16); + }); + } else { + this.rgb = rgb.split('').map(function (c) { + return parseInt(c + c, 16); + }); + } + this.alpha = typeof(a) === 'number' ? a : 1; +}; +Color.prototype = { + eval: function () { return this }, + + // + // If we have some transparency, the only way to represent it + // is via `rgba`. Otherwise, we use the hex representation, + // which has better compatibility with older browsers. + // Values are capped between `0` and `255`, rounded and zero-padded. + // + toCSS: function () { + if (this.alpha < 1.0) { + return "rgba(" + this.rgb.map(function (c) { + return Math.round(c); + }).concat(this.alpha).join(', ') + ")"; + } else { + return '#' + this.rgb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + } + }, + + toHSL: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2, d = max - min; + + if (max === min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h * 360, s: s, l: l, a: a }; + }, + + toARGB: function () { + var argb = [Math.round(this.alpha * 255)].concat(this.rgb); + return '#' + argb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + + mix: function (color2, weight) { + color1 = this; + + var p = weight; // .value / 100.0; + var w = p * 2 - 1; + var a = color1.toHSL().a - color2.toHSL().a; + + var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, + color1.rgb[1] * w1 + color2.rgb[1] * w2, + color1.rgb[2] * w1 + color2.rgb[2] * w2]; + + var alpha = color1.alpha * p + color2.alpha * (1 - p); + + return new Color(rgb, alpha); + } +}; + + +// End colors from less.js + +var LinearRamp = function( start_color, end_color, start_value, end_value ) { + /** + * Simple linear gradient + */ + this.start_color = new Color( start_color ); + this.end_color = new Color( end_color ); + this.start_value = start_value; + this.end_value = end_value; + this.value_range = end_value - start_value; +} +LinearRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + value = ( value - this.start_value ) / this.value_range; + // HACK: just red for now + // return "hsl(0,100%," + (value * 100) + "%)" + return this.start_color.mix( this.end_color, 1 - value ).toCSS(); +} + +var SplitRamp = function( start_color, middle_color, end_color, start_value, end_value ) { + /** + * Two gradients split away from 0 + */ + this.positive_ramp = new LinearRamp( middle_color, end_color, 0, end_value ); + this.negative_ramp = new LinearRamp( middle_color, start_color, 0, -start_value ); + this.start_value = start_value; + this.end_value = end_value; +} +SplitRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + if ( value >= 0 ) { + return this.positive_ramp.map_value( value ) + } else { + return this.negative_ramp.map_value( -value ) + } +} + +var DiagonalHeatmapPainter = function(data, view_start, view_end, prefs, mode) { + Painter.call( this, data, view_start, view_end, prefs, mode ); + if ( this.prefs.min_value === undefined ) { + var min_value = Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + min_value = Math.min( min_value, this.data[i][5] ); + } + this.prefs.min_value = min_value; + } + if ( this.prefs.max_value === undefined ) { + var max_value = -Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + max_value = Math.max( max_value, this.data[i][5] ); + } + this.prefs.max_value = max_value; + } +}; + +DiagonalHeatmapPainter.prototype.default_prefs = { + min_value: undefined, + max_value: undefined, + mode: "Heatmap", + pos_color: "4169E1", + neg_color: "FF8C00" +}; + +DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) { + var + min_value = this.prefs.min_value, + max_value = this.prefs.max_value, + value_range = max_value - min_value, + height_px = height, + view_start = this.view_start, + view_range = this.view_end - this.view_start, + mode = this.mode, + data = this.data, + invsqrt2 = 1 / Math.sqrt(2); + + var ramp = ( new SplitRamp( this.prefs.neg_color, "FFFFFF", this.prefs.pos_color, min_value, max_value ) ); + + var d, s1, e1, s2, e2, value; + + var scale = function( p ) { return ( p - view_start ) * w_scale }; + + ctx.save(); + + // Draw into triangle, then rotate and scale + ctx.rotate(-45 * Math.PI / 180); + ctx.scale( invsqrt2, invsqrt2 ); + + // Paint track. + for (var i = 0, len = data.length; i < len; i++) { + + d = data[i]; + + // Ensure the cell is visible + // if ( ) + + s1 = scale( d[1] ); + e1 = scale( d[2] ); + s2 = scale( d[4] ); + e2 = scale( d[5] ); + value = d[6]; + + ctx.fillStyle = ( ramp.map_value( value ) ) + + ctx.fillRect( s1, s2, ( e1 - s1 ), ( e2 - s2 ) ); + } + + ctx.restore(); +}; + exports.Scaler = Scaler; exports.SummaryTreePainter = SummaryTreePainter; exports.LinePainter = LinePainter; exports.LinkedFeaturePainter = LinkedFeaturePainter; exports.ReadPainter = ReadPainter; exports.ArcLinkedFeaturePainter = ArcLinkedFeaturePainter; +exports.DiagonalHeatmapPainter = DiagonalHeatmapPainter; // End painters_module encapsulation }; diff -r 0b8c0fda4f852199d12e67cbd7bcdda9a99ac444 -r dd200ac195bd7ea45d3abd6307ae3785d8487950 static/scripts/trackster_ui.js --- a/static/scripts/trackster_ui.js +++ b/static/scripts/trackster_ui.js @@ -61,6 +61,7 @@ "FeatureTrack": FeatureTrack, "VcfTrack": VcfTrack, "ReadTrack": ReadTrack, + // "DiagonalHeatmapTrack": DiagonalHeatmapTrack, "CompositeTrack": CompositeTrack, "DrawableGroup": DrawableGroup }; https://bitbucket.org/galaxy/galaxy-central/changeset/d8ab6ffb8acb/ changeset: d8ab6ffb8acb user: james_taylor date: 2012-06-21 16:22:21 summary: merge affected #: 5 files diff -r f6a710440c0500fb09e980300419b53b2cda6088 -r d8ab6ffb8acb51791e4a3b425ccb0788591ebd86 lib/galaxy/datatypes/interval.py --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -1298,7 +1298,32 @@ return False def get_track_type( self ): - return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + +class ChromatinInteractions( Interval ): + ''' + Chromatin interactions obtained from 3C/5C/Hi-C experiments. + ''' + + file_ext = "chrint" + + column_names = [ 'Chrom', 'Start1', 'End1', 'Start2', 'End2', 'Value' ] + + """Add metadata elements""" + MetadataElement( name="chromCol", default=1, desc="Chrom column", param=metadata.ColumnParameter ) + MetadataElement( name="start1Col", default=2, desc="Start1 column", param=metadata.ColumnParameter ) + MetadataElement( name="end1Col", default=3, desc="End1 column", param=metadata.ColumnParameter ) + MetadataElement( name="start2Col", default=2, desc="Start2 column", param=metadata.ColumnParameter ) + MetadataElement( name="end2Col", default=3, desc="End2 column", param=metadata.ColumnParameter ) + MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True, visible=False ) + + def sniff( self, filename ): + return False + + def get_track_type( self ): + return "DiagonalHeatmapTrack", {"data": "tabix", "index": "summary_tree"} + + if __name__ == '__main__': import doctest, sys diff -r f6a710440c0500fb09e980300419b53b2cda6088 -r d8ab6ffb8acb51791e4a3b425ccb0788591ebd86 lib/galaxy/visualization/tracks/data_providers.py --- a/lib/galaxy/visualization/tracks/data_providers.py +++ b/lib/galaxy/visualization/tracks/data_providers.py @@ -19,7 +19,7 @@ from galaxy.visualization.tracks.summary import * import galaxy_utils.sequence.vcf from galaxy.datatypes.tabular import Vcf -from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak +from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak, ChromatinInteractions from pysam import csamtools, ctabix @@ -146,6 +146,9 @@ { 'name' : attrs[ 'name' ], 'type' : column_types[viz_col_index], \ 'index' : attrs[ 'index' ] } ) return filters + + def get_default_max_vals( self ): + return 5000; # # -- Base mixins and providers -- @@ -1254,6 +1257,67 @@ 'tool_id': 'Filter1', 'tool_exp_name': 'c9' } ) return filters + +# +# -- ChromatinInteraction data providers -- +# +class ChromatinInteractionsDataProvider( TracksDataProvider ): + def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ): + """ + Provides + """ + + rval = [] + message = None + for count, line in enumerate( iterator ): + if count < start_val: + continue + if max_vals and count-start_val >= max_vals: + message = ERROR_MAX_VALS % ( max_vals, "interactions" ) + break + + feature = line.split() + length = len( feature ) + + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3], + s2 = int( feature[4] ), + e2 = int( feature[5] ), + v = float( feature[6] ) + + # Feature initialization. + payload = [ + # GUID is just a hash of the line + hash( line ), + # Add start1, end1, chr2, start2, end2, value. + s1, e1, c, s2, e2, v + ] + + rval.append( payload ) + + return { 'data': rval, 'message': message } + + def get_default_max_vals( self ): + return 50000; + +class ChromatinInteractionsTabixDataProvider( TabixDataProvider, ChromatinInteractionsDataProvider ): + def get_iterator( self, chrom, start, end ): + """ + """ + # Modify start as needed to get earlier interactions with start region. + start = max( 0, int( start) - 1000000 ) + def filter( iter ): + for line in iter: + feature = line.split() + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3] + s2 = int( feature[4] ), + e2 = int( feature[5] ), + if ( ( c == chrom ) and ( s1 < end and e1 > start ) and ( s2 < end and e2 > start ) ): + yield line + return filter( TabixDataProvider.get_iterator( self, chrom, start, end ) ) # # -- Helper methods. -- @@ -1269,7 +1333,9 @@ Gtf: GtfTabixDataProvider, ENCODEPeak: ENCODEPeakTabixDataProvider, Interval: IntervalTabixDataProvider, - "default" : TabixDataProvider }, + ChromatinInteractions: ChromatinInteractionsTabixDataProvider, + "default" : TabixDataProvider + }, "interval_index": IntervalIndexDataProvider, "bai": BamDataProvider, "bam": SamDataProvider, diff -r f6a710440c0500fb09e980300419b53b2cda6088 -r d8ab6ffb8acb51791e4a3b425ccb0788591ebd86 lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -345,7 +345,7 @@ return { "status": messages.DATA, "valid_chroms": valid_chroms } @web.json - def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=5000, **kwargs ): + def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=None, **kwargs ): """ Provides a block of data from a dataset. """ @@ -401,6 +401,10 @@ deps = dataset.get_converted_dataset_deps( trans, tracks_dataset_type ) data_provider = data_provider_class( converted_dataset=converted_dataset, original_dataset=dataset, dependencies=deps ) + # Allow max_vals top be data provider set if not passed + if max_vals is None: + max_vals = data_provider.get_default_max_vals() + # Get and return data from data_provider. result = data_provider.get_data( chrom, low, high, int( start_val ), int( max_vals ), **kwargs ) result.update( { 'dataset_type': tracks_dataset_type, 'extra_info': extra_info } ) diff -r f6a710440c0500fb09e980300419b53b2cda6088 -r d8ab6ffb8acb51791e4a3b425ccb0788591ebd86 static/scripts/viz/trackster.js --- a/static/scripts/viz/trackster.js +++ b/static/scripts/viz/trackster.js @@ -4247,6 +4247,84 @@ }, }); +var DiagonalHeatmapTrack = function (view, container, obj_dict) { + var track = this; + this.display_modes = ["Heatmap"]; + this.mode = "Heatmap"; + TiledTrack.call(this, view, container, obj_dict); + + // This all seems to be duplicated + this.hda_ldda = obj_dict.hda_ldda; + this.dataset_id = obj_dict.dataset_id; + this.original_dataset_id = this.dataset_id; + this.left_offset = 0; + + // Define track configuration + this.config = new DrawableConfig( { + track: this, + params: [ + { key: 'name', label: 'Name', type: 'text', default_value: this.name }, + { key: 'pos_color', label: 'Positive Color', type: 'color', default_value: "4169E1" }, + { key: 'negative_color', label: 'Negative Color', type: 'color', default_value: "FF8C00" }, + { key: 'min_value', label: 'Min Value', type: 'float', default_value: 0 }, + { key: 'max_value', label: 'Max Value', type: 'float', default_value: 1 }, + { key: 'mode', type: 'string', default_value: this.mode, hidden: true }, + { key: 'height', type: 'int', default_value: 500, hidden: true } + ], + saved_values: obj_dict.prefs, + onchange: function() { + track.set_name(track.prefs.name); + track.vertical_range = track.prefs.max_value - track.prefs.min_value; + track.set_min_value(track.prefs.min_value); + track.set_max_value(track.prefs.max_value); + } + }); + + this.prefs = this.config.values; + this.visible_height_px = this.config.values.height; + this.vertical_range = this.config.values.max_value - this.config.values.min_value; +}; +extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype, { + /** + * Action to take during resize. + */ + on_resize: function() { + this.request_draw(true); + }, + /** + * Set track minimum value. + */ + set_min_value: function(new_val) { + this.prefs.min_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + /** + * Set track maximum value. + */ + set_max_value: function(new_val) { + this.prefs.max_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + + /** + * Draw LineTrack tile. + */ + draw_tile: function(result, ctx, mode, resolution, tile_index, w_scale) { + // Paint onto canvas. + var + canvas = ctx.canvas, + tile_bounds = this._get_tile_bounds(tile_index, resolution), + tile_low = tile_bounds[0], + tile_high = tile_bounds[1], + painter = new painters.DiagonalHeatmapPainter(result.data, tile_low, tile_high, this.prefs, mode); + painter.draw(ctx, canvas.width, canvas.height, w_scale); + + return new Tile(this, tile_index, resolution, canvas, result.data); + } +}); + var FeatureTrack = function(view, container, obj_dict) { // // Preinitialization: do things that need to be done before calling Track and TiledTrack @@ -4795,6 +4873,7 @@ exports.DrawableGroup = DrawableGroup; exports.LineTrack = LineTrack; exports.FeatureTrack = FeatureTrack; +exports.DiagonalHeatmapTrack = DiagonalHeatmapTrack; exports.ReadTrack = ReadTrack; exports.VcfTrack = VcfTrack; exports.CompositeTrack = CompositeTrack; @@ -5963,12 +6042,224 @@ } }); +// Color stuff from less.js + +var Color = function (rgb, a) { + /** + * The end goal here, is to parse the arguments + * into an integer triplet, such as `128, 255, 0` + * + * This facilitates operations and conversions. + */ + if (Array.isArray(rgb)) { + this.rgb = rgb; + } else if (rgb.length == 6) { + this.rgb = rgb.match(/.{2}/g).map(function (c) { + return parseInt(c, 16); + }); + } else { + this.rgb = rgb.split('').map(function (c) { + return parseInt(c + c, 16); + }); + } + this.alpha = typeof(a) === 'number' ? a : 1; +}; +Color.prototype = { + eval: function () { return this }, + + // + // If we have some transparency, the only way to represent it + // is via `rgba`. Otherwise, we use the hex representation, + // which has better compatibility with older browsers. + // Values are capped between `0` and `255`, rounded and zero-padded. + // + toCSS: function () { + if (this.alpha < 1.0) { + return "rgba(" + this.rgb.map(function (c) { + return Math.round(c); + }).concat(this.alpha).join(', ') + ")"; + } else { + return '#' + this.rgb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + } + }, + + toHSL: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2, d = max - min; + + if (max === min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h * 360, s: s, l: l, a: a }; + }, + + toARGB: function () { + var argb = [Math.round(this.alpha * 255)].concat(this.rgb); + return '#' + argb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + + mix: function (color2, weight) { + color1 = this; + + var p = weight; // .value / 100.0; + var w = p * 2 - 1; + var a = color1.toHSL().a - color2.toHSL().a; + + var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, + color1.rgb[1] * w1 + color2.rgb[1] * w2, + color1.rgb[2] * w1 + color2.rgb[2] * w2]; + + var alpha = color1.alpha * p + color2.alpha * (1 - p); + + return new Color(rgb, alpha); + } +}; + + +// End colors from less.js + +var LinearRamp = function( start_color, end_color, start_value, end_value ) { + /** + * Simple linear gradient + */ + this.start_color = new Color( start_color ); + this.end_color = new Color( end_color ); + this.start_value = start_value; + this.end_value = end_value; + this.value_range = end_value - start_value; +} +LinearRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + value = ( value - this.start_value ) / this.value_range; + // HACK: just red for now + // return "hsl(0,100%," + (value * 100) + "%)" + return this.start_color.mix( this.end_color, 1 - value ).toCSS(); +} + +var SplitRamp = function( start_color, middle_color, end_color, start_value, end_value ) { + /** + * Two gradients split away from 0 + */ + this.positive_ramp = new LinearRamp( middle_color, end_color, 0, end_value ); + this.negative_ramp = new LinearRamp( middle_color, start_color, 0, -start_value ); + this.start_value = start_value; + this.end_value = end_value; +} +SplitRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + if ( value >= 0 ) { + return this.positive_ramp.map_value( value ) + } else { + return this.negative_ramp.map_value( -value ) + } +} + +var DiagonalHeatmapPainter = function(data, view_start, view_end, prefs, mode) { + Painter.call( this, data, view_start, view_end, prefs, mode ); + if ( this.prefs.min_value === undefined ) { + var min_value = Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + min_value = Math.min( min_value, this.data[i][5] ); + } + this.prefs.min_value = min_value; + } + if ( this.prefs.max_value === undefined ) { + var max_value = -Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + max_value = Math.max( max_value, this.data[i][5] ); + } + this.prefs.max_value = max_value; + } +}; + +DiagonalHeatmapPainter.prototype.default_prefs = { + min_value: undefined, + max_value: undefined, + mode: "Heatmap", + pos_color: "4169E1", + neg_color: "FF8C00" +}; + +DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) { + var + min_value = this.prefs.min_value, + max_value = this.prefs.max_value, + value_range = max_value - min_value, + height_px = height, + view_start = this.view_start, + view_range = this.view_end - this.view_start, + mode = this.mode, + data = this.data, + invsqrt2 = 1 / Math.sqrt(2); + + var ramp = ( new SplitRamp( this.prefs.neg_color, "FFFFFF", this.prefs.pos_color, min_value, max_value ) ); + + var d, s1, e1, s2, e2, value; + + var scale = function( p ) { return ( p - view_start ) * w_scale }; + + ctx.save(); + + // Draw into triangle, then rotate and scale + ctx.rotate(-45 * Math.PI / 180); + ctx.scale( invsqrt2, invsqrt2 ); + + // Paint track. + for (var i = 0, len = data.length; i < len; i++) { + + d = data[i]; + + // Ensure the cell is visible + // if ( ) + + s1 = scale( d[1] ); + e1 = scale( d[2] ); + s2 = scale( d[4] ); + e2 = scale( d[5] ); + value = d[6]; + + ctx.fillStyle = ( ramp.map_value( value ) ) + + ctx.fillRect( s1, s2, ( e1 - s1 ), ( e2 - s2 ) ); + } + + ctx.restore(); +}; + exports.Scaler = Scaler; exports.SummaryTreePainter = SummaryTreePainter; exports.LinePainter = LinePainter; exports.LinkedFeaturePainter = LinkedFeaturePainter; exports.ReadPainter = ReadPainter; exports.ArcLinkedFeaturePainter = ArcLinkedFeaturePainter; +exports.DiagonalHeatmapPainter = DiagonalHeatmapPainter; // End painters_module encapsulation }; diff -r f6a710440c0500fb09e980300419b53b2cda6088 -r d8ab6ffb8acb51791e4a3b425ccb0788591ebd86 static/scripts/viz/trackster_ui.js --- a/static/scripts/viz/trackster_ui.js +++ b/static/scripts/viz/trackster_ui.js @@ -61,6 +61,7 @@ "FeatureTrack": FeatureTrack, "VcfTrack": VcfTrack, "ReadTrack": ReadTrack, + // "DiagonalHeatmapTrack": DiagonalHeatmapTrack, "CompositeTrack": CompositeTrack, "DrawableGroup": DrawableGroup }; https://bitbucket.org/galaxy/galaxy-central/changeset/e42eb412c9bd/ changeset: e42eb412c9bd user: james_taylor date: 2012-07-19 00:22:52 summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central affected #: 5 files diff -r 891dd09be1614b2138f61d83b4d3feb86cc8f6c7 -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 lib/galaxy/datatypes/interval.py --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -1298,7 +1298,32 @@ return False def get_track_type( self ): - return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + +class ChromatinInteractions( Interval ): + ''' + Chromatin interactions obtained from 3C/5C/Hi-C experiments. + ''' + + file_ext = "chrint" + + column_names = [ 'Chrom', 'Start1', 'End1', 'Start2', 'End2', 'Value' ] + + """Add metadata elements""" + MetadataElement( name="chromCol", default=1, desc="Chrom column", param=metadata.ColumnParameter ) + MetadataElement( name="start1Col", default=2, desc="Start1 column", param=metadata.ColumnParameter ) + MetadataElement( name="end1Col", default=3, desc="End1 column", param=metadata.ColumnParameter ) + MetadataElement( name="start2Col", default=2, desc="Start2 column", param=metadata.ColumnParameter ) + MetadataElement( name="end2Col", default=3, desc="End2 column", param=metadata.ColumnParameter ) + MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True, visible=False ) + + def sniff( self, filename ): + return False + + def get_track_type( self ): + return "DiagonalHeatmapTrack", {"data": "tabix", "index": "summary_tree"} + + if __name__ == '__main__': import doctest, sys diff -r 891dd09be1614b2138f61d83b4d3feb86cc8f6c7 -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 lib/galaxy/visualization/tracks/data_providers.py --- a/lib/galaxy/visualization/tracks/data_providers.py +++ b/lib/galaxy/visualization/tracks/data_providers.py @@ -19,7 +19,7 @@ from galaxy.visualization.tracks.summary import * import galaxy_utils.sequence.vcf from galaxy.datatypes.tabular import Vcf -from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak +from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak, ChromatinInteractions from pysam import csamtools, ctabix @@ -146,6 +146,9 @@ { 'name' : attrs[ 'name' ], 'type' : column_types[viz_col_index], \ 'index' : attrs[ 'index' ] } ) return filters + + def get_default_max_vals( self ): + return 5000; # # -- Base mixins and providers -- @@ -1268,6 +1271,67 @@ 'tool_id': 'Filter1', 'tool_exp_name': 'c9' } ) return filters + +# +# -- ChromatinInteraction data providers -- +# +class ChromatinInteractionsDataProvider( TracksDataProvider ): + def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ): + """ + Provides + """ + + rval = [] + message = None + for count, line in enumerate( iterator ): + if count < start_val: + continue + if max_vals and count-start_val >= max_vals: + message = ERROR_MAX_VALS % ( max_vals, "interactions" ) + break + + feature = line.split() + length = len( feature ) + + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3], + s2 = int( feature[4] ), + e2 = int( feature[5] ), + v = float( feature[6] ) + + # Feature initialization. + payload = [ + # GUID is just a hash of the line + hash( line ), + # Add start1, end1, chr2, start2, end2, value. + s1, e1, c, s2, e2, v + ] + + rval.append( payload ) + + return { 'data': rval, 'message': message } + + def get_default_max_vals( self ): + return 50000; + +class ChromatinInteractionsTabixDataProvider( TabixDataProvider, ChromatinInteractionsDataProvider ): + def get_iterator( self, chrom, start, end ): + """ + """ + # Modify start as needed to get earlier interactions with start region. + start = max( 0, int( start) - 1000000 ) + def filter( iter ): + for line in iter: + feature = line.split() + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3] + s2 = int( feature[4] ), + e2 = int( feature[5] ), + if ( ( c == chrom ) and ( s1 < end and e1 > start ) and ( s2 < end and e2 > start ) ): + yield line + return filter( TabixDataProvider.get_iterator( self, chrom, start, end ) ) # # -- Helper methods. -- @@ -1283,7 +1347,9 @@ Gtf: GtfTabixDataProvider, ENCODEPeak: ENCODEPeakTabixDataProvider, Interval: IntervalTabixDataProvider, - "default" : TabixDataProvider }, + ChromatinInteractions: ChromatinInteractionsTabixDataProvider, + "default" : TabixDataProvider + }, "interval_index": IntervalIndexDataProvider, "bai": BamDataProvider, "bam": SamDataProvider, diff -r 891dd09be1614b2138f61d83b4d3feb86cc8f6c7 -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -345,7 +345,7 @@ return { "status": messages.DATA, "valid_chroms": valid_chroms } @web.json - def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=5000, **kwargs ): + def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=None, **kwargs ): """ Provides a block of data from a dataset. """ @@ -401,6 +401,10 @@ deps = dataset.get_converted_dataset_deps( trans, tracks_dataset_type ) data_provider = data_provider_class( converted_dataset=converted_dataset, original_dataset=dataset, dependencies=deps ) + # Allow max_vals top be data provider set if not passed + if max_vals is None: + max_vals = data_provider.get_default_max_vals() + # Get and return data from data_provider. result = data_provider.get_data( chrom, low, high, int( start_val ), int( max_vals ), **kwargs ) result.update( { 'dataset_type': tracks_dataset_type, 'extra_info': extra_info } ) diff -r 891dd09be1614b2138f61d83b4d3feb86cc8f6c7 -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 static/scripts/viz/trackster.js --- a/static/scripts/viz/trackster.js +++ b/static/scripts/viz/trackster.js @@ -4346,6 +4346,84 @@ } }); +var DiagonalHeatmapTrack = function (view, container, obj_dict) { + var track = this; + this.display_modes = ["Heatmap"]; + this.mode = "Heatmap"; + TiledTrack.call(this, view, container, obj_dict); + + // This all seems to be duplicated + this.hda_ldda = obj_dict.hda_ldda; + this.dataset_id = obj_dict.dataset_id; + this.original_dataset_id = this.dataset_id; + this.left_offset = 0; + + // Define track configuration + this.config = new DrawableConfig( { + track: this, + params: [ + { key: 'name', label: 'Name', type: 'text', default_value: this.name }, + { key: 'pos_color', label: 'Positive Color', type: 'color', default_value: "4169E1" }, + { key: 'negative_color', label: 'Negative Color', type: 'color', default_value: "FF8C00" }, + { key: 'min_value', label: 'Min Value', type: 'float', default_value: 0 }, + { key: 'max_value', label: 'Max Value', type: 'float', default_value: 1 }, + { key: 'mode', type: 'string', default_value: this.mode, hidden: true }, + { key: 'height', type: 'int', default_value: 500, hidden: true } + ], + saved_values: obj_dict.prefs, + onchange: function() { + track.set_name(track.prefs.name); + track.vertical_range = track.prefs.max_value - track.prefs.min_value; + track.set_min_value(track.prefs.min_value); + track.set_max_value(track.prefs.max_value); + } + }); + + this.prefs = this.config.values; + this.visible_height_px = this.config.values.height; + this.vertical_range = this.config.values.max_value - this.config.values.min_value; +}; +extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype, { + /** + * Action to take during resize. + */ + on_resize: function() { + this.request_draw(true); + }, + /** + * Set track minimum value. + */ + set_min_value: function(new_val) { + this.prefs.min_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + /** + * Set track maximum value. + */ + set_max_value: function(new_val) { + this.prefs.max_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + + /** + * Draw LineTrack tile. + */ + draw_tile: function(result, ctx, mode, resolution, tile_index, w_scale) { + // Paint onto canvas. + var + canvas = ctx.canvas, + tile_bounds = this._get_tile_bounds(tile_index, resolution), + tile_low = tile_bounds[0], + tile_high = tile_bounds[1], + painter = new painters.DiagonalHeatmapPainter(result.data, tile_low, tile_high, this.prefs, mode); + painter.draw(ctx, canvas.width, canvas.height, w_scale); + + return new Tile(this, tile_index, resolution, canvas, result.data); + } +}); + var FeatureTrack = function(view, container, obj_dict) { // // Preinitialization: do things that need to be done before calling Track and TiledTrack @@ -4878,6 +4956,7 @@ exports.DrawableGroup = DrawableGroup; exports.LineTrack = LineTrack; exports.FeatureTrack = FeatureTrack; +exports.DiagonalHeatmapTrack = DiagonalHeatmapTrack; exports.ReadTrack = ReadTrack; exports.VcfTrack = VcfTrack; exports.CompositeTrack = CompositeTrack; @@ -6041,12 +6120,224 @@ } }); +// Color stuff from less.js + +var Color = function (rgb, a) { + /** + * The end goal here, is to parse the arguments + * into an integer triplet, such as `128, 255, 0` + * + * This facilitates operations and conversions. + */ + if (Array.isArray(rgb)) { + this.rgb = rgb; + } else if (rgb.length == 6) { + this.rgb = rgb.match(/.{2}/g).map(function (c) { + return parseInt(c, 16); + }); + } else { + this.rgb = rgb.split('').map(function (c) { + return parseInt(c + c, 16); + }); + } + this.alpha = typeof(a) === 'number' ? a : 1; +}; +Color.prototype = { + eval: function () { return this }, + + // + // If we have some transparency, the only way to represent it + // is via `rgba`. Otherwise, we use the hex representation, + // which has better compatibility with older browsers. + // Values are capped between `0` and `255`, rounded and zero-padded. + // + toCSS: function () { + if (this.alpha < 1.0) { + return "rgba(" + this.rgb.map(function (c) { + return Math.round(c); + }).concat(this.alpha).join(', ') + ")"; + } else { + return '#' + this.rgb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + } + }, + + toHSL: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2, d = max - min; + + if (max === min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h * 360, s: s, l: l, a: a }; + }, + + toARGB: function () { + var argb = [Math.round(this.alpha * 255)].concat(this.rgb); + return '#' + argb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + + mix: function (color2, weight) { + color1 = this; + + var p = weight; // .value / 100.0; + var w = p * 2 - 1; + var a = color1.toHSL().a - color2.toHSL().a; + + var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, + color1.rgb[1] * w1 + color2.rgb[1] * w2, + color1.rgb[2] * w1 + color2.rgb[2] * w2]; + + var alpha = color1.alpha * p + color2.alpha * (1 - p); + + return new Color(rgb, alpha); + } +}; + + +// End colors from less.js + +var LinearRamp = function( start_color, end_color, start_value, end_value ) { + /** + * Simple linear gradient + */ + this.start_color = new Color( start_color ); + this.end_color = new Color( end_color ); + this.start_value = start_value; + this.end_value = end_value; + this.value_range = end_value - start_value; +} +LinearRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + value = ( value - this.start_value ) / this.value_range; + // HACK: just red for now + // return "hsl(0,100%," + (value * 100) + "%)" + return this.start_color.mix( this.end_color, 1 - value ).toCSS(); +} + +var SplitRamp = function( start_color, middle_color, end_color, start_value, end_value ) { + /** + * Two gradients split away from 0 + */ + this.positive_ramp = new LinearRamp( middle_color, end_color, 0, end_value ); + this.negative_ramp = new LinearRamp( middle_color, start_color, 0, -start_value ); + this.start_value = start_value; + this.end_value = end_value; +} +SplitRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + if ( value >= 0 ) { + return this.positive_ramp.map_value( value ) + } else { + return this.negative_ramp.map_value( -value ) + } +} + +var DiagonalHeatmapPainter = function(data, view_start, view_end, prefs, mode) { + Painter.call( this, data, view_start, view_end, prefs, mode ); + if ( this.prefs.min_value === undefined ) { + var min_value = Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + min_value = Math.min( min_value, this.data[i][5] ); + } + this.prefs.min_value = min_value; + } + if ( this.prefs.max_value === undefined ) { + var max_value = -Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + max_value = Math.max( max_value, this.data[i][5] ); + } + this.prefs.max_value = max_value; + } +}; + +DiagonalHeatmapPainter.prototype.default_prefs = { + min_value: undefined, + max_value: undefined, + mode: "Heatmap", + pos_color: "4169E1", + neg_color: "FF8C00" +}; + +DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) { + var + min_value = this.prefs.min_value, + max_value = this.prefs.max_value, + value_range = max_value - min_value, + height_px = height, + view_start = this.view_start, + view_range = this.view_end - this.view_start, + mode = this.mode, + data = this.data, + invsqrt2 = 1 / Math.sqrt(2); + + var ramp = ( new SplitRamp( this.prefs.neg_color, "FFFFFF", this.prefs.pos_color, min_value, max_value ) ); + + var d, s1, e1, s2, e2, value; + + var scale = function( p ) { return ( p - view_start ) * w_scale }; + + ctx.save(); + + // Draw into triangle, then rotate and scale + ctx.rotate(-45 * Math.PI / 180); + ctx.scale( invsqrt2, invsqrt2 ); + + // Paint track. + for (var i = 0, len = data.length; i < len; i++) { + + d = data[i]; + + // Ensure the cell is visible + // if ( ) + + s1 = scale( d[1] ); + e1 = scale( d[2] ); + s2 = scale( d[4] ); + e2 = scale( d[5] ); + value = d[6]; + + ctx.fillStyle = ( ramp.map_value( value ) ) + + ctx.fillRect( s1, s2, ( e1 - s1 ), ( e2 - s2 ) ); + } + + ctx.restore(); +}; + exports.Scaler = Scaler; exports.SummaryTreePainter = SummaryTreePainter; exports.LinePainter = LinePainter; exports.LinkedFeaturePainter = LinkedFeaturePainter; exports.ReadPainter = ReadPainter; exports.ArcLinkedFeaturePainter = ArcLinkedFeaturePainter; +exports.DiagonalHeatmapPainter = DiagonalHeatmapPainter; // End painters_module encapsulation }; diff -r 891dd09be1614b2138f61d83b4d3feb86cc8f6c7 -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 static/scripts/viz/trackster_ui.js --- a/static/scripts/viz/trackster_ui.js +++ b/static/scripts/viz/trackster_ui.js @@ -67,6 +67,7 @@ "FeatureTrack": FeatureTrack, "VcfTrack": VcfTrack, "ReadTrack": ReadTrack, + // "DiagonalHeatmapTrack": DiagonalHeatmapTrack, "CompositeTrack": CompositeTrack, "DrawableGroup": DrawableGroup }; https://bitbucket.org/galaxy/galaxy-central/changeset/c3a4c579e439/ changeset: c3a4c579e439 user: james_taylor date: 2012-07-19 19:55:48 summary: Add a message to the root index page for browsers without javascript. affected #: 4 files diff -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 -r c3a4c579e439ff067789986deb1436857b039399 static/june_2007_style/base.less --- a/static/june_2007_style/base.less +++ b/static/june_2007_style/base.less @@ -289,13 +289,13 @@ } } -#overlay { +#overlay, .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 20000; } -#overlay.is_modal #overlay-background { +#overlay.is_modal #overlay-background, .overlay-background { background: rgba(0,0,0,0.5); } diff -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 -r c3a4c579e439ff067789986deb1436857b039399 static/june_2007_style/blue/base.css --- a/static/june_2007_style/blue/base.css +++ b/static/june_2007_style/blue/base.css @@ -225,7 +225,7 @@ .navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} .navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} .navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Lucida Grande",verdana,arial,helvetica,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;color:rgba(255, 255, 255, 0.75);background:#666;background:rgba(255, 255, 255, 0.3);border:1px solid #111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query :-moz-placeholder{color:#eeeeee;} -.navbar-search .search-query ::-webkit-input-placeholder{color:#eeeeee;} +.navbar-search .search-query::-webkit-input-placeholder{color:#eeeeee;} .navbar-search .search-query:hover{color:#ffffff;background-color:#999999;background-color:rgba(255, 255, 255, 0.5);} .navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} .navbar-fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030;} @@ -516,8 +516,8 @@ .panel-header-button{color:#333;text-decoration:none;display:inline-block;cursor:pointer;margin:-1px;padding:1px;margin-top:-0.2em;padding-right:0.5em;padding-left:0.5em;}.panel-header-button:hover{color:maroon;-webkit-transition:color 0.25s linear;-moz-transition:color 0.25s linear;-ms-transition:color 0.25s linear;-o-transition:color 0.25s linear;transition:color 0.25s linear;} .panel-header-button .caret{margin-top:7px;} .panel-header-button.popup{padding-right:1.75em;background:url(../images/dropdownarrow.png) no-repeat right 7px;} -#overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:20000;} -#overlay.is_modal #overlay-background{background:rgba(0, 0, 0, 0.5);} +#overlay,.overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:20000;} +#overlay.is_modal #overlay-background,.overlay-background{background:rgba(0, 0, 0, 0.5);} .dialog-box-container{position:relative;margin-top:80px;margin-right:auto;margin-left:auto;} .dialog-box-wrapper{position:relative;padding:1em;background-color:rgba(0, 0, 0, 0.5);-moz-border-radius:1em;-webkit-border-radius:1em;} #dialog-box.dialog-box{min-width:660px;margin:-250px 0 0 -330px;} diff -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 -r c3a4c579e439ff067789986deb1436857b039399 templates/base_panels.mako --- a/templates/base_panels.mako +++ b/templates/base_panels.mako @@ -8,6 +8,7 @@ self.message_box_class="" self.active_view=None self.body_class="" + self.require_javascript=False %><%def name="init()"> @@ -47,7 +48,7 @@ <!--[if lt IE 7]> ${h.js( 'IE7', 'ie7-recalc' )} <![endif]--> - ${h.js( 'jquery', 'libs/underscore', 'libs/backbone', 'libs/backbone-relational', 'libs/handlebars.runtime', 'mvc/ui' )} + ${h.js( 'modernizr', 'jquery', 'libs/underscore', 'libs/backbone', 'libs/backbone-relational', 'libs/handlebars.runtime', 'mvc/ui' )} <script type="text/javascript"> // Set up needed paths. var galaxy_paths = new GalaxyPaths({ @@ -233,6 +234,16 @@ </head><body scroll="no" class="${self.body_class}"> + %if self.require_javascript: + <noscript> + <div class="overlay overlay-background"> + <div class="modal dialog-box" border="0"> + <div class="modal-header"><h3 class="title">Javascript Required</h3></div> + <div class="modal-body">The Galaxy analysis interface requires a browser with Javascript enabled. <br> Please enable Javascript and refresh this page</div> + </div> + </div> + </noscript> + %endif <div id="everything" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; min-width: 600px;"> ## Background displays first <div id="background"></div> diff -r e42eb412c9bd76aa1e4e250ea76c82e680c4f7c4 -r c3a4c579e439ff067789986deb1436857b039399 templates/root/index.mako --- a/templates/root/index.mako +++ b/templates/root/index.mako @@ -188,6 +188,7 @@ self.has_left_panel = True self.has_right_panel = True self.active_view = "analysis" + self.require_javascript = True %> %if trans.app.config.require_login and not trans.user: <script type="text/javascript"> https://bitbucket.org/galaxy/galaxy-central/changeset/724c07bf72ef/ changeset: 724c07bf72ef user: james_taylor date: 2012-07-19 19:56:48 summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central affected #: 9 files diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 lib/galaxy/datatypes/interval.py --- a/lib/galaxy/datatypes/interval.py +++ b/lib/galaxy/datatypes/interval.py @@ -1298,7 +1298,32 @@ return False def get_track_type( self ): - return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + return "FeatureTrack", {"data": "tabix", "index": "summary_tree"} + +class ChromatinInteractions( Interval ): + ''' + Chromatin interactions obtained from 3C/5C/Hi-C experiments. + ''' + + file_ext = "chrint" + + column_names = [ 'Chrom', 'Start1', 'End1', 'Start2', 'End2', 'Value' ] + + """Add metadata elements""" + MetadataElement( name="chromCol", default=1, desc="Chrom column", param=metadata.ColumnParameter ) + MetadataElement( name="start1Col", default=2, desc="Start1 column", param=metadata.ColumnParameter ) + MetadataElement( name="end1Col", default=3, desc="End1 column", param=metadata.ColumnParameter ) + MetadataElement( name="start2Col", default=2, desc="Start2 column", param=metadata.ColumnParameter ) + MetadataElement( name="end2Col", default=3, desc="End2 column", param=metadata.ColumnParameter ) + MetadataElement( name="columns", default=3, desc="Number of columns", readonly=True, visible=False ) + + def sniff( self, filename ): + return False + + def get_track_type( self ): + return "DiagonalHeatmapTrack", {"data": "tabix", "index": "summary_tree"} + + if __name__ == '__main__': import doctest, sys diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 lib/galaxy/visualization/tracks/data_providers.py --- a/lib/galaxy/visualization/tracks/data_providers.py +++ b/lib/galaxy/visualization/tracks/data_providers.py @@ -19,7 +19,7 @@ from galaxy.visualization.tracks.summary import * import galaxy_utils.sequence.vcf from galaxy.datatypes.tabular import Vcf -from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak +from galaxy.datatypes.interval import Interval, Bed, Gff, Gtf, ENCODEPeak, ChromatinInteractions from pysam import csamtools, ctabix @@ -146,6 +146,9 @@ { 'name' : attrs[ 'name' ], 'type' : column_types[viz_col_index], \ 'index' : attrs[ 'index' ] } ) return filters + + def get_default_max_vals( self ): + return 5000; # # -- Base mixins and providers -- @@ -1272,6 +1275,67 @@ 'tool_id': 'Filter1', 'tool_exp_name': 'c9' } ) return filters + +# +# -- ChromatinInteraction data providers -- +# +class ChromatinInteractionsDataProvider( TracksDataProvider ): + def process_data( self, iterator, start_val=0, max_vals=None, **kwargs ): + """ + Provides + """ + + rval = [] + message = None + for count, line in enumerate( iterator ): + if count < start_val: + continue + if max_vals and count-start_val >= max_vals: + message = ERROR_MAX_VALS % ( max_vals, "interactions" ) + break + + feature = line.split() + length = len( feature ) + + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3], + s2 = int( feature[4] ), + e2 = int( feature[5] ), + v = float( feature[6] ) + + # Feature initialization. + payload = [ + # GUID is just a hash of the line + hash( line ), + # Add start1, end1, chr2, start2, end2, value. + s1, e1, c, s2, e2, v + ] + + rval.append( payload ) + + return { 'data': rval, 'message': message } + + def get_default_max_vals( self ): + return 50000; + +class ChromatinInteractionsTabixDataProvider( TabixDataProvider, ChromatinInteractionsDataProvider ): + def get_iterator( self, chrom, start, end ): + """ + """ + # Modify start as needed to get earlier interactions with start region. + start = max( 0, int( start) - 1000000 ) + def filter( iter ): + for line in iter: + feature = line.split() + s1 = int( feature[1] ), + e1 = int( feature[2] ), + c = feature[3] + s2 = int( feature[4] ), + e2 = int( feature[5] ), + if ( ( c == chrom ) and ( s1 < end and e1 > start ) and ( s2 < end and e2 > start ) ): + yield line + return filter( TabixDataProvider.get_iterator( self, chrom, start, end ) ) # # -- Helper methods. -- @@ -1287,7 +1351,9 @@ Gtf: GtfTabixDataProvider, ENCODEPeak: ENCODEPeakTabixDataProvider, Interval: IntervalTabixDataProvider, - "default" : TabixDataProvider }, + ChromatinInteractions: ChromatinInteractionsTabixDataProvider, + "default" : TabixDataProvider + }, "interval_index": IntervalIndexDataProvider, "bai": BamDataProvider, "bam": SamDataProvider, diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py +++ b/lib/galaxy/web/controllers/tracks.py @@ -347,7 +347,7 @@ return { "status": messages.DATA, "valid_chroms": valid_chroms } @web.json - def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=5000, **kwargs ): + def data( self, trans, hda_ldda, dataset_id, chrom, low, high, start_val=0, max_vals=None, **kwargs ): """ Provides a block of data from a dataset. """ @@ -403,6 +403,10 @@ deps = dataset.get_converted_dataset_deps( trans, tracks_dataset_type ) data_provider = data_provider_class( converted_dataset=converted_dataset, original_dataset=dataset, dependencies=deps ) + # Allow max_vals top be data provider set if not passed + if max_vals is None: + max_vals = data_provider.get_default_max_vals() + # Get and return data from data_provider. result = data_provider.get_data( chrom, int( low ), int( high ), int( start_val ), int( max_vals ), **kwargs ) result.update( { 'dataset_type': tracks_dataset_type, 'extra_info': extra_info } ) diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 static/june_2007_style/base.less --- a/static/june_2007_style/base.less +++ b/static/june_2007_style/base.less @@ -289,13 +289,13 @@ } } -#overlay { +#overlay, .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 20000; } -#overlay.is_modal #overlay-background { +#overlay.is_modal #overlay-background, .overlay-background { background: rgba(0,0,0,0.5); } diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 static/june_2007_style/blue/base.css --- a/static/june_2007_style/blue/base.css +++ b/static/june_2007_style/blue/base.css @@ -225,7 +225,7 @@ .navbar-form .radio,.navbar-form .checkbox{margin-top:5px;} .navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;} .navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.navbar-search .search-query{padding:4px 9px;font-family:"Lucida Grande",verdana,arial,helvetica,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;color:rgba(255, 255, 255, 0.75);background:#666;background:rgba(255, 255, 255, 0.3);border:1px solid #111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query :-moz-placeholder{color:#eeeeee;} -.navbar-search .search-query ::-webkit-input-placeholder{color:#eeeeee;} +.navbar-search .search-query::-webkit-input-placeholder{color:#eeeeee;} .navbar-search .search-query:hover{color:#ffffff;background-color:#999999;background-color:rgba(255, 255, 255, 0.5);} .navbar-search .search-query:focus,.navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;} .navbar-fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030;} @@ -516,8 +516,8 @@ .panel-header-button{color:#333;text-decoration:none;display:inline-block;cursor:pointer;margin:-1px;padding:1px;margin-top:-0.2em;padding-right:0.5em;padding-left:0.5em;}.panel-header-button:hover{color:maroon;-webkit-transition:color 0.25s linear;-moz-transition:color 0.25s linear;-ms-transition:color 0.25s linear;-o-transition:color 0.25s linear;transition:color 0.25s linear;} .panel-header-button .caret{margin-top:7px;} .panel-header-button.popup{padding-right:1.75em;background:url(../images/dropdownarrow.png) no-repeat right 7px;} -#overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:20000;} -#overlay.is_modal #overlay-background{background:rgba(0, 0, 0, 0.5);} +#overlay,.overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:20000;} +#overlay.is_modal #overlay-background,.overlay-background{background:rgba(0, 0, 0, 0.5);} .dialog-box-container{position:relative;margin-top:80px;margin-right:auto;margin-left:auto;} .dialog-box-wrapper{position:relative;padding:1em;background-color:rgba(0, 0, 0, 0.5);-moz-border-radius:1em;-webkit-border-radius:1em;} #dialog-box.dialog-box{min-width:660px;margin:-250px 0 0 -330px;} diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 static/scripts/viz/trackster.js --- a/static/scripts/viz/trackster.js +++ b/static/scripts/viz/trackster.js @@ -4345,6 +4345,84 @@ } }); +var DiagonalHeatmapTrack = function (view, container, obj_dict) { + var track = this; + this.display_modes = ["Heatmap"]; + this.mode = "Heatmap"; + TiledTrack.call(this, view, container, obj_dict); + + // This all seems to be duplicated + this.hda_ldda = obj_dict.hda_ldda; + this.dataset_id = obj_dict.dataset_id; + this.original_dataset_id = this.dataset_id; + this.left_offset = 0; + + // Define track configuration + this.config = new DrawableConfig( { + track: this, + params: [ + { key: 'name', label: 'Name', type: 'text', default_value: this.name }, + { key: 'pos_color', label: 'Positive Color', type: 'color', default_value: "4169E1" }, + { key: 'negative_color', label: 'Negative Color', type: 'color', default_value: "FF8C00" }, + { key: 'min_value', label: 'Min Value', type: 'float', default_value: 0 }, + { key: 'max_value', label: 'Max Value', type: 'float', default_value: 1 }, + { key: 'mode', type: 'string', default_value: this.mode, hidden: true }, + { key: 'height', type: 'int', default_value: 500, hidden: true } + ], + saved_values: obj_dict.prefs, + onchange: function() { + track.set_name(track.prefs.name); + track.vertical_range = track.prefs.max_value - track.prefs.min_value; + track.set_min_value(track.prefs.min_value); + track.set_max_value(track.prefs.max_value); + } + }); + + this.prefs = this.config.values; + this.visible_height_px = this.config.values.height; + this.vertical_range = this.config.values.max_value - this.config.values.min_value; +}; +extend(DiagonalHeatmapTrack.prototype, Drawable.prototype, TiledTrack.prototype, { + /** + * Action to take during resize. + */ + on_resize: function() { + this.request_draw(true); + }, + /** + * Set track minimum value. + */ + set_min_value: function(new_val) { + this.prefs.min_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + /** + * Set track maximum value. + */ + set_max_value: function(new_val) { + this.prefs.max_value = new_val; + this.tile_cache.clear(); + this.request_draw(); + }, + + /** + * Draw LineTrack tile. + */ + draw_tile: function(result, ctx, mode, resolution, tile_index, w_scale) { + // Paint onto canvas. + var + canvas = ctx.canvas, + tile_bounds = this._get_tile_bounds(tile_index, resolution), + tile_low = tile_bounds[0], + tile_high = tile_bounds[1], + painter = new painters.DiagonalHeatmapPainter(result.data, tile_low, tile_high, this.prefs, mode); + painter.draw(ctx, canvas.width, canvas.height, w_scale); + + return new Tile(this, tile_index, resolution, canvas, result.data); + } +}); + var FeatureTrack = function(view, container, obj_dict) { // // Preinitialization: do things that need to be done before calling Track and TiledTrack @@ -4877,6 +4955,7 @@ exports.DrawableGroup = DrawableGroup; exports.LineTrack = LineTrack; exports.FeatureTrack = FeatureTrack; +exports.DiagonalHeatmapTrack = DiagonalHeatmapTrack; exports.ReadTrack = ReadTrack; exports.VcfTrack = VcfTrack; exports.CompositeTrack = CompositeTrack; @@ -6040,12 +6119,224 @@ } }); +// Color stuff from less.js + +var Color = function (rgb, a) { + /** + * The end goal here, is to parse the arguments + * into an integer triplet, such as `128, 255, 0` + * + * This facilitates operations and conversions. + */ + if (Array.isArray(rgb)) { + this.rgb = rgb; + } else if (rgb.length == 6) { + this.rgb = rgb.match(/.{2}/g).map(function (c) { + return parseInt(c, 16); + }); + } else { + this.rgb = rgb.split('').map(function (c) { + return parseInt(c + c, 16); + }); + } + this.alpha = typeof(a) === 'number' ? a : 1; +}; +Color.prototype = { + eval: function () { return this }, + + // + // If we have some transparency, the only way to represent it + // is via `rgba`. Otherwise, we use the hex representation, + // which has better compatibility with older browsers. + // Values are capped between `0` and `255`, rounded and zero-padded. + // + toCSS: function () { + if (this.alpha < 1.0) { + return "rgba(" + this.rgb.map(function (c) { + return Math.round(c); + }).concat(this.alpha).join(', ') + ")"; + } else { + return '#' + this.rgb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + } + }, + + toHSL: function () { + var r = this.rgb[0] / 255, + g = this.rgb[1] / 255, + b = this.rgb[2] / 255, + a = this.alpha; + + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2, d = max - min; + + if (max === min) { + h = s = 0; + } else { + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + + switch (max) { + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + return { h: h * 360, s: s, l: l, a: a }; + }, + + toARGB: function () { + var argb = [Math.round(this.alpha * 255)].concat(this.rgb); + return '#' + argb.map(function (i) { + i = Math.round(i); + i = (i > 255 ? 255 : (i < 0 ? 0 : i)).toString(16); + return i.length === 1 ? '0' + i : i; + }).join(''); + }, + + mix: function (color2, weight) { + color1 = this; + + var p = weight; // .value / 100.0; + var w = p * 2 - 1; + var a = color1.toHSL().a - color2.toHSL().a; + + var w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + var rgb = [color1.rgb[0] * w1 + color2.rgb[0] * w2, + color1.rgb[1] * w1 + color2.rgb[1] * w2, + color1.rgb[2] * w1 + color2.rgb[2] * w2]; + + var alpha = color1.alpha * p + color2.alpha * (1 - p); + + return new Color(rgb, alpha); + } +}; + + +// End colors from less.js + +var LinearRamp = function( start_color, end_color, start_value, end_value ) { + /** + * Simple linear gradient + */ + this.start_color = new Color( start_color ); + this.end_color = new Color( end_color ); + this.start_value = start_value; + this.end_value = end_value; + this.value_range = end_value - start_value; +} +LinearRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + value = ( value - this.start_value ) / this.value_range; + // HACK: just red for now + // return "hsl(0,100%," + (value * 100) + "%)" + return this.start_color.mix( this.end_color, 1 - value ).toCSS(); +} + +var SplitRamp = function( start_color, middle_color, end_color, start_value, end_value ) { + /** + * Two gradients split away from 0 + */ + this.positive_ramp = new LinearRamp( middle_color, end_color, 0, end_value ); + this.negative_ramp = new LinearRamp( middle_color, start_color, 0, -start_value ); + this.start_value = start_value; + this.end_value = end_value; +} +SplitRamp.prototype.map_value = function( value ) { + value = Math.max( value, this.start_value ); + value = Math.min( value, this.end_value ); + if ( value >= 0 ) { + return this.positive_ramp.map_value( value ) + } else { + return this.negative_ramp.map_value( -value ) + } +} + +var DiagonalHeatmapPainter = function(data, view_start, view_end, prefs, mode) { + Painter.call( this, data, view_start, view_end, prefs, mode ); + if ( this.prefs.min_value === undefined ) { + var min_value = Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + min_value = Math.min( min_value, this.data[i][5] ); + } + this.prefs.min_value = min_value; + } + if ( this.prefs.max_value === undefined ) { + var max_value = -Infinity; + for (var i = 0, len = this.data.length; i < len; i++) { + max_value = Math.max( max_value, this.data[i][5] ); + } + this.prefs.max_value = max_value; + } +}; + +DiagonalHeatmapPainter.prototype.default_prefs = { + min_value: undefined, + max_value: undefined, + mode: "Heatmap", + pos_color: "4169E1", + neg_color: "FF8C00" +}; + +DiagonalHeatmapPainter.prototype.draw = function(ctx, width, height, w_scale) { + var + min_value = this.prefs.min_value, + max_value = this.prefs.max_value, + value_range = max_value - min_value, + height_px = height, + view_start = this.view_start, + view_range = this.view_end - this.view_start, + mode = this.mode, + data = this.data, + invsqrt2 = 1 / Math.sqrt(2); + + var ramp = ( new SplitRamp( this.prefs.neg_color, "FFFFFF", this.prefs.pos_color, min_value, max_value ) ); + + var d, s1, e1, s2, e2, value; + + var scale = function( p ) { return ( p - view_start ) * w_scale }; + + ctx.save(); + + // Draw into triangle, then rotate and scale + ctx.rotate(-45 * Math.PI / 180); + ctx.scale( invsqrt2, invsqrt2 ); + + // Paint track. + for (var i = 0, len = data.length; i < len; i++) { + + d = data[i]; + + // Ensure the cell is visible + // if ( ) + + s1 = scale( d[1] ); + e1 = scale( d[2] ); + s2 = scale( d[4] ); + e2 = scale( d[5] ); + value = d[6]; + + ctx.fillStyle = ( ramp.map_value( value ) ) + + ctx.fillRect( s1, s2, ( e1 - s1 ), ( e2 - s2 ) ); + } + + ctx.restore(); +}; + exports.Scaler = Scaler; exports.SummaryTreePainter = SummaryTreePainter; exports.LinePainter = LinePainter; exports.LinkedFeaturePainter = LinkedFeaturePainter; exports.ReadPainter = ReadPainter; exports.ArcLinkedFeaturePainter = ArcLinkedFeaturePainter; +exports.DiagonalHeatmapPainter = DiagonalHeatmapPainter; // End painters_module encapsulation }; diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 static/scripts/viz/trackster_ui.js --- a/static/scripts/viz/trackster_ui.js +++ b/static/scripts/viz/trackster_ui.js @@ -67,6 +67,7 @@ "FeatureTrack": FeatureTrack, "VcfTrack": VcfTrack, "ReadTrack": ReadTrack, + // "DiagonalHeatmapTrack": DiagonalHeatmapTrack, "CompositeTrack": CompositeTrack, "DrawableGroup": DrawableGroup }; diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 templates/base_panels.mako --- a/templates/base_panels.mako +++ b/templates/base_panels.mako @@ -8,6 +8,7 @@ self.message_box_class="" self.active_view=None self.body_class="" + self.require_javascript=False %><%def name="init()"> @@ -47,7 +48,7 @@ <!--[if lt IE 7]> ${h.js( 'IE7', 'ie7-recalc' )} <![endif]--> - ${h.js( 'jquery', 'libs/underscore', 'libs/backbone', 'libs/backbone-relational', 'libs/handlebars.runtime', 'mvc/ui' )} + ${h.js( 'modernizr', 'jquery', 'libs/underscore', 'libs/backbone', 'libs/backbone-relational', 'libs/handlebars.runtime', 'mvc/ui' )} <script type="text/javascript"> // Set up needed paths. var galaxy_paths = new GalaxyPaths({ @@ -233,6 +234,16 @@ </head><body scroll="no" class="${self.body_class}"> + %if self.require_javascript: + <noscript> + <div class="overlay overlay-background"> + <div class="modal dialog-box" border="0"> + <div class="modal-header"><h3 class="title">Javascript Required</h3></div> + <div class="modal-body">The Galaxy analysis interface requires a browser with Javascript enabled. <br> Please enable Javascript and refresh this page</div> + </div> + </div> + </noscript> + %endif <div id="everything" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; min-width: 600px;"> ## Background displays first <div id="background"></div> diff -r 2ce01d5a691a9e04eb5f597c4a8cfa519236c822 -r 724c07bf72efdb66070b7ca396800b2cfb1b2012 templates/root/index.mako --- a/templates/root/index.mako +++ b/templates/root/index.mako @@ -188,6 +188,7 @@ self.has_left_panel = True self.has_right_panel = True self.active_view = "analysis" + self.require_javascript = True %> %if trans.app.config.require_login and not trans.user: <script type="text/javascript"> Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.
participants (1)
-
Bitbucket