Fixing up track browser UI. No longer uses session, cleaned up a bunch of unceccesary stuff. Still pretty limited -- coverage indexing doesn't seem to work right

This commit is contained in:
James Taylor
2009-06-11 15:37:19 -04:00
parent 537f8952ef
commit 74db2397c5
12 changed files with 218 additions and 372 deletions
+81 -94
View File
@@ -1,113 +1,96 @@
"""
Support for constructing and viewing custom "track" browsers within Galaxy.
Track browsers are currently transient -- nothing is stored to the database
when a browser is created. Building a browser consists of selecting a set
of datasets associated with the same dbkey to display. Once selected, jobs
are started to create any neccesary indexes in the background, and the user
is redirected to the browser interface, which loads the appropriate datasets.
Problems
--------
- Assumes that the only indexing type in Galaxy is for this particular
application. Thus, datatypes can only have one indexer, and the presence
of an indexer results in assuming that datatype can be displayed as a track.
"""
import math
import mimeparse
from galaxy.tracks import messages
from galaxy.util.json import to_json_string
from galaxy.web.base.controller import *
from galaxy.web.framework import simplejson
class MultiResponse(object):
class TracksController( BaseController ):
"""
Shamelessly ripped off of a django snippet.
Controller for track browser interface. Handles building a new browser from
datasets in the current history, and display of the resulting browser.
"""
def __init__(self, handlers):
self.handlers = handlers
def __call__(self, view_func):
def wrapper(that, trans, *args, **kwargs):
data_resource = view_func(that, trans, *args, **kwargs)
content_type = mimeparse.best_match(self.handlers.keys(),
trans.request.environ['HTTP_ACCEPT'])
response = self.handlers[content_type](data_resource, trans)
trans.response.headers['Content-Type'] = "%s" % content_type
return response
return wrapper
@classmethod
def JSON( cls, data_resource, trans ):
return simplejson.dumps( data_resource )
class XML( object ):
def __call__(self, data_resource, trans ):
raise NotImplementedError( "XML MultiResponse handler is not implemented." )
class AMF( object ):
def __call__(self, data_resource, trans ):
raise NotImplementedError( "XML MultiResponse handler is not implemented." )
class HTML( object ):
def __init__(self, template ):
self.template = template
def __call__(self, data_resource, trans ):
return trans.fill_template( self.template, data_resource=data_resource, trans=trans )
class WebRoot( BaseController ):
@web.expose
@MultiResponse( {'text/html': MultiResponse.HTML( "tracks/dbkeys.mako"),
'text/javascript':MultiResponse.JSON} )
def dbkeys(self, trans ):
return list(set([x.metadata.dbkey for x in trans.get_history().datasets if not x.deleted]))
@web.expose
@MultiResponse( {'text/html':MultiResponse.HTML( "tracks/chroms.mako" ),
'text/javascript':MultiResponse.JSON} )
def chroms(self, trans, dbkey=None):
return self.chroms_handler( trans, dbkey )
@web.expose
@MultiResponse( {'text/html':MultiResponse.HTML( "tracks/datasets.mako" ),
'text/javascript':MultiResponse.JSON} )
def list(self, trans, dbkey=None ):
trans.session["track_dbkey"] = dbkey
trans.session.save()
datasets = trans.app.model.HistoryDatasetAssociation.filter_by(deleted=False, history_id=trans.history.id).all()
dataset_list = {}
for dataset in datasets:
if dataset.metadata.dbkey == dbkey and trans.app.datatypes_registry.get_indexers_by_datatype( dataset.extension ):
dataset_list[dataset.id] = dataset.name
return dataset_list
@web.expose
@MultiResponse( {'text/html':MultiResponse.JSON,
'text/javascript':MultiResponse.JSON} )
def data(self, trans, dataset_id=None, chr="", low="", high=""):
return self.data_handler( trans, dataset_id, chrom=chr, low=low, high=high )
@web.expose
def build( self, trans, **kwargs ):
trans.session["track_sets"] = list(kwargs.keys())
trans.session.save()
#waiting = False
#for id, value in kwargs.items():
# status = self.data_handler( trans, id )
# if status == messages.PENDING:
# waiting = True
#if not waiting:
return trans.response.send_redirect( web.url_for( controller='tracks/', action='index', chrom="" ) )
#return trans.fill_template( 'tracks/build.mako' )
def index( self, trans ):
return trans.fill_template( "tracks/index.mako" )
@web.expose
def index(self, trans, **kwargs):
def new_browser( self, trans, dbkey=None, dataset_ids=None, browse=None ):
"""
Build a new browser from datasets in the current history. Redirects
to 'index' once datasets to browse have been selected.
"""
session = trans.sa_session
# If the user clicked the submit button explicately, try to build the browser
if browse and dataset_ids:
dataset_ids = ",".join( map( str, dataset_ids ) )
trans.response.send_redirect( web.url_for( controller='tracks', action='browser', chrom="", dataset_ids=dataset_ids ) )
return
# Determine the set of all dbkeys that are used in the current history
dbkeys = [ d.metadata.dbkey for d in trans.get_history().datasets if not d.deleted ]
dbkey_set = set( dbkeys )
# If a dbkey argument was not provided, or is no longer valid, default
# to the first one
if dbkey is None or dbkey not in dbkey_set:
dbkey = dbkeys[0]
# Find all datasets in the current history that are of that dbkey and
# have an indexer.
datasets = {}
for dataset in session.query( model.HistoryDatasetAssociation ).filter_by( deleted=False, history_id=trans.history.id ):
if dataset.metadata.dbkey == dbkey and trans.app.datatypes_registry.get_indexers_by_datatype( dataset.extension ):
datasets[dataset.id] = dataset.name
# Render the template
return trans.fill_template( "tracks/new_browser.mako", dbkey=dbkey, dbkey_set=dbkey_set, datasets=datasets )
@web.expose
def browser(self, trans, dataset_ids, chrom=""):
"""
Display browser for the datasets listed in `dataset_ids`.
"""
tracks = []
dbkey = ""
for track in trans.session["track_sets"]:
dataset = trans.app.model.HistoryDatasetAssociation.get( track )
tracks.append({
"type": dataset.datatype.get_track_type(),
"name": dataset.name,
"id": dataset.id
})
for dataset_id in dataset_ids.split( "," ):
dataset = trans.app.model.HistoryDatasetAssociation.get( dataset_id )
tracks.append( {
"type": dataset.datatype.get_track_type(),
"name": dataset.name,
"id": dataset.id
} )
dbkey = dataset.dbkey
chrom = kwargs.get("chrom","")
LEN = self.chroms_handler(trans, trans.session["track_dbkey"]).get(chrom,0)
return trans.fill_template( 'tracks/index.mako',
tracks=tracks, chrom=chrom, dbkey=dbkey,
LEN = self._chroms(trans, dbkey ).get(chrom,0)
return trans.fill_template( 'tracks/browser.mako',
dataset_ids=dataset_ids,
tracks=tracks,
chrom=chrom,
dbkey=dbkey,
LEN=LEN )
def chroms_handler(self, trans, dbkey ):
@web.json
def chroms(self, trans, dbkey=None ):
return self._chroms( trans, dbkey )
def _chroms( self, trans, dbkey ):
"""
Called by the browser to get a list of valid chromosomes and lengths
"""
db_manifest = trans.db_dataset_for( dbkey )
if not db_manifest:
db_manifest = os.path.join( trans.app.config.tool_data_path, 'shared','ucsc','chrom', "%s.len" % dbkey )
@@ -134,7 +117,11 @@ class WebRoot( BaseController ):
pass
return manifest
def data_handler( self, trans, dataset_id, chrom="", low="", high="" ):
@web.json
def data( self, trans, dataset_id, chrom="", low="", high="" ):
"""
Called by the browser to request a block of data
"""
dataset = trans.app.model.HistoryDatasetAssociation.get( dataset_id )
if not dataset: return messages.NO_DATA
if dataset.state == trans.app.model.Job.states.ERROR:
+2 -1
View File
@@ -155,7 +155,7 @@ $.extend( DataCache.prototype, {
// use closure to preserve this and parameters for getJSON
var fetcher = function (ref) {
return function () {
$.getJSON( "data" + ref.type, { chr: ref.view.chr, low: low, high: high, dataset_id: ref.track.dataset_id }, function ( data ) {
$.getJSON( TRACKSTER_DATA_URL + ref.type, { chrom: ref.view.chr, low: low, high: high, dataset_id: ref.track.dataset_id }, function ( data ) {
if( data == "pending" ) {
setTimeout( fetcher, 5000 );
} else {
@@ -218,6 +218,7 @@ $.extend( LineTrack.prototype, TiledTrack.prototype, {
var y1 = data[i][1];
var x2 = data[i+1][0] - tile_low;
var y2 = data[i+1][1];
console.log( x1, y1, x2, y2 );
// Missing data causes us to stop drawing
if ( isNaN( y1 ) || isNaN( y2 ) ) {
in_path = false;
+1 -1
View File
@@ -1,5 +1,5 @@
body {
margin: 4em 0;
margin: 0 0;
padding: 0;
font-family: verdana;
font-size: 75%;
+12 -3
View File
@@ -148,13 +148,22 @@
<span class="${cls}" style="${style}"><a target="${target}" href="${href}">${display}</a></span>
</%def>
## ${tab( "tracks", "View Data", h.url_for( controller='tracks', action='dbkeys' ), target="galaxy_main")}
${tab( "analysis", "Analyze Data", h.url_for( controller='root', action='index' ))}
${tab( "workflow", "Workflow", h.url_for( controller='workflow', action='index' ))}
${tab( "libraries", "Libraries", h.url_for( controller='library', action='index' ))}
${tab( "libraries", "Libraries", h.url_for( controller='library', action='index' ))}
%if app.config.get_bool( 'enable_tracks', False ):
<span class="tab">
Visualization
<div class="submenu">
<ul>
<li><a href="${h.url_for( controller='tracks', action='index' )}">Build track browser</a></li>
</ul>
</div>
</span>
%endif
${tab( "admin", "Admin", h.url_for( controller='admin', action='index' ), extra_class="admin-only", visible=( trans.user and app.config.is_admin_user( trans.user ) ) )}
@@ -1,23 +1,30 @@
<html>
<%inherit file="/base.mako"/>
<head>
<%def name="stylesheets()">
${parent.stylesheets()}
<link rel="stylesheet" type="text/css" href="/static/trackster.css" />
</%def>
<link rel="stylesheet" type="text/css" href="/s/css/trackster.css" />
<script type="text/javascript" src="/static/scripts/jquery.js"></script>
<%def name="javascripts()">
${parent.javascripts()}
<script type="text/javascript" src="/static/scripts/jquery.event.drag.js"></script>
<script type="text/javascript" src="/static/scripts/trackster.js"></script>
<script>
<script type="text/javascript">
var view = new View( "chr5", 180857866, 0, 180857866 );
## HACK
TRACKSTER_DATA_URL = "${h.url_for( action='data' )}";
var view = new View( "${chrom}", ${LEN}, 0, ${max(LEN,1)} );
var tracks = new TrackLayout( view );
var dbkey = "${dbkey}";
$(function() {
tracks.add( new LabelTrack( view, $("#viewport" ) ) );
tracks.add( new LineTrack( "phastCons44way", view, $("#viewport" ) ) );
tracks.add( new FeatureTrack( "knownGene", view, $("#viewport" ) ) );
%for track in tracks:
tracks.add( new ${track["type"]}( "${track["name"]}", view, $("#viewport" ), ${track["id"]} ) );
%endfor
$(document).bind( "redraw", function( e ) {
tracks.redraw();
});
@@ -45,11 +52,46 @@
view.high = new_high;
tracks.redraw();
});
tracks.redraw();
load_chroms();
});
var load_chroms = function () {
var fetcher = function (ref) {
return function () {
$.getJSON( "${h.url_for( action='chroms' )}", { dbkey: dbkey }, function ( data ) {
// Hacky - check length of "object"
var chrom_length = 0;
for (key in data) chrom_length++;
if( chrom_length == 0 ) {
setTimeout( fetcher, 5000 );
} else {
var chrom_options = '';
for (key in data) {
if( key == view.chr ) {
chrom_options += '<option value="' + key + '" selected="true">' + key + '</option>';
} else {
chrom_options += '<option value="' + key + '">' + key + '</option>';
}
}
$("#chrom").html(chrom_options);
$("#chrom").bind( "change", function ( e ) {
$("#chr").submit();
});
if( view.chr == "" ) {
$("#chrom option:first").attr("selected", true);
$("#chrom").trigger( "change" );
}
}
});
};
}(this);
fetcher();
};
</script>
<body>
</%def>
<div id="content">
@@ -59,14 +101,23 @@
</div>
</div>
<div id="viewport">
</div>
</div>
<div id="nav">
<div id="nav-controls">
<form name="chr" id="chr" method="GET">
<input type="hidden" name="dataset_ids" value="${dataset_ids}" />
<a href="#" onclick="javascript:view.left(5);tracks.redraw();">&lt;&lt;</a>
<a href="#" onclick="javascript:view.left(2);tracks.redraw();">&lt;</a>
<span style="display: inline-block; width: 30em; text-align: center;">Viewing chr5:<span id="low">0</span>-<span id="high">180857866</span></span>
<span style="display: inline-block; width: 30em; text-align: center;">Viewing
<select id="chrom" name="chrom">
<option value="">loading</option>
</select>
<span id="low">0</span>-<span id="high">180857866</span></span>
<span style="display: inline-block; width: 10em;">
<a href="#" onclick="javascript:view.zoom_in(2);tracks.redraw();">+</a>
<a href="#" onclick="javascript:view.zoom_out(2);tracks.redraw();">-</a>
@@ -74,15 +125,8 @@
<a href="#" onclick="javascript:view.right(2);tracks.redraw();">&gt;</a>
<a href="#" onclick="javascript:view.right(5);tracks.redraw();">&gt;&gt;</a>
</form>
</div>
</div>
<div id="viewport">
</div>
</div>
</body>
</html>
-22
View File
@@ -1,22 +0,0 @@
<%inherit file="/base.mako"/>
<%def name="init()">
<%
self.active_view="view"
self.has_left_panel=False
%>
</%def>
<script type="text/javascript">
setTimeout(function () {
window.location.reload();
}, 5000 );
</script>
<div class="donemessage">
<p>
Please wait while we index your tracks for viewing. You will be
automatically redirected to choose a chromosome to view after indices
are built.
</p>
</div>
-30
View File
@@ -1,30 +0,0 @@
<%inherit file="/base.mako"/>
<%def name="init()">
<%
self.active_view="view"
self.has_left_panel=False
%>
</%def>
<div class="form">
<div class="form-title">Select Chromosome/Contig/Scaffold/etc.</div>
<div id="dbkey" class="form-body">
<form action="/tracks/index" method="GET" target="_parent">
<div class="form-row">
<label for="dbkey">Chrom: </label>
<div class="form-row-input">
<select name="chrom" id="chrom">
%for chrom in data_resource:
<option value="${chrom}">${chrom}</option>
%endfor
</select>
</div>
<div style="clear: both;"></div>
</div>
<div class="form-row">
<input type="submit" value="View" />
</div>
</form>
</div>
</div>
-28
View File
@@ -1,28 +0,0 @@
<%inherit file="/base.mako"/>
<%def name="init()">
<%
self.active_view="view"
self.has_left_panel=False
%>
</%def>
<div class="form">
<div class="form-title">Select Datasets to View</div>
<div id="dbkey" class="form-body">
<form action="/tracks/build" method="GET">
%for key,value in data_resource.items():
<div class="form-row">
<label for="${key}">${value}</label>
<div class="form-row-input">
<input type="checkbox" name="${key}" />
</div>
<div style="clear: both;"></div>
</div>
%endfor
<div class="form-row">
<input type="submit" value="Build..." />
</div>
</form>
</div>
</div>
-30
View File
@@ -1,30 +0,0 @@
<%inherit file="/base.mako"/>
<%def name="init()">
<%
self.active_view="view"
self.has_left_panel=False
%>
</%def>
<div class="form">
<div class="form-title">Select DBKey</div>
<div id="dbkey" class="form-body">
<form action="/tracks/list" method="GET">
<div class="form-row">
<label for="dbkey">DBKey: </label>
<div class="form-row-input">
<select name="dbkey" id="dbkey">
%for dbkey in data_resource:
<option value="${dbkey}">${dbkey}</option>
%endfor
</select>
</div>
<div style="clear: both;"></div>
</div>
<div class="form-row">
<input type="submit" value="Select Datasets..."/>
</div>
</form>
</div>
</div>
-1
View File
@@ -1 +0,0 @@
${data_resource}
+4 -138
View File
@@ -2,149 +2,15 @@
<%def name="init()">
<%
self.active_view="tracks"
self.has_left_panel=False
self.has_right_panel=False
self.active_view="visualization"
self.message_box_visible=False
%>
</%def>
<%def name="stylesheets()">
${parent.stylesheets()}
<link rel="stylesheet" type="text/css" href="/static/trackster.css" />
</%def>
<%def name="late_javascripts()">
${parent.late_javascripts()}
<script type="text/javascript" src="/static/scripts/jquery.event.drag.js"></script>
<script type="text/javascript" src="/static/scripts/trackster.js"></script>
<script type="text/javascript">
var view = new View( "${chrom}", ${LEN}, 0, ${max(LEN,1)} );
var tracks = new TrackLayout( view );
var dbkey = "${dbkey}";
$(function() {
tracks.add( new LabelTrack( view, $("#viewport" ) ) );
%for track in tracks:
tracks.add( new ${track["type"]}( "${track["name"]}", view, $("#viewport" ), ${track["id"]} ) );
%endfor
$(document).bind( "redraw", function( e ) {
tracks.redraw();
});
$(window).resize( function( e ) {
tracks.redraw();
});
$("#viewport").bind( "dragstart", function ( e ) {
this.original_low = view.low;
}).bind( "drag", function( e ) {
var move_amount = ( e.offsetX - this.offsetLeft ) / this.offsetWidth;
var range = view.high - view.low;
var move_bases = Math.round( range * move_amount );
var new_low = this.original_low - move_bases;
if ( new_low < 0 ) {
new_low = 0;
}
var new_high = new_low + range;
if ( new_high > view.length ) {
new_high = view.length;
new_low = new_high - range;
}
view.low = new_low;
view.high = new_high;
tracks.redraw();
});
tracks.redraw();
load_chroms();
});
var load_chroms = function () {
var fetcher = function (ref) {
return function () {
$.getJSON( "chroms", { dbkey: dbkey }, function ( data ) {
// Hacky - check length of "object"
var chrom_length = 0;
for (key in data) chrom_length++;
if( chrom_length == 0 ) {
setTimeout( fetcher, 5000 );
} else {
var chrom_options = '';
for (key in data) {
if( key == view.chr ) {
chrom_options += '<option value="' + key + '" selected="true">' + key + '</option>';
} else {
chrom_options += '<option value="' + key + '">' + key + '</option>';
}
}
$("#chrom").html(chrom_options);
$("#chrom").bind( "change", function ( e ) {
$("#chr").submit();
});
if( view.chr == "" ) {
$("#chrom option:first").attr("selected", true);
$("#chrom").trigger( "change" );
}
}
});
};
}(this);
fetcher();
};
</script>
</%def>
<%def name="center_panel()">
<div id="content">
<div id="overview">
<div id="overview-viewport">
<div id="overview-box"></div>
</div>
</div>
<iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="tracks", action="new_browser" )}"> </iframe>
<div id="viewport">
</div>
</div>
<div id="nav">
<div id="nav-controls">
<form name="chr" id="chr" method="GET">
<a href="#" onclick="javascript:view.left(5);tracks.redraw();">&lt;&lt;</a>
<a href="#" onclick="javascript:view.left(2);tracks.redraw();">&lt;</a>
<span style="display: inline-block; width: 30em; text-align: center;">Viewing
<select id="chrom" name="chrom">
<option value="">loading</option>
</select>
<span id="low">0</span>-<span id="high">180857866</span></span>
<span style="display: inline-block; width: 10em;">
<a href="#" onclick="javascript:view.zoom_in(2);tracks.redraw();">+</a>
<a href="#" onclick="javascript:view.zoom_out(2);tracks.redraw();">-</a>
</span>
<a href="#" onclick="javascript:view.right(2);tracks.redraw();">&gt;</a>
<a href="#" onclick="javascript:view.right(5);tracks.redraw();">&gt;&gt;</a>
</form>
</div>
</div>
</%def>
<%def name="right_panel()">
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner">
<div style="float: right">
<a class='panel-header-button' href="${h.url_for( controller='root', action='history_options' )}" target="galaxy_main"><span>Options</span></a>
</div>
<div class="panel-header-text">History</div>
</div>
</div>
<div class="unified-panel-body" style="overflow: hidden;">
<iframe name="galaxy_history" width="100%" height="100%" frameborder="0" style="position: absolute; margin: 0; border: 0 none; height: 100%;" src="${h.url_for( controller='root', action='history' )}"></iframe>
</div>
</%def>
</%def>
+50
View File
@@ -0,0 +1,50 @@
<%inherit file="/base.mako"/>
<%def name="javascripts()">
${parent.javascripts()}
<script type="text/javascript">
$( function() {
$( "select[refresh_on_change='true']").change( function() {
$("#form").submit();
});
});
</script>
</%def>
<div class="form">
<div class="form-title">Select datasets to include in browser</div>
<div id="dbkey" class="form-body">
<form id="form" method="POST">
<div class="form-row">
<label for="dbkey">Reference genome build (dbkey): </label>
<div class="form-row-input">
<select name="dbkey" id="dbkey" refresh_on_change="true">
%for tmp_dbkey in dbkey_set:
<option value="${tmp_dbkey}"
%if tmp_dbkey == dbkey:
selected="true"
%endif
>${tmp_dbkey}</option>
%endfor
</select>
</div>
<div style="clear: both;"></div>
</div>
<div class="form-row">
<label for="dataset_ids">Datasets to include: </label>
%for key,value in datasets.items():
<div>
<input type="checkbox" name="dataset_ids" value="${key}" />
${value}
</div>
%endfor
<div style="clear: both;"></div>
</div>
</div>
<div class="form-row">
<input type="submit" name="browse" value="Browse"/>
</div>
</form>
</div>
</div>