details: http://www.bx.psu.edu/hg/galaxy/rev/c8ef8611841d changeset: 3274:c8ef8611841d user: Kanwei Li <kanwei@gmail.com> date: Tue Jan 26 16:37:25 2010 -0500 description: trackster: - Fixed incorrect drawing of thick strands - Linetrack: can now set min and max values for y-axis - Featuretrack: can set color for label and block - Drawing delays 50ms so that scrolling quickly across browser does not load everything travelled through - Fixed another memory leak in arraytree provider - Made intron tracks lighter in color diffstat: lib/galaxy/visualization/tracks/data/array_tree.py | 2 + lib/galaxy/web/controllers/tracks.py | 7 + static/scripts/pack_scripts.py | 2 +- static/scripts/trackster.js | 144 ++++++++++++++---- static/trackster.css | 2 +- templates/root/history.mako | 2 +- templates/tracks/browser.mako | 162 +++++++++++++++----- templates/workflow/editor.mako | 2 +- 8 files changed, 243 insertions(+), 80 deletions(-) diffs (619 lines): diff -r 4a3a4af6be53 -r c8ef8611841d lib/galaxy/visualization/tracks/data/array_tree.py --- a/lib/galaxy/visualization/tracks/data/array_tree.py Tue Jan 26 16:16:03 2010 -0500 +++ b/lib/galaxy/visualization/tracks/data/array_tree.py Tue Jan 26 16:37:25 2010 -0500 @@ -22,6 +22,7 @@ try: chrom_array_tree = d[chrom] except KeyError: + f.close() return "no data" root_summary = chrom_array_tree.get_summary( 0, chrom_array_tree.levels ) @@ -36,6 +37,7 @@ try: chrom_array_tree = d[chrom] except KeyError: + f.close() return None block_size = chrom_array_tree.block_size diff -r 4a3a4af6be53 -r c8ef8611841d lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py Tue Jan 26 16:16:03 2010 -0500 +++ b/lib/galaxy/web/controllers/tracks.py Tue Jan 26 16:37:25 2010 -0500 @@ -132,6 +132,7 @@ error( "No chromosome lengths file found for '%s'" % dataset.name ) return trans.fill_template( 'tracks/browser.mako', #dataset_ids=dataset_ids, + title = vis.title, id=id, tracks=tracks, chrom=chrom, @@ -249,3 +250,9 @@ trans.sa_session.add( assoc ) trans.sa_session.flush() return new_dataset + + @web.json + def update_config( self, trans, **kwargs ): + raise RuntimeError + + \ No newline at end of file diff -r 4a3a4af6be53 -r c8ef8611841d static/scripts/pack_scripts.py --- a/static/scripts/pack_scripts.py Tue Jan 26 16:16:03 2010 -0500 +++ b/static/scripts/pack_scripts.py Tue Jan 26 16:37:25 2010 -0500 @@ -8,7 +8,7 @@ from os import path # Scripts that should not be packed -- just copied -do_not_pack = set( [ "ie_pngfix.js", "aflax.js" ] ) +do_not_pack = set() cmd = "java -jar ../../scripts/yuicompressor.jar --type js %(fname)s -o packed/%(fname)s" diff -r 4a3a4af6be53 -r c8ef8611841d static/scripts/trackster.js --- a/static/scripts/trackster.js Tue Jan 26 16:16:03 2010 -0500 +++ b/static/scripts/trackster.js Tue Jan 26 16:37:25 2010 -0500 @@ -4,7 +4,7 @@ var DEBUG = false; var DENSITY = 1000, - FEATURE_LEVELS = 100, + FEATURE_LEVELS = 10, DATA_ERROR = "There was an error in indexing this dataset.", DATA_NONE = "No data for this chrom/contig.", DATA_PENDING = "Currently indexing... please wait", @@ -47,8 +47,7 @@ var Cache = function( num_elements ) { this.num_elements = num_elements; - this.obj_cache = {}; - this.key_ary = []; + this.clear(); }; $.extend( Cache.prototype, { get: function( key ) { @@ -71,11 +70,16 @@ } this.obj_cache[key] = value; return value; + }, + clear: function() { + this.obj_cache = {}; + this.key_ary = []; } }); -var View = function( chrom, max_high ) { +var View = function( chrom, max_high, config ) { this.chrom = chrom; + this.config = config; this.tracks = []; this.max_low = 0; this.max_high = max_high; @@ -181,7 +185,6 @@ var w_scale = this.content_div.width() / range; - var max_height = 20; var tile_element; // Index of first tile that overlaps visible region var tile_index = Math.floor( low / resolution / DENSITY ); @@ -201,22 +204,30 @@ }); // Our responsibility to move the element to the new parent parent_element.append( cached ); - max_height = Math.max( max_height, cached.height() ); + this.max_height = Math.max( this.max_height, cached.height() ); } else { - tile_element = this.draw_tile( resolution, tile_index, parent_element, w_scale ); + this.delayed_draw(this, key, low, high, tile_index, resolution, parent_element, w_scale); + } + tile_index += 1; + } + }, delayed_draw: function(track, key, low, high, tile_index, resolution, parent_element, w_scale) { + // Put a 50ms delay on drawing so that if the user scrolls fast, we don't load extra data + setTimeout(function() { + if ( !(low > track.view.high || high < track.view.low) ) { + tile_element = track.draw_tile( resolution, tile_index, parent_element, w_scale ); if ( tile_element ) { - this.tile_cache.set(key, tile_element); - max_height = Math.max( max_height, tile_element.height() ); + track.tile_cache.set(key, tile_element); + track.max_height = Math.max( track.max_height, tile_element.height() ); + track.content_div.css( "height", track.max_height ); } } - this.content_div.css( "height", max_height ); - tile_index += 1; - } + }, 50); } }); var LabelTrack = function ( parent_element ) { Track.call( this, null, parent_element ); + this.hidden = true; this.container_div.addClass( "label-track" ); }; $.extend( LabelTrack.prototype, Track.prototype, { @@ -241,7 +252,7 @@ } }); -var LineTrack = function ( name, dataset_id, indexer, height ) { +var LineTrack = function ( name, dataset_id, indexer, height, minval, maxval ) { this.tile_cache = new Cache(CACHED_TILES_LINE); Track.call( this, name, $("#viewport") ); TiledTrack.call( this ); @@ -251,12 +262,19 @@ this.container_div.addClass( "line-track" ); this.dataset_id = dataset_id; this.data_queue = {}; - this.cache = new Cache(CACHED_DATA); // We need to cache some data because of + this.data_cache = new Cache(CACHED_DATA); // We need to cache some data because of // asynchronous calls + if (minval !== undefined && maxval !== undefined) { + this.min_value = minval; + this.max_value = maxval; + this.vertical_range = maxval - minval; + } }; $.extend( LineTrack.prototype, TiledTrack.prototype, { init: function() { - var track = this; + var track = this, + track_id = track.view.tracks.indexOf(track); + track.content_div.text(DATA_LOADING); $.getJSON( data_url, { stats: true, indexer: track.indexer, chrom: track.view.chrom, low: null, high: null, @@ -274,13 +292,20 @@ } else { track.content_div.text(""); track.content_div.css( "height", track.height_px + "px" ); - track.min_value = data.min; - track.max_value = data.max; - track.vertical_range = track.max_value - track.min_value; + + if (track.min_value === undefined || track.max_value === undefined) { + track.min_value = data.min; + track.max_value = data.max; + track.vertical_range = track.max_value - track.min_value; + + // Update the config + $('#track_' + track_id + '_minval').val(track.min_value); + $('#track_' + track_id + '_maxval').val(track.max_value); + } // Draw y-axis labels - var min_label = $("<div class='yaxislabel'>" + track.min_value + "</div>"); - var max_label = $("<div class='yaxislabel'>" + track.max_value + "</div>"); + var min_label = $("<div></div>").addClass('yaxislabel').attr("id", 'linetrack_' + track_id + '_minval').text(track.min_value); + var max_label = $("<div></div>").addClass('yaxislabel').attr("id", 'linetrack_' + track_id + '_maxval').text(track.max_value); max_label.css({ position: "relative", top: "25px" }); max_label.prependTo(track.container_div); @@ -303,11 +328,11 @@ $.getJSON( data_url, { indexer: this.indexer, chrom: this.view.chrom, low: low, high: high, dataset_id: this.dataset_id, resolution: this.view.resolution }, function ( data ) { - track.cache.set(key, data); + track.data_cache.set(key, data); delete track.data_queue[key]; track.draw(); }); - } + } }, draw_tile: function( resolution, tile_index, parent_element, w_scale ) { if (!this.vertical_range) { // We don't have the necessary information yet @@ -319,12 +344,12 @@ canvas = $("<canvas class='tile'></canvas>"), key = resolution + "_" + tile_index; - if (!this.cache.get(key)) { + if (!this.data_cache.get(key)) { this.get_data( resolution, tile_index ); return; } - var data = this.cache.get(key); + var data = this.data_cache.get(key); canvas.css( { position: "absolute", top: 0, @@ -345,7 +370,14 @@ } else { // Translate x = x * w_scale; - y = (y - this.min_value) / this.vertical_range * this.height_px; + // console.log(y, this.min_value, this.vertical_range, (y - this.min_value) / this.vertical_range * this.height_px); + if (y <= this.min_value) { + y = this.min_value; + } else if (y >= this.max_value) { + y = this.max_value; + } + y = Math.round( this.height_px - (y - this.min_value) / this.vertical_range * this.height_px ); + // console.log(canvas.get(0).height, canvas.get(0).width); if ( in_path ) { ctx.lineTo( x, y ); } else { @@ -357,6 +389,29 @@ ctx.stroke(); parent_element.append( canvas ); return canvas; + }, gen_options: function(track_id) { + var container = $("<div></div>").addClass("form-row"); + + var minval = 'track_' + track_id + '_minval', + maxval = 'track_' + track_id + '_maxval', + min_label = $('<label></label>').attr("for", minval).text("Min value:"), + min_input = $('<input></input>').attr("id", minval).val(this.min_value || ""), + max_label = $('<label></label>').attr("for", maxval).text("Max value:"), + max_input = $('<input></input>').attr("id", maxval).val(this.max_value || ""); + + return container.append(min_label).append(min_input).append(max_label).append(max_input); + }, update_options: function(track_id) { + var min_value = $('#track_' + track_id + '_minval').val(), + max_value = $('#track_' + track_id + '_maxval').val(); + if ( min_value !== this.min_value || max_value !== this.max_value) { + this.min_value = parseFloat(min_value); + this.max_value = parseFloat(max_value); + this.vertical_range = this.max_value - this.min_value; + $('#linetrack_' + track_id + '_minval').text(this.min_value); + $('#linetrack_' + track_id + '_maxval').text(this.max_value); + this.tile_cache.clear(); + this.draw(); + } } }); @@ -374,7 +429,8 @@ this.showing_details = false; this.vertical_detail_px = 10; this.vertical_nodetail_px = 3; - this.base_color = "#2C3143"; + this.block_color = "black"; + this.label_color = "black"; this.default_font = "9px Monaco, Lucida Console, monospace"; this.left_offset = 200; this.inc_slots = {}; @@ -564,7 +620,7 @@ new_canvas.get(0).height = required_height; // console.log(( tile_low - this.view.low ) * w_scale, tile_index, w_scale); var ctx = new_canvas.get(0).getContext("2d"); - ctx.fillStyle = this.base_color; + ctx.fillStyle = this.block_color; ctx.font = this.default_font; ctx.textAlign = "right"; @@ -586,10 +642,10 @@ ctx.fillRect(f_start + this.left_offset, y_center + 5, f_end - f_start, 1); } else { // Showing labels, blocks, details - if (ctx.fillText && feature.start > tile_low) { + if (feature.start > tile_low) { + ctx.fillStyle = this.label_color; ctx.fillText(feature.name, f_start - 1 + this.left_offset, y_center + 8); - // ctx.fillText(commatize(feature.start), f_start - 1 + this.left_offset, y_center + 8); - // ctx.fillText(slots[feature.uid], f_start - 1 + this.left_offset, y_center + 8); + ctx.fillStyle = this.block_color; } var blocks = feature.blocks; if (blocks) { @@ -601,7 +657,7 @@ ctx.fillStyle = LEFT_STRAND; } ctx.fillRect(f_start + this.left_offset, y_center, f_end - f_start, 10); - ctx.fillStyle = this.base_color; + ctx.fillStyle = this.block_color; } for (var k = 0, k_len = blocks.length; k < k_len; k++) { @@ -614,7 +670,8 @@ y_start = 3; ctx.fillRect(block_start + this.left_offset, y_center + y_start, block_end - block_start, thickness); - if (thick_start !== undefined && (block_start <= thick_end || block_end >= thick_start) ) { + // Draw thick regions: check if block intersects with thick region + if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) { thickness = 9; y_start = 1; var block_thick_start = Math.max(block_start, thick_start), @@ -635,7 +692,7 @@ ctx.fillStyle = LEFT_STRAND_INV; } ctx.fillRect(f_start + this.left_offset, y_center, f_end - f_start, 10); - ctx.fillStyle = this.base_color; + ctx.fillStyle = this.block_color; } } } @@ -645,6 +702,25 @@ parent_element.append( new_canvas ); return new_canvas; + }, gen_options: function(track_id) { + var container = $("<div></div>").addClass("form-row"); + + var block_color = 'track_' + track_id + '_block_color', + block_color_label = $('<label></label>').attr("for", block_color).text("Block color:"), + block_color_input = $('<input></input>').attr("id", block_color).attr("name", block_color).val(this.block_color), + label_color = 'track_' + track_id + '_label_color', + label_color_label = $('<label></label>').attr("for", label_color).text("Label color:"), + label_color_input = $('<input></input>').attr("id", label_color).attr("name", label_color).val(this.label_color); + return container.append(block_color_label).append(block_color_input).append(label_color_label).append(label_color_input); + }, update_options: function(track_id) { + var block_color = $('#track_' + track_id + '_block_color').val(), + label_color = $('#track_' + track_id + '_label_color').val(); + if (block_color !== this.block_color || label_color !== this.label_color) { + this.block_color = block_color; + this.label_color = label_color; + this.tile_cache.clear(); + this.draw(); + } } }); @@ -684,7 +760,7 @@ new_canvas.get(0).height = required_height; // console.log(( tile_low - this.view.low ) * w_scale, tile_index, w_scale); var ctx = new_canvas.get(0).getContext("2d"); - ctx.fillStyle = this.base_color; + ctx.fillStyle = this.block_color; ctx.font = this.default_font; ctx.textAlign = "right"; var px_per_char = ctx.measureText("A").width; diff -r 4a3a4af6be53 -r c8ef8611841d static/trackster.css --- a/static/trackster.css Tue Jan 26 16:16:03 2010 -0500 +++ b/static/trackster.css Tue Jan 26 16:37:25 2010 -0500 @@ -109,7 +109,7 @@ } .track-header { - text-align: left; + text-align: center; padding: 4px; color: #666; } diff -r 4a3a4af6be53 -r c8ef8611841d templates/root/history.mako --- a/templates/root/history.mako Tue Jan 26 16:16:03 2010 -0500 +++ b/templates/root/history.mako Tue Jan 26 16:37:25 2010 -0500 @@ -20,7 +20,7 @@ <script type="text/javascript"> $(function() { // Load jStore for local storage - $.extend(jQuery.jStore.defaults, { project: 'galaxy', flash: '/static/jStore.Flash.html' }) + $.extend(jQuery.jStore.defaults, { project: 'galaxy', flash: '${h.url_for("/static/jStore.Flash.html")}' }) $.jStore.load(); // Auto-select best storage $.jStore.ready(function(engine) { diff -r 4a3a4af6be53 -r c8ef8611841d templates/tracks/browser.mako --- a/templates/tracks/browser.mako Tue Jan 26 16:16:03 2010 -0500 +++ b/templates/tracks/browser.mako Tue Jan 26 16:37:25 2010 -0500 @@ -11,12 +11,84 @@ <%def name="stylesheets()"> ${parent.stylesheets()} -<link rel="stylesheet" type="text/css" href="/static/trackster.css" /> + +${h.css( "history" )} +<link rel="stylesheet" type="text/css" href="${h.url_for('/static/trackster.css')}" /> +<link rel="stylesheet" type="text/css" href="${h.url_for('/static/ui.theme.css')}" /> +<style type="text/css"> + ul#sortable-ul { + list-style: none; + padding: 0; + margin: 5px; + } + ul#sortable-ul li { + display: block; + margin: 5px 0; + background: #eee; + } + .delete-button { + background: transparent url(history-buttons.png) no-repeat scroll 0 -104px; + height: 20px; + width: 20px; + } + .delete-button:hover { + background-position: 0 -130px; + } + +</style> +</%def> + +<%def name="center_panel()"> +<div id="content"> + <div id="top-labeltrack"></div> + <div id="viewport-container" style="overflow-x: hidden; overflow-y: auto;"> + <div id="viewport"></div> + </div> + +</div> +<div id="nav-container"> + <div id="nav-labeltrack"></div> + <div id="nav"> + <div id="overview"> + <div id="overview-viewport"> + <div id="overview-box"></div> + </div> + </div> + <div id="nav-controls"> + <form name="chr" id="chr" method="get"> + <select id="chrom" name="chrom" style="width: 15em;"> + <option value="">Loading</option> + </select> + <input id="low" size="12" />:<input id="high" size="12" /> + ## <input type="hidden" name="dataset_ids" value="${dataset_ids}" /> + <input type="hidden" name="id" value="${id}" /> + <a href="#" onclick="javascript:view.zoom_in();view.redraw();">+</a> + <a href="#" onclick="javascript:view.zoom_out();view.redraw();">-</a> + </form> + <div id="debug" style="float: right"></div> + </div> + </div> +</div> +</%def> + +<%def name="right_panel()"> + <div class="unified-panel-header" unselectable="on"> + <div class="unified-panel-header-inner">Configuration</div> + </div> + <form action="${h.url_for( action='update_config' )}"> +## <input name="title" id="title" value="${title}" /> + <div id="show-hide-move"> + <ul id="sortable-ul"></ul> +## <input type="submit" id="update-config" value="Save settings" /> + <input type="button" id="refresh-button" value="Refresh" /> + </div> + </form> + </%def> <%def name="javascripts()"> ${parent.javascripts()} -${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "trackster" )} +${h.js( "jquery", "jquery.event.drag", "jquery.mousewheel", "trackster", "ui.core", "ui.sortable" )} <script type="text/javascript"> @@ -36,7 +108,7 @@ view.redraw(); }); - $(document).bind("mousewheel", function( e, delta ) { + $("#content").bind("mousewheel", function( e, delta ) { if (delta > 0) { view.zoom_in(e.pageX); } else { @@ -45,7 +117,7 @@ e.preventDefault(); }); - $(document).bind("dblclick", function( e ) { + $("#content").bind("dblclick", function( e ) { view.zoom_in(e.pageX); }); @@ -83,7 +155,6 @@ var new_scroll = container.scrollTop() - (e.clientY - this.current_height); if ( new_scroll < container.get(0).scrollHeight - container.height() ) { container.scrollTop(new_scroll); - } this.current_height = e.clientY; this.current_x = e.offsetX; @@ -92,6 +163,17 @@ view.center -= delta_chrom; view.redraw(); }); + + $("#refresh-button").bind( "click", function(e) { + for (var track_id in view.tracks) { + var track = view.tracks[track_id]; + if (track.update_options) { + track.update_options(track_id); + } + } + }); + + // Execute this on page load (function () { $.getJSON( "${h.url_for( action='chroms' )}", { dbkey: "${dbkey}" }, function ( data ) { var chrom_options = '<option value="">Select Chrom/Contig</option>'; @@ -108,6 +190,39 @@ $("#chr").submit(); }); }); + + // Populate sort/move ul + for (var track_id in view.tracks) { + var track = view.tracks[track_id]; + if (!track.hidden) { + var label = $('<label for="track_' + track_id + 'title">' + track.name + '</label>'); + var title = $('<div class="toolFormTitle"></div>'); + var del_icon = $('<a style="display:block; float:right" href="#" class="icon-button delete" />'); + var body = $('<div class="toolFormBody"></div>'); + // var checkbox = $('<input type="checkbox" checked="checked"></input>').attr("id", "track_" + track_id + "title"); + var li = $('<li class="sortable"></li>').attr("id", "track_" + track_id); + var div = $('<div class="toolForm"></div>'); + del_icon.prependTo(title); + label.appendTo(title); + // checkbox.prependTo(title); + if (track.gen_options) { + body.append(track.gen_options(track_id)); + } + title.prependTo(div); + body.appendTo(div); + li.append(div); + $("ul#sortable-ul").append(li); + } + } + + $("ul#sortable-ul").sortable({ + update: function(event, ui) { + for (var track_id in view.tracks) { + var track = view.tracks[track_id]; + } + } + }); + })(); $(window).trigger("resize"); }); @@ -115,40 +230,3 @@ </script> </%def> -<%def name="center_panel()"> -<div id="content"> - <div id="top-labeltrack"></div> - <div id="viewport-container" style="overflow-x: hidden; overflow-y: auto;"> - <div id="viewport"></div> - </div> - -</div> -<div id="nav-container"> - <div id="nav-labeltrack"></div> - <div id="nav"> - <div id="overview"> - <div id="overview-viewport"> - <div id="overview-box"></div> - </div> - </div> - <div id="nav-controls"> - <form name="chr" id="chr" method="get"> - <select id="chrom" name="chrom" style="width: 15em;"> - <option value="">Loading</option> - </select> - <input id="low" size="12" />:<input id="high" size="12" /> - ## <input type="hidden" name="dataset_ids" value="${dataset_ids}" /> - <input type="hidden" name="id" value="${id}" /> - <a href="#" onclick="javascript:view.zoom_in();view.redraw();">+</a> - <a href="#" onclick="javascript:view.zoom_out();view.redraw();">-</a> - </form> - <div id="debug" style="float: right"></div> - </div> - </div> -</div> -</%def> - -<%def name="right_panel()"> - <div>Configs</div> - -</%def> \ No newline at end of file diff -r 4a3a4af6be53 -r c8ef8611841d templates/workflow/editor.mako --- a/templates/workflow/editor.mako Tue Jan 26 16:16:03 2010 -0500 +++ b/templates/workflow/editor.mako Tue Jan 26 16:37:25 2010 -0500 @@ -59,7 +59,7 @@ } // Load jStore for local storage - $.extend(jQuery.jStore.defaults, { project: 'galaxy', flash: '/static/jStore.Flash.html' }) + $.extend(jQuery.jStore.defaults, { project: 'galaxy', flash: '${h.url_for("/static/jStore.Flash.html")}' }) $.jStore.load(); // Auto-select best storage // Canvas overview management