This commit is contained in:
James Taylor
2011-11-07 15:31:41 -05:00
14 changed files with 340 additions and 101 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ repository = http://eggs.g2.bx.psu.edu
no_auto = pbs_python DRMAA_python
[eggs:platform]
bx_python = 0.7.0
bx_python = 0.7.1
Cheetah = 2.2.2
ctypes = 1.0.2
DRMAA_python = 0.2
@@ -67,7 +67,7 @@ Whoosh = 0.3.18
psycopg2 = _8.4.2_static
pysqlite = _3.6.17_static
MySQL_python = _5.1.41_static
bx_python = _494c2d1d68b3_rebuild1
bx_python = _7b95ff194725
GeneTrack = _dev_48da9e998f0caf01c5be731e926f4b0481f658f0
SQLAlchemy = _dev_r6498
pysam = _kanwei_b10f6e722e9a
@@ -50,4 +50,4 @@ def main():
st.write(out_fname)
if __name__ == "__main__":
main()
main()
@@ -2,14 +2,15 @@
Data providers for tracks visualizations.
"""
import sys
from math import ceil, log
import sys, time
from math import ceil, log, sqrt
import pkg_resources
pkg_resources.require( "bx-python" )
if sys.version_info[:2] == (2, 4):
pkg_resources.require( "ctypes" )
pkg_resources.require( "pysam" )
pkg_resources.require( "numpy" )
import numpy
from galaxy.datatypes.util.gff_util import *
from galaxy.util.json import from_json_string
from bx.interval_index_file import Indexes
@@ -740,40 +741,73 @@ class BBIDataProvider( TracksDataProvider ):
# Bigwig has the possibility of it being a standalone bigwig file, in which case we use
# original_dataset, or coming from wig->bigwig conversion in which we use converted_dataset
f, bbi = self._get_dataset()
# If the stats kwarg was provide, we compute overall summary data for
# the entire chromosome, but no reduced data -- currently only
# providing values used by trackster to determine the default range
if 'stats' in kwargs:
all_dat = bbi.query(chrom, 0, 2147483647, 1)
# FIXME: use actual chromosome size
summary = bbi.summarize( chrom, 0, 214783647, 1 )
f.close()
if all_dat is None:
if summary is None:
return None
all_dat = all_dat[0] # only 1 summary
return { 'data' : { 'max': float( all_dat['max'] ), \
'min': float( all_dat['min'] ), \
'total_frequency': float( all_dat['coverage'] ) } \
}
else:
# Does the summary contain any defined values?
valid_count = summary.valid_count[0]
if summary.valid_count < 1:
return None
# Compute $\mu \pm 2\sigma$ to provide an estimate for upper and lower
# bounds that contain ~95% of the data.
mean = summary.sum_data[0] / valid_count
var = summary.sum_squares[0] - mean
if valid_count > 1:
var /= valid_count - 1
sd = numpy.sqrt( var )
return dict( data=dict( min=summary.min_val[0], max=summary.max_val[0], mean=mean, sd=sd ) )
start = int(start)
end = int(end)
# The following seems not to work very well, for example it will only return one
# data point if the tile is 1280px wide. Not sure what the intent is.
# The first zoom level for BBI files is 640. If too much is requested, it will look at each block instead
# of summaries. The calculation done is: zoom <> (end-start)/num_points/2.
# Thus, the optimal number of points is (end-start)/num_points/2 = 640
# num_points = (end-start) / 1280
num_points = (end-start) / 1280
if num_points < 1:
num_points = end - start
else:
num_points = min(num_points, 500)
#num_points = (end-start) / 1280
#if num_points < 1:
# num_points = end - start
#else:
# num_points = min(num_points, 500)
data = bbi.query(chrom, start, end, num_points)
# For now, we'll do 1000 data points by default However, the summaries
# don't seem to work when a summary pixel corresponds to less than one
# datapoint, so we prevent that.
# FIXME: need to switch over to using the full data at high levels of
# detail.
num_points = min( 1000, end - start )
summary = bbi.summarize( chrom, start, end, num_points )
f.close()
pos = start
step_size = (end - start) / num_points
result = []
if data:
for dat_dict in data:
result.append( (pos, float_nan(dat_dict['mean']) ) )
if summary:
mean = summary.sum_data / summary.valid_count
## Standard deviation by bin, not yet used
## var = summary.sum_squares - mean
## var /= minimum( valid_count - 1, 1 )
## sd = sqrt( var )
pos = start
step_size = (end - start) / num_points
for i in range( num_points ):
result.append( (pos, float_nan( mean[i] ) ) )
pos += step_size
return { 'data': result }
+2 -1
View File
@@ -528,6 +528,7 @@ class TracksController( BaseUIController, UsesVisualization, UsesHistoryDatasetA
if not standalone_provider.has_data( chrom ):
return messages.NO_DATA
valid_chroms = standalone_provider.valid_chroms()
# Have data if we get here
return { "status": messages.DATA, "valid_chroms": valid_chroms }
@@ -1041,4 +1042,4 @@ def _get_highest_priority_msg( message_list ):
return_message = message
elif return_message == None and message == messages.PENDING:
return_message = message
return return_message
return return_message
+4
View File
@@ -231,6 +231,10 @@ div.form-row-error-message {
padding: 3px 0 0 1em;
}
.form-row .help {
color: #666;
}
select, input, textarea {
font: inherit;
}
+1
View File
@@ -43,6 +43,7 @@ div.form-row label.inline{display:inline;}
div.form-row-input{float:left;}
div.form-row-input label{font-weight:normal;display:inline;}
div.form-row-error-message{width:300px;float:left;color:red;font-weight:bold;padding:3px 0 0 1em;}
.form-row .help{color:#666;}
select,input,textarea{font:inherit;}
select,textarea,input[type="text"],input[type="file"],input[type="password"]{-webkit-box-sizing:border-box;max-width:300px;}
.errormessagelarge,.warningmessagelarge,.donemessagelarge,.infomessagelarge{padding:10px;padding-left:52px;min-height:32px;border:1px solid #AA6666;background-color:#FFCCCC;background-image:url(error_message_icon.png);background-repeat:no-repeat;background-position:10px 10px;}
@@ -23,9 +23,11 @@ div.unified-panel-body{position:absolute;top:2em;bottom:0;width:100%;margin-top:
.panel-header-button:hover{color:black;background-color:#ccc;}
.panel-header-button:active{color:white;background-color:#aaaaaa;}
#overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:20000;}
#overlay.modal #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{border:solid #999 1px;background:white;z-index:80000;}
#overlay.modal .dialog-box .body{min-width:600px;}
.dialog-box .body{padding:5px;overflow:auto;max-height:500px;min-width:300px;}
.dialog-box .buttons{padding:5px;}
.panel-error-message,.panel-warning-message,.panel-done-message,.panel-info-message{height:24px;line-height:24px;color:#303030;padding:0px;padding-left:26px;background-color:#FFCCCC;background-image:url(error_small.png);background-repeat:no-repeat;background-position:6px 50%;}
+28 -25
View File
@@ -3,30 +3,9 @@
.content{font:10px verdana;}
.nav-controls{text-align:center;padding:1px 0;}
.nav-controls input{margin:0 5px;}
.menu-button{padding: 0px 4px 0px 4px;}
#zoom-in,#zoom-out{display:inline-block;height:16px;width:16px;margin-bottom:-3px;cursor:pointer;}
#zoom-out{background:transparent url(../images/fugue/magnifier-zoom-out.png) center center no-repeat;}
#zoom-in{margin-left:10px;background:transparent url(../images/fugue/magnifier-zoom.png) center center no-repeat;}
.overview-icon{background:transparent url(../images/fugue/application-dock-270-bw.png) no-repeat;}
.overview-icon:hover{background:transparent url(../images/fugue/application-dock-270.png) no-repeat;}
.settings-icon{background:transparent url(../images/fugue/gear-bw.png) no-repeat;}
.settings-icon:hover{background:transparent url(../images/fugue/gear.png) no-repeat;}
.tools-icon{background:transparent url(../images/fugue/toolbox-bw.png) no-repeat;}
.tools-icon:hover{background:transparent url(../images/fugue/toolbox.png) no-repeat;}
.filters-icon{background:transparent url(../images/fugue/ui-slider-050-bw.png) no-repeat;}
.filters-icon:hover{background:transparent url(../images/fugue/ui-slider-050.png) no-repeat;}
.remove-icon,.overview-close{background:transparent url(../images/fugue/cross-small-bw.png) no-repeat;}
.remove-icon:hover,.overview-close:hover{background:transparent url(../images/fugue/cross-circle.png) no-repeat;}
#close-icon{background:transparent url(../images/fugue/cross-circle-bw.png) no-repeat;margin-right:0px;}
#close-icon:hover{background:transparent url(../images/fugue/cross-circle.png) no-repeat;}
#add-tracks-icon{background:transparent url(../images/fugue/plus-button-bw.png) no-repeat;}
#add-tracks-icon:hover{background:transparent url(../images/fugue/plus-button.png) no-repeat;}
#add-group-icon{background:transparent url(../images/fugue/block--plus-bw.png) no-repeat;}
#add-group-icon:hover{background:transparent url(../images/fugue/block--plus.png) no-repeat;}
#bookmarks-icon{background:transparent url(../images/fugue/bookmarks-bw.png) no-repeat;}
#bookmarks-icon:hover{background:transparent url(../images/fugue/bookmarks.png) no-repeat;}
#save-icon{background:transparent url(../images/fugue/disk--arrow-bw.png) no-repeat;}
#save-icon:hover{background:transparent url(../images/fugue/disk--arrow.png) no-repeat;}
.nav-input{font-size:12px;width:30em;z-index:1000;}
.location{display:inline-block;width:15em;margin:0 10px;}
.draghandle{margin-top:2px;cursor:move;float:left;background:transparent url(../images/visualization/draggable_horizontal.png) center center no-repeat;width:10px;height:12px;}
@@ -65,16 +44,39 @@
input{font:10px verdana;}
.dynamic-tool,.filters{margin-left:0.25em;padding-bottom:0.5em;}
.dynamic-tool{width:410px;}
.filters>.sliders,.display-controls{float:left;margin:1em;}
.filters > .sliders,.display-controls{float:left;margin:1em;}
.sliders{width:410px;}
.display-controls{border-left:solid 2px #DDDDDD;padding-left:1em}
.filter-row{margin-top:0.4em;}
.display-controls{border-left:solid 2px #DDDDDD;padding-left:1em
}
.filter-row {
margin-top:0.4em;}
.slider-row{margin-left:1em;}
.elt-label{float:left;font-weight:bold;margin-right:1em;}
.slider{float:right;width:200px;position:relative;}
.tool-name{font-size:110%;font-weight:bold;}
.param-row{margin-top:0.2em;margin-left:1em;}
.param-label{float:left;font-weight:bold;padding-top:0.2em;}
.menu-button{padding:0px 4px 0px 4px;}
.settings-icon{background:transparent url(../images/fugue/gear-bw.png) no-repeat;}
.settings-icon:hover{background:transparent url(../images/fugue/gear.png) no-repeat;}
.overview-icon{background:transparent url(../images/fugue/application-dock-270-bw.png) no-repeat;}
.overview-icon:hover{background:transparent url(../images/fugue/application-dock-270.png) no-repeat;}
.tools-icon{background:transparent url(../images/fugue/toolbox-bw.png) no-repeat;}
.tools-icon:hover{background:transparent url(../images/fugue/toolbox.png) no-repeat;}
.filters-icon{background:transparent url(../images/fugue/ui-slider-050-bw.png) no-repeat;}
.filters-icon:hover{background:transparent url(../images/fugue/ui-slider-050.png) no-repeat;}
.remove-icon,.overview-close{background:transparent url(../images/fugue/cross-small-bw.png) no-repeat;}
.remove-icon:hover,.overview-close:hover{background:transparent url(../images/fugue/cross-circle.png) no-repeat;}
#close-icon{background:transparent url(../images/fugue/cross-circle-bw.png) no-repeat;margin-right:0px;}
#close-icon:hover{background:transparent url(../images/fugue/cross-circle.png) no-repeat;}
#add-tracks-icon{background:transparent url(../images/fugue/plus-button-bw.png) no-repeat;}
#add-tracks-icon:hover{background:transparent url(../images/fugue/plus-button.png) no-repeat;}
#add-group-icon{background:transparent url(../images/fugue/block--plus-bw.png) no-repeat;}
#add-group-icon:hover{background:transparent url(../images/fugue/block--plus.png) no-repeat;}
#bookmarks-icon{background:transparent url(../images/fugue/bookmarks-bw.png) no-repeat;}
#bookmarks-icon:hover{background:transparent url(../images/fugue/bookmarks.png) no-repeat;}
#save-icon{background:transparent url(../images/fugue/disk--arrow-bw.png) no-repeat;}
#save-icon:hover{background:transparent url(../images/fugue/disk--arrow.png) no-repeat;}
.child-track-icon{background:url('../images/fugue/arrow-000-small-bw.png') no-repeat;width:30px;cursor:move;}
.track-resize{background:white url('../images/visualization/draggable_vertical.png') no-repeat top center;position:absolute;right:3px;bottom:-4px;width:14px;height:7px;border:solid #999 1px;z-index:100;}
.bookmark{background:white;border:solid #999 1px;border-right:none;margin:0.5em;margin-right:0;padding:0.5em;}
@@ -85,4 +87,5 @@ input{font:10px verdana;}
.icon.more-across{background:url('../images/fugue/arrow-transition-bw.png') no-repeat 0px 0px;}
.intro{padding:1em;}
.intro > .action-button{background-color:#CCC;padding:1em;}
.feature-popup{background-color:#DDD;position:absolute;z-index:1000}
.feature-popup{position:absolute;z-index:1000;padding:5px;font-size:10px;filter:alpha(opacity=80);background-repeat:no-repeat;background-image:url(../images/tipsy.gif);background-position:top center;}
.feature-popup-inner{padding:5px 8px 4px 8px;background-color:black;color:white;}
@@ -161,6 +161,10 @@ div.unified-panel-body {
z-index: 20000;
}
#overlay.modal #overlay-background {
background: rgba(0,0,0,0.5);
}
.dialog-box-container {
position: relative;
margin-top: 80px;
@@ -182,6 +186,10 @@ div.unified-panel-body {
z-index: 80000;
}
#overlay.modal .dialog-box .body {
min-width: 600px;
}
.dialog-box .body {
padding: 5px;
overflow: auto;
+14 -3
View File
@@ -405,8 +405,19 @@ input {
background-color: #CCC;
padding: 1em;
}
.feature-popup{
background-color: #DDD;
.feature-popup {
position: absolute;
z-index: 1000
z-index: 1000;
padding: 5px;
font-size: 10px;
filter: alpha(opacity=80);
background-repeat: no-repeat;
background-image: url(../images/tipsy.gif);
background-position: top center;
}
.feature-popup-inner {
padding: 5px 8px 4px 8px;
background-color: black;
color: white;
}
+13 -3
View File
@@ -163,13 +163,23 @@ function make_right_panel( panel_el, center_el, border_el ) {
// Modal dialog boxes
function hide_modal() {
$(".dialog-box-container" ).fadeOut( function() {
$(".dialog-box-container" ).hide( 0, function() {
$("#overlay").hide();
$("#overlay").removeClass( "modal" );
$( ".dialog-box" ).find( ".body" ).children().remove();
} );
};
function show_modal( title, body, buttons, extra_buttons, init_fn ) {
function show_modal() {
$("#overlay").addClass( "modal" );
_show_modal.apply( this, arguments );
}
function show_message() {
_show_modal.apply( this, arguments );
}
function _show_modal( title, body, buttons, extra_buttons, init_fn ) {
if ( title ) {
$( ".dialog-box" ).find( ".title" ).html( title );
$( ".dialog-box" ).find( ".unified-panel-header" ).show();
@@ -205,7 +215,7 @@ function show_modal( title, body, buttons, extra_buttons, init_fn ) {
$( ".dialog-box" ).find( ".body" ).html( body );
if ( ! $(".dialog-box-container").is( ":visible" ) ) {
$("#overlay").show();
$(".dialog-box-container").fadeIn();
$(".dialog-box-container").show();
}
// Fix min-width so that modal cannot shrink considerably if
// new content is loaded.
+204 -39
View File
@@ -701,7 +701,7 @@ extend(Drawable.prototype, {
remove: function() {
this.container.remove_drawable(this);
this.container_div.fadeOut('slow', function() {
this.container_div.hide(0, function() {
$(this).remove();
// HACK: is there a better way to update the view?
view.update_intro_div();
@@ -1027,6 +1027,7 @@ extend( View.prototype, DrawableCollection.prototype, {
// Only act on x axis scrolling if we see if, y will be i
// handled by the browser when the event bubbles up
if ( dx ) {
dx *= 50;
var delta_chrom = Math.round( - dx / view.viewport_container.width() * (view.high - view.low) );
view.move_delta( delta_chrom );
}
@@ -1255,7 +1256,7 @@ extend( View.prototype, DrawableCollection.prototype, {
DrawableCollection.prototype.remove_drawable.call(this, drawable);
if (hide) {
var view = this;
drawable.container_div.fadeOut('slow', function() {
drawable.container_div.hide(0, function() {
$(this).remove();
view.update_intro_div();
});
@@ -2114,8 +2115,14 @@ extend(DrawableConfig.prototype, {
build_form: function() {
var track_config = this;
var container = $("<div />");
$.each( this.params, function( index, param ) {
if ( ! param.hidden ) {
var param;
// Function to process parameters recursively
function handle_params( params, container ) {
for ( var index = 0; index < params.length; index++ ) {
param = params[index];
// Hidden params have no representation in the form
if ( param.hidden ) { continue; }
// Build row for param
var id = 'param_' + index;
var value = track_config.values[ param.key ];
var row = $("<div class='form-row' />").appendTo( container );
@@ -2127,18 +2134,19 @@ extend(DrawableConfig.prototype, {
} else if ( param.type === 'color' ) {
var input = $('<input />').attr("id", id ).attr("name", id ).val( value );
// Color picker in tool tip style float
var tip = $( "<div class='tipsy tipsy-north' style='position: absolute;' />" ).hide();
var tip = $( "<div class='tipsy tipsy-west' style='position: absolute;' />" ).hide();
// Inner div for padding purposes
var tip_inner = $("<div style='background-color: black; padding: 10px;'></div>").appendTo(tip);
var farb_container = $("<div/>")
.appendTo(tip_inner)
.farbtastic( { width: 100, height: 100, callback: input, color: value });
// Outer div container input and tip for hover to work
$("<div />").append( input ).append( tip ).appendTo( row ).bind( "click", function ( e ) {
tip.css( {
left: $(this).position().left + ( $(input).width() / 2 ) - 60,
top: $(this).position().top + $(this.height)
// left: $(this).position().left + ( $(input).width() / 2 ) - 60,
// top: $(this).position().top + $(this.height)
left: $(this).position().left + $(input).width() + 5,
top: $(this).position().top - ( $(tip).height() / 2 ) + ( $(input).height() / 2 )
} ).show();
$(document).bind( "click.color-picker", function() {
tip.hide();
@@ -2150,8 +2158,15 @@ extend(DrawableConfig.prototype, {
else {
row.append( $('<input />').attr("id", id ).attr("name", id ).val( value ) );
}
// Help text
if ( param.help ) {
row.append( $("<div class='help'/>").text( param.help ) );
}
}
});
}
// Handle top level parameters in order
handle_params( this.params, container );
// Return element containing constructed form
return container;
},
update_from_form: function( container ) {
@@ -2229,7 +2244,16 @@ FeatureTrackTile.prototype.predisplay_actions = function() {
// Only show popups in Pack mode.
if (tile.mode !== "Pack") { return; }
$(this.canvas).mousemove(function (e) {
$(this.canvas).hover( function() {
this.hovered = true;
$(this).mousemove();
}, function() {
this.hovered = false;
// Clear popup if it is still hanging around (this is probably not needed)
$(this).siblings(".feature-popup").remove();
} ).mousemove(function (e) {
// Use the hover plugin to get a delay before showing popup
if ( !this.hovered ) { return; }
// Get feature data for position.
var
this_offset = $(this).offset(),
@@ -2270,8 +2294,8 @@ FeatureTrackTile.prototype.predisplay_actions = function() {
// Build popup.
var popup = $("<div/>").attr("id", feature_uid).addClass("feature-popup"),
key, value,
table = $("<table/>").appendTo(popup), row;
table = $("<table/>"),
key, value, row;
for (key in feature_dict) {
value = feature_dict[key];
row = $("<tr/>").appendTo(table);
@@ -2279,6 +2303,7 @@ FeatureTrackTile.prototype.predisplay_actions = function() {
$("<td/>").attr("align", "left").appendTo(row)
.text(typeof(value) == 'number' ? round(value, 2) : value);
}
popup.append( $("<div class='feature-popup-inner'>").append( table ) );
popups[feature_uid] = popup;
}
@@ -2289,7 +2314,7 @@ FeatureTrackTile.prototype.predisplay_actions = function() {
// parseInt strips "px" from left, top measurements. +7 so that mouse pointer does not
// overlap popup.
var
popupX = offsetX + parseInt( tile.canvas.css("left") ) + 7,
popupX = offsetX + parseInt( tile.canvas.css("left") ) - popup.width() / 2,
popupY = offsetY + parseInt( tile.canvas.css("top") ) + 7;
popup.css("left", popupX + "px").css("top", popupY + "px")
}
@@ -2331,6 +2356,12 @@ var Track = function(name, view, container, show_header, prefs, data_url, data_q
this.data_url_extra_params = {}
this.data_query_wait = (data_query_wait ? data_query_wait : DEFAULT_DATA_QUERY_WAIT);
this.dataset_check_url = converted_datasets_state_url;
// FIXME: this should be a saved setting
this.content_visible = true;
if (!Track.id_counter) { Track.id_counter = 0; }
this.id = Track.id_counter++;
//
// Create HTML element structure for track.
@@ -2346,6 +2377,9 @@ var Track = function(name, view, container, show_header, prefs, data_url, data_q
this.icons_div = $("<div/>").css("float", "left").appendTo(this.header_div).hide();
// Track icons.
this.toggle_icon = $("<a/>").attr("href", "javascript:void(0);").attr("title", "Hide/show track content")
.addClass("icon-button toggle").tipsy( {gravity: 's'} )
.appendTo(this.icons_div);
this.settings_icon = $("<a/>").attr("href", "javascript:void(0);").attr("title", "Edit settings")
.addClass("icon-button settings-icon").tipsy( {gravity: 's'} )
.appendTo(this.icons_div);
@@ -2365,6 +2399,21 @@ var Track = function(name, view, container, show_header, prefs, data_url, data_q
// Suppress double clicks in header so that they do not impact viz.
this.header_div.dblclick( function(e) { e.stopPropagation(); } );
// Toggle icon hides or shows the track content
this.toggle_icon.click( function() {
if ( track.content_visible ) {
track.toggle_icon.addClass("toggle-expand").removeClass("toggle");
track.hide_contents();
track.mode_div.hide();
track.content_visible = false;
} else {
track.toggle_icon.addClass("toggle").removeClass("toggle-expand");
track.content_visible = true;
track.mode_div.show();
track.show_contents();
}
});
// Clicking on settings icon opens track config.
this.settings_icon.click( function() {
@@ -2551,6 +2600,26 @@ extend(Track.prototype, Drawable.prototype, {
this.update_icons();
},
/**
* Hide any elements that are part of the tracks contents area. Should
* remove as approprite, the track will be redrawn by show_contents.
*/
hide_contents : function () {
// Clear contents by removing any elements that are contained in
// the tracks content_div
this.content_div.children().remove();
// Hide the content div
this.content_div.hide();
// And any y axis labels (common to several track types)
this.container_div.find(".yaxislabel, .track-resize").hide()
},
show_contents : function() {
// Show the contents div and labels (if present)
this.content_div.show();
this.container_div.find(".yaxislabel, .track-resize").show()
// Request a redraw of the content
this.request_draw();
},
/**
* Additional initialization required before drawing track for the first time.
*/
@@ -2682,6 +2751,11 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
*/
_draw: function(force, clear_after) {
if (!this.enabled) { return; }
// TODO: There should probably be a general way to disable content drawing
// for all drawables. However the button to toggle this is currently
// only present for Track instances.
if (!this.content_visible) { return; }
// HACK: ReferenceTrack can draw without dataset ID, but other tracks cannot.
if ( !(this instanceof ReferenceTrack) && (!this.dataset_id) ) { return; }
@@ -2694,7 +2768,7 @@ extend(TiledTrack.prototype, Drawable.prototype, Track.prototype, {
w_scale = width / range,
resolution = this.view.resolution,
parent_element = $("<div style='position: relative;'></div>");
// For overview, adjust high, low, resolution, and w_scale.
if (this.is_overview) {
low = this.view.max_low;
@@ -3028,7 +3102,8 @@ var LineTrack = function (name, view, container, hda_ldda, dataset_id, prefs) {
this.min_height_px = 16;
this.max_height_px = 400;
this.height_px = 80;
// Default height for new tracks, should be a defined constant?
this.height_px = 32;
this.hda_ldda = hda_ldda;
this.dataset_id = dataset_id;
this.original_dataset_id = dataset_id;
@@ -3076,8 +3151,10 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
var drag_control = $( "<div class='track-resize'>" )
// Control shows on hover over track, stays while dragging
$(track.container_div).hover( function() {
in_handle = true;
drag_control.show();
if ( track.content_visible ) {
in_handle = true;
drag_control.show();
}
}, function() {
in_handle = false;
if ( ! in_drag ) { drag_control.hide(); }
@@ -3108,9 +3185,20 @@ extend(LineTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
track.container_div.addClass( "line-track" );
var data = result.data;
if ( isNaN(parseFloat(track.prefs.min_value)) || isNaN(parseFloat(track.prefs.max_value)) ) {
track.prefs.min_value = data.min;
track.prefs.max_value = data.max;
// Compute default minimum and maximum values
var min_value = data.min
var max_value = data.max
// If mean and sd are present, use them to compute a ~95% window
// but only if it would shrink the range on one side
min_value = Math.floor( Math.min( 0, Math.max( min_value, data.mean - 2 * data.sd ) ) )
max_value = Math.ceil( Math.max( 0, Math.min( max_value, data.mean + 2 * data.sd ) ) )
// Update the prefs
track.prefs.min_value = min_value;
track.prefs.max_value = max_value;
// Update the config
// FIXME: we should probably only save this when the user explicately sets it
// since we lose the ability to compute it on the fly (when changing
// chromosomes for example).
$('#track_' + track.dataset_id + '_minval').val(track.prefs.min_value);
$('#track_' + track.dataset_id + '_maxval').val(track.prefs.max_value);
}
@@ -3179,7 +3267,8 @@ var FeatureTrack = function(name, view, container, hda_ldda, dataset_id, prefs,
{ key: 'name', label: 'Name', type: 'text', default_value: name },
{ key: 'block_color', label: 'Block color', type: 'color', default_value: get_random_color() },
{ key: 'label_color', label: 'Label color', type: 'color', default_value: 'black' },
{ key: 'show_counts', label: 'Show summary counts', type: 'bool', default_value: true },
{ key: 'show_counts', label: 'Show summary counts', type: 'bool', default_value: true,
help: 'Show the number of items in each bin when drawing summary histogram' },
{ key: 'mode', type: 'string', default_value: this.mode, hidden: true },
],
saved_values: prefs,
@@ -3527,7 +3616,7 @@ extend(FeatureTrack.prototype, Drawable.prototype, TiledTrack.prototype, {
var filter_height_scaler = (this.filters_manager.height_filter ? new FilterScaler(this.filters_manager.height_filter) : null);
// HACK: ref_seq will only be defined for ReadTracks, and only the ReadPainter accepts that argument
var painter = new (this.painter)(filtered, tile_low, tile_high, this.prefs, mode, filter_alpha_scaler, filter_height_scaler, ref_seq);
var required_height = Math.max(MIN_TRACK_HEIGHT, painter.get_required_height(slots_required));
var required_height = Math.max(MIN_TRACK_HEIGHT, painter.get_required_height(slots_required,width));
var canvas = this.view.canvas_manager.new_canvas();
var feature_mapper = null;
@@ -4057,6 +4146,7 @@ var FeaturePositionMapper = function(slot_height) {
this.feature_positions = {};
this.slot_height = slot_height;
this.translation = 0;
this.y_translation = 0;
};
/**
@@ -4078,7 +4168,7 @@ FeaturePositionMapper.prototype.map_feature_data = function(feature_data, slot,
*/
FeaturePositionMapper.prototype.get_feature_data = function(x, y) {
// Find slot using Y.
var slot = Math.floor( y/this.slot_height ),
var slot = Math.floor( (y-this.y_translation)/this.slot_height ),
feature_dict;
// May not be over a slot due to padding, margin, etc.
@@ -4108,15 +4198,23 @@ var FeaturePainter = function(data, view_start, view_end, prefs, mode, alpha_sca
FeaturePainter.prototype.default_prefs = { block_color: "#FFF", connector_color: "#FFF" };
extend(FeaturePainter.prototype, {
get_required_height: function(rows_required) {
get_required_height: function(rows_required, width) {
// y_scale is the height per row
var required_height = y_scale = this.get_row_height(), mode = this.mode;
// If using a packing mode, need to multiply by the number of slots used
if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
required_height = rows_required * y_scale;
}
return required_height + this.get_top_padding(width) + this.get_bottom_padding(width);
},
/** Extra padding before first row of features */
get_top_padding: function(width) {
return 0;
},
/** Extra padding after last row of features */
get_bottom_padding: function(width) {
// Pad bottom by half a row, at least 5 px
return required_height + Math.max( Math.round( y_scale / 2 ), 5 );
return Math.max( Math.round( this.get_row_height() / 2 ), 5 )
},
/**
* Draw data on ctx using slots and within the rectangle defined by width and height. Returns
@@ -4153,6 +4251,7 @@ extend(FeaturePainter.prototype, {
}
ctx.restore();
feature_mapper.y_translation = this.get_top_padding(width);
return feature_mapper;
},
/**
@@ -4179,6 +4278,10 @@ var DENSE_TRACK_HEIGHT = 10,
var LinkedFeaturePainter = function(data, view_start, view_end, prefs, mode, alpha_scaler, height_scaler) {
FeaturePainter.call(this, data, view_start, view_end, prefs, mode, alpha_scaler, height_scaler);
// Whether to draw a single connector in the background that spans the entire feature (the intron fishbone)
this.draw_background_connector = true;
// Whether to call draw_connector for every pair of blocks
this.draw_individual_connectors = false;
};
extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
@@ -4216,7 +4319,7 @@ extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ),
draw_start = f_start,
draw_end = f_end,
y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale + this.get_top_padding(width),
thickness, y_start, thick_start = null, thick_end = null,
// TODO: is there any reason why block, label color cannot be set at the Painter level?
block_color = this.prefs.block_color,
@@ -4279,38 +4382,51 @@ extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
// needed. This ensures that whole feature, regardless of whether it starts with
// a block, is visible.
//
// Draw whole feature as connector/intron.
// Compute y axis center position and height
var cur_y_center, cur_height;
if (mode === "Squish" || mode === "Dense") {
ctx.fillStyle = CONNECTOR_COLOR;
cur_y_center = y_center + Math.floor(SQUISH_FEATURE_HEIGHT/2) + 1;
cur_height = 1;
}
else { // mode === "Pack"
if (feature_strand) {
var cur_y_center = y_center;
var cur_height = thick_height;
if (feature_strand === "+") {
ctx.fillStyle = ctx.canvas.manager.get_pattern( 'right_strand' );
} else if (feature_strand === "-") {
ctx.fillStyle = ctx.canvas.manager.get_pattern( 'left_strand' );
}
cur_y_center = y_center;
cur_height = thick_height;
}
else {
ctx.fillStyle = CONNECTOR_COLOR;
cur_y_center += (SQUISH_FEATURE_HEIGHT/2) + 1;
cur_height = 1;
}
}
ctx.fillRect(f_start, cur_y_center, f_end - f_start, cur_height);
// Draw whole feature as connector/intron.
if ( this.draw_background_connector ) {
if (mode === "Squish" || mode === "Dense") {
ctx.fillStyle = CONNECTOR_COLOR;
}
else { // mode === "Pack"
if (feature_strand) {
if (feature_strand === "+") {
ctx.fillStyle = ctx.canvas.manager.get_pattern( 'right_strand' );
} else if (feature_strand === "-") {
ctx.fillStyle = ctx.canvas.manager.get_pattern( 'left_strand' );
}
}
else {
ctx.fillStyle = CONNECTOR_COLOR;
}
}
ctx.fillRect(f_start, cur_y_center, f_end - f_start, cur_height);
}
// Draw blocks.
var start_and_height;
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) ),
block_end = Math.ceil( Math.min(width, Math.max((block[1] - tile_low) * w_scale)) );
block_end = Math.ceil( Math.min(width, Math.max((block[1] - tile_low) * w_scale)) ),
last_block_start, last_block_end;
// Skip drawing if block not on tile.
if (block_start > block_end) { continue; }
@@ -4341,6 +4457,12 @@ extend(LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
ctx.fillRect(block_thick_start, y_center + 1, block_thick_end - block_thick_start, thick_height );
}
}
// Draw individual connectors if required
if ( this.draw_individual_connectors && last_block_start ) {
this.draw_connector( ctx, last_block_start, last_block_end, block_start, block_end, y_center );
}
last_block_start = block_start;
last_block_end = block_end;
}
// FIXME: Height scaling only works in Pack mode right now.
@@ -4673,11 +4795,54 @@ extend(ReadPainter.prototype, FeaturePainter.prototype, {
}
});
var ArcLinkedFeaturePainter = function(data, view_start, view_end, prefs, mode, alpha_scaler, height_scaler) {
LinkedFeaturePainter.call(this, data, view_start, view_end, prefs, mode, alpha_scaler, height_scaler);
// Need to know the longest feature length for adding spacing
this.longest_feature_length = this.calculate_longest_feature_length();
this.draw_background_connector = false;
this.draw_individual_connectors = true;
};
extend(ArcLinkedFeaturePainter.prototype, FeaturePainter.prototype, LinkedFeaturePainter.prototype, {
calculate_longest_feature_length: function () {
var longest_feature_length = 0;
for (var i = 0, len = this.data.length; i < len; i++) {
var feature = this.data[i], feature_start = feature[1], feature_end = feature[2];
longest_feature_length = Math.max( longest_feature_length, feature_end - feature_start );
}
return longest_feature_length;
},
get_top_padding: function( width ) {
var view_range = this.view_end - this.view_start,
w_scale = width / view_range;
return Math.min( 128, Math.ceil( ( this.longest_feature_length / 2 ) * w_scale ) );
},
draw_connector: function( ctx, block1_start, block1_end, block2_start, block2_end, y_center ) {
// Arc drawing -- from closest endpoints
var x_center = ( block1_end + block2_start ) / 2,
radius = block2_start - x_center;
// For full half circles
var angle1 = Math.PI, angle2 = 0;
if ( radius > 0 ) {
ctx.beginPath();
ctx.arc( x_center, y_center, block2_start - x_center, Math.PI, 0 );
ctx.stroke();
}
}
});
exports.Scaler = Scaler;
exports.SummaryTreePainter = SummaryTreePainter;
exports.LinePainter = LinePainter;
exports.LinkedFeaturePainter = LinkedFeaturePainter;
exports.ReadPainter = ReadPainter;
exports.ArcLinkedFeaturePainter = ArcLinkedFeaturePainter;
// End painters_module encapsulation
};
@@ -4708,4 +4873,4 @@ exports.ReadPainter = ReadPainter;
for ( key in modules.trackster ) {
target[key] = modules.trackster[key];
}
})(window);
})(window);
+1 -1
View File
@@ -42,7 +42,7 @@ ${parent.javascripts()}
<script type='text/javascript' src="${h.url_for('/static/scripts/excanvas.js')}"></script>
<![endif]-->
${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.event.drag", "jquery.mousewheel", "jquery.autocomplete", "trackster", "trackster_ui", "jquery.ui.sortable.slider", "farbtastic", "jquery.tipsy" )}
${h.js( "galaxy.base", "galaxy.panels", "json2", "jquery", "jstorage", "jquery.event.drag", "jquery.event.hover","jquery.mousewheel", "jquery.autocomplete", "trackster", "trackster_ui", "jquery.ui.sortable.slider", "farbtastic", "jquery.tipsy" )}
<script type="text/javascript">
//
+2 -2
View File
@@ -213,7 +213,7 @@
show_workflow_parameters();
},
beforeSubmit: function( data ) {
show_modal( "Loading workflow", "progress" );
show_message( "Loading workflow", "progress" );
}
});
}
@@ -650,7 +650,7 @@
};
var save_current_workflow = function ( eventObj, success_callback ) {
show_modal( "Saving workflow", "progress" );
show_message( "Saving workflow", "progress" );
workflow.check_changes_in_active_form();
if (!workflow.has_changes) {
hide_modal();