1 new changeset in galaxy-central: http://bitbucket.org/galaxy/galaxy-central/changeset/acee660871cd/ changeset: r5375:acee660871cd user: jgoecks date: 2011-04-14 00:09:07 summary: Add Tile objects to trackster to support better tracking of Tile data properties; use SummaryTreeTile objects to ensure that feature track coverage histograms are drawn with the same max. Some refactoring of TiledTrack.draw() done as well for simplicity. Finally, fixed typos and GFF display bugs. affected #: 1 file (1.8 KB) --- a/static/scripts/trackster.js Wed Apr 13 16:05:34 2011 -0400 +++ b/static/scripts/trackster.js Wed Apr 13 18:09:07 2011 -0400 @@ -741,7 +741,7 @@ 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 @@ 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 @@ }); /** - * 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 = $("<div class='track-tile'/>").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 @@ // w_scale units are pixels per base. w_scale = width / range, resolution = this.view.resolution, - parent_element = $("<div style='position: relative;'></div>"); + parent_element = $("<div style='position: relative;'></div>"), + 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 @@ // 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); @@ -1725,6 +1741,29 @@ } // + // 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 @@ 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 @@ 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 = $("<div class='track-tile'>").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 @@ // $.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 @@ } } }, 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 @@ 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 @@ 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 @@ this.prefs, this.mode ); painter.draw( ctx, width, height ); - return canvas; + return new Tile(tile_length, resolution, canvas); } }); @@ -2196,11 +2233,14 @@ * 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 @@ // 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 @@ 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 @@ // If there's no data, return. if (!result.data) { - return canvas; + return new Tile(tile_index, resolution, canvas); } } @@ -2444,7 +2485,7 @@ 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 @@ 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 @@ // ---- 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.ht... @@ -2776,7 +2817,7 @@ 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 @@ // 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 @@ } }); -// 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 @@ 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 @@ 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 @@ 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) ), 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.