diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 171f6fd92ce..a83bee4c272 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -741,7 +741,7 @@ extend( View.prototype, { this.high = Math.ceil(high); // 10^log10(range / DENSITY) Close approximation for browser window, assuming DENSITY = window width - this.resolution = Math.pow( 10, Math.ceil( Math.log( (this.high - this.low) / 200 ) / Math.LN10 ) ); + this.resolution = Math.pow( 10, Math.ceil( Math.log( (this.high - this.low) / DENSITY ) / Math.LN10 ) ); this.zoom_res = Math.pow( FEATURE_LEVELS, Math.max(0,Math.ceil( Math.log( this.resolution, FEATURE_LEVELS ) / Math.log(FEATURE_LEVELS) ))); // Overview @@ -1016,14 +1016,16 @@ extend(NumberFilter.prototype, { return false; }, /** - * Returns true iff element is in [low, high]; range is inclusive. + * Returns true if (a) element's value is in [low, high] (range is inclusive) + * or (b) if value is non-numeric and hence unfilterable. */ keep: function(element) { if ( !this.applies_to( element ) ) { // No element to filter on. return true; } - return (element[this.index] >= this.low && element[this.index] <= this.high); + var val = parseInt(element[this.index]); + return (isNaN(val) || (val >= this.low && val <= this.high)); }, /** * Update filter's min and max values based on element's values. @@ -1306,11 +1308,22 @@ extend( TrackConfig.prototype, { }); /** - * Tiles for TiledTracks. + * Tiles drawn by tracks. */ -var Tile = function(track, canvas) { - this.track = track; - this.canvas = canvas; +var Tile = function(index, resolution, canvas) { + // Wrap element in div for background + this.index = index; + this.resolution = resolution; + this.canvas = $("
").append(canvas); +}; + +var SummaryTreeTile = function(index, resolution, canvas, max_val) { + Tile.call(this, index, resolution, canvas); + this.max_val = max_val; +}; + +var FeatureTrackTile = function(index, resolution, canvas) { + Tile.call(this, index, resolution, canvas); }; /** @@ -1669,30 +1682,33 @@ extend(TiledTrack.prototype, Track.prototype, { // w_scale units are pixels per base. w_scale = width / range, resolution = this.view.resolution, - parent_element = $("
"); + parent_element = $("
"), + gen_key = function(width, w_scale, tile_index) { + return width + '_' + w_scale + '_' + tile_index; + }; if (!clear_after) { this.content_div.children().remove(); } this.content_div.append( parent_element ); this.max_height = 0; // Index of first tile that overlaps visible region var tile_index = Math.floor( low / resolution / DENSITY ); - // A set of setTimeout() ids used when drawing tiles. Each ID indicates - // a tile has been requested to be drawn or is being drawn. - var draw_tile_dict = {}; + // A list of tiles drawn/retrieved. + var drawn_tiles = []; + var tile_count = 0 while ( ( tile_index * DENSITY * resolution ) < high ) { // Check in cache - var key = width + '_' + w_scale + '_' + tile_index; + var key = gen_key(width, w_scale, tile_index); var cached = this.tile_cache.get(key); var tile_low = tile_index * DENSITY * this.view.resolution; var tile_high = tile_low + DENSITY * this.view.resolution; - // console.log(cached, this.tile_cache); if ( !force && cached ) { + drawn_tiles[drawn_tiles.length] = cached; this.show_tile(cached, parent_element, tile_low, w_scale); } else { - this.delayed_draw(force, key, tile_low, tile_high, tile_index, - resolution, parent_element, w_scale, draw_tile_dict); + this.delayed_draw(force, key, tile_index, resolution, parent_element, w_scale, drawn_tiles); } tile_index += 1; + tile_count++; } // @@ -1700,7 +1716,7 @@ extend(TiledTrack.prototype, Track.prototype, { // var track = this; var intervalId = setInterval(function() { - if (obj_length(draw_tile_dict) === 0) { + if (drawn_tiles.length === tile_count) { // All tiles have been drawn. clearInterval(intervalId); @@ -1724,6 +1740,29 @@ extend(TiledTrack.prototype, Track.prototype, { } } + // + // If mode is Histogram and tiles do not share max, redraw tiles as necessary using new max. + // + if (track.mode == "Histogram") { + // Get global max. + var global_max = -1; + for (var i = 0; i < drawn_tiles.length; i++) { + var cur_max = drawn_tiles[i].max_val; + if (cur_max > global_max) { + global_max = cur_max; + } + } + + for (var i = 0; i < drawn_tiles.length; i++) { + if (drawn_tiles[i].max_val !== global_max) { + var tile = drawn_tiles[i]; + tile.canvas.remove(); + track.delayed_draw(true, gen_key(width, w_scale, tile.index), tile.index, + tile.resolution, parent_element, w_scale, [], { max: global_max }); + } + } + } + // // Update filter attributes, UI. // @@ -1755,6 +1794,19 @@ extend(TiledTrack.prototype, Track.prototype, { track.make_name_popup_menu(); } } + + // Store initial canvas in case we need to use it for overview + /* This is completely broken, just saves the first tile it sees + regardless of if it should be the overview + if (!track.initial_canvas && !window.G_vmlCanvasManager) { + track.initial_canvas = $(tile_element).clone(); + var src_ctx = tile_element.get(0).getContext("2d"); + var tgt_ctx = track.initial_canvas.get(0).getContext("2d"); + var data = src_ctx.getImageData(0, 0, src_ctx.canvas.width, src_ctx.canvas.height); + tgt_ctx.putImageData(data, 0, 0); + track.set_overview(); + } + */ } }, 50); @@ -1765,44 +1817,28 @@ extend(TiledTrack.prototype, Track.prototype, { this.child_tracks[i].draw(force, clear_after); } }, - delayed_draw: function(force, key, tile_low, tile_high, tile_index, resolution, parent_element, w_scale, draw_tile_dict) { - var track = this; - // Put a 50ms delay on drawing so that if the user scrolls fast, we don't load extra data + delayed_draw: function(force, key, tile_index, resolution, parent_element, w_scale, drawn_tiles, more_tile_data) { + var track = this, + tile_low = tile_index * DENSITY * resolution, + tile_high = tile_low + DENSITY * resolution; + + // Helper method. var draw_and_show_tile = function(id, result, resolution, tile_index, parent_element, w_scale, seq_data) { // DEBUG: this is still called too many times when moving slowly, // console.log( "draw_and_show_tile", resolution, tile_index, w_scale ); - returned_tile = track.draw_tile(result, resolution, tile_index, parent_element, w_scale, seq_data) - - // Wrap element in div for background - var wrapper_element = $("
").prepend(returned_tile); - tile_element = wrapper_element; - - track.tile_cache.set(key, tile_element); - track.show_tile(tile_element, parent_element, tile_low, w_scale); - - // TODO: this should go in a post-draw function. - // Store initial canvas in case we need to use it for overview - /* This is completely broken, just saves the first tile it sees - regardless of if it should be the overview - if (!track.initial_canvas && !window.G_vmlCanvasManager) { - track.initial_canvas = $(tile_element).clone(); - var src_ctx = tile_element.get(0).getContext("2d"); - var tgt_ctx = track.initial_canvas.get(0).getContext("2d"); - var data = src_ctx.getImageData(0, 0, src_ctx.canvas.width, src_ctx.canvas.height); - tgt_ctx.putImageData(data, 0, 0); - track.set_overview(); - } - */ - - delete draw_tile_dict[id]; + var tile = track.draw_tile(result, resolution, tile_index, parent_element, w_scale, seq_data); + track.tile_cache.set(key, tile); + track.show_tile(tile, parent_element, tile_low, w_scale); + drawn_tiles[drawn_tiles.length] = tile; }; + // Put a 50ms delay on drawing so that if the user scrolls fast, we don't load extra data var id = setTimeout(function() { if (tile_low <= track.view.high && tile_high >= track.view.low) { // Show/draw tile: check cache for tile; if tile not in cache, draw it. - var tile_element = (force ? undefined : track.tile_cache.get(key)); - if (tile_element) { - track.show_tile(tile_element, parent_element, tile_low, w_scale); - delete draw_tile_dict[id]; + var tile = (force ? undefined : track.tile_cache.get(key)); + if (tile) { + track.show_tile(tile, parent_element, tile_low, w_scale); + drawn_tiles[drawn_tiles.length] = tile; } else { // @@ -1810,6 +1846,7 @@ extend(TiledTrack.prototype, Track.prototype, { // $.when(track.data_cache.get_data(view.chrom, tile_low, tile_high, track.mode, resolution, track.data_url_extra_params)).then(function(tile_data) { + extend(tile_data, more_tile_data); // If sequence data needed, get that and draw. Otherwise draw. if (view.reference_track && w_scale > view.canvas_manager.char_width_px) { $.when(view.reference_track.data_cache.get_data(view.chrom, tile_low, tile_high, @@ -1825,12 +1862,11 @@ extend(TiledTrack.prototype, Track.prototype, { } } }, 50); - draw_tile_dict[id] = true; }, /** * Show track tile and perform associated actions. */ - show_tile: function(tile_element, parent_element, tile_low, w_scale) { + show_tile: function(tile, parent_element, tile_low, w_scale) { // Readability. var track = this; @@ -1844,6 +1880,7 @@ extend(TiledTrack.prototype, Track.prototype, { if (this.left_offset) { left -= this.left_offset; } + var tile_element = tile.canvas; tile_element.css({ position: 'absolute', top: 0, left: left, height: '' }); // Setup and show tile element. @@ -1966,7 +2003,7 @@ extend(ReferenceTrack.prototype, TiledTrack.prototype, { var c_start = Math.round(c * w_scale); ctx.fillText(seq[c], c_start + track.left_offset, 10); } - return canvas; + return new Tile(tile_index, resolution, canvas); } this.content_div.css("height", "0px"); } @@ -2106,7 +2143,7 @@ extend(LineTrack.prototype, TiledTrack.prototype, { this.prefs, this.mode ); painter.draw( ctx, width, height ); - return canvas; + return new Tile(tile_length, resolution, canvas); } }); @@ -2196,11 +2233,14 @@ extend(FeatureTrack.prototype, TiledTrack.prototype, { * position. Return value is a dict with keys 'data', 'delta' (bin size) and 'max.' Data * is a two-item list; first item is bin start, second is bin's count. */ - get_summary_tree_data: function(data, low, high, bin_size) { - var num_bins = Math.floor((high - low)/bin_size), + get_summary_tree_data: function(data, low, high, num_bins) { + if (num_bins > high - low) { + num_bins = high - low; + } + var bin_size = Math.floor((high - low)/num_bins), bins = [], max_count = 0; - + /* // For debugging: for (var i = 0; i < data.length; i++) @@ -2335,8 +2375,7 @@ extend(FeatureTrack.prototype, TiledTrack.prototype, { // Deal with left_offset by translating ctx.translate( left_offset, SUMMARY_TREE_TOP_PADDING ); painter.draw( ctx, width, required_height ); - // Canvas element is returned - return canvas; + return new SummaryTreeTile(tile_index, resolution, canvas, result.max); } // Drawing coverage histogram. This is different from summary tree because data can feature @@ -2359,13 +2398,15 @@ extend(FeatureTrack.prototype, TiledTrack.prototype, { canvas.height = required_height + SUMMARY_TREE_TOP_PADDING; // Paint summary tree into canvas. var binned_data = this.get_summary_tree_data(result.data, tile_low, tile_high, 200); + if (result.max) { + binned_data.max = result.max; + } var painter = new painters.SummaryTreePainter(binned_data, tile_low, tile_high, this.prefs); var ctx = canvas.getContext("2d"); // Deal with left_offset by translating ctx.translate(left_offset, SUMMARY_TREE_TOP_PADDING); painter.draw(ctx, width, required_height); - // Canvas element is returned - return canvas; + return new SummaryTreeTile(tile_index, resolution, canvas, binned_data.max); } // Start dealing with row-by-row tracks @@ -2433,7 +2474,7 @@ extend(FeatureTrack.prototype, TiledTrack.prototype, { // If there's no data, return. if (!result.data) { - return canvas; + return new Tile(tile_index, resolution, canvas); } } @@ -2444,7 +2485,7 @@ extend(FeatureTrack.prototype, TiledTrack.prototype, { ctx.translate( left_offset, ERROR_PADDING ); painter.draw( ctx, width, required_height, slots ); - return canvas; + return new FeatureTrackTile(tile_index, resolution, canvas); } }); @@ -2558,7 +2599,7 @@ exports.FeatureSlotter = function ( w_scale, include_label, max_rows, measureTex this.include_label = include_label; this.max_rows = max_rows; this.measureText = measureText; -} +}; /** * Slot a set of features, `this.slots` will be updated with slots by id, and @@ -2704,7 +2745,7 @@ extend( exports.FeatureSlotter.prototype, { // ---- Painters ---- var painters_module = function(require, exports){ - + /** * Draw a dashed line on a canvas using filled rectangles. This function is based on: * http://vetruvet.blogspot.com/2010/10/drawing-dashed-lines-on-html5-canvas.html @@ -2776,7 +2817,7 @@ var SummaryTreePainter = function( data, view_start, view_end, prefs, mode ) { Painter.call( this, data, view_start, view_end, prefs, mode ); } -SummaryTreePainter.prototype.default_prefis = { show_counts: false }; +SummaryTreePainter.prototype.default_prefs = { show_counts: false }; SummaryTreePainter.prototype.draw = function( ctx, width, height ) { @@ -2790,16 +2831,15 @@ SummaryTreePainter.prototype.draw = function( ctx, width, height ) { // max rectangle is required_height. base_y = height; delta_x_px = Math.ceil(delta * w_scale); - ctx.save(); for (var i = 0, len = points.length; i < len; i++) { - var x = Math.floor( (points[i][0] - view_start) * w_scale ); var y = points[i][1]; if (!y) { continue; } - var y_px = y / max * height; + var y_px = y / max * height + if (y !== 0 && y_px < 1) { y_px = 1; } ctx.fillStyle = "black"; ctx.fillRect( x, base_y - y_px, delta_x_px, y_px ); @@ -2991,7 +3031,7 @@ extend( FeaturePainter.prototype, { } }); -// Contstants specific to feature tracks moved here (HACKING, these should +// Constants specific to feature tracks moved here (HACKING, these should // basically all be configuration options) var DENSE_TRACK_HEIGHT = 10, NO_DETAIL_TRACK_HEIGHT = 3, @@ -3034,8 +3074,7 @@ extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, { var feature_uid = feature[0], feature_start = feature[1], - // -1 b/c intervals are half-open. - feature_end = feature[2] - 1, + feature_end = feature[2], feature_name = feature[3], f_start = Math.floor( Math.max(0, (feature_start - tile_low) * w_scale) ), f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ), @@ -3134,8 +3173,7 @@ extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, { for (var k = 0, k_len = feature_blocks.length; k < k_len; k++) { var block = feature_blocks[k], block_start = Math.floor( Math.max(0, (block[0] - tile_low) * w_scale) ), - // -1 b/c intervals are half-open. - block_end = Math.ceil( Math.min(width, Math.max((block[1] - 1 - tile_low) * w_scale)) ); + block_end = Math.ceil( Math.min(width, Math.max((block[1] - tile_low) * w_scale)) ); // Skip drawing if block not on tile. if (block_start > block_end) { continue; } @@ -3199,8 +3237,7 @@ extend( VariantPainter.prototype, FeaturePainter.prototype, { var feature = data[i], feature_uid = feature[0], feature_start = feature[1], - // -1 b/c intervals are half-open. - feature_end = feature[2] - 1, + feature_end = feature[2], feature_name = feature[3], // All features need a start, end, and vertical center. f_start = Math.floor( Math.max(0, (feature_start - tile_low) * w_scale) ),