trackster: Supports display of reference sequence in twobit format. Mapping of dbkey to twobit file is done in tool-data/twobit.loc. Reference track will automatically be shown when zoomed in if the twobit file exists for that dbkey. Also fixes a drawing issue with Reference/Read tracks.

This commit is contained in:
Kanwei Li
2010-06-15 17:51:12 -04:00
parent c2335891ad
commit ce75edfa5f
4 changed files with 136 additions and 34 deletions
+37 -8
View File
@@ -13,9 +13,10 @@ Problems
need to support that, but need to make user defined build support better)
"""
import math, re, logging, glob
log = logging.getLogger(__name__)
import math, re, logging, glob, pkg_resources
pkg_resources.require( "bx-python" )
from bx.seq.twobit import TwoBitFile
from galaxy import model
from galaxy.util.json import to_json_string, from_json_string
from galaxy.web.base.controller import *
@@ -71,11 +72,9 @@ class DatasetSelectionGrid( grids.Grid ):
DbKeyColumn( "Dbkey", key="dbkey", model_class=model.HistoryDatasetAssociation, visible=False )
]
columns.append(
grids.MulticolFilterColumn(
"Search",
cols_to_filter=[ columns[0], columns[1] ],
grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0], columns[1] ],
key="free-text-search", visible=False, filterable="standard" )
)
)
def build_initial_query( self, trans, **kwargs ):
return trans.sa_session.query( self.model_class ).join( model.History.table).join( model.Dataset.table )
@@ -96,6 +95,15 @@ class TracksController( BaseController, UsesVisualization ):
"""
available_tracks = None
available_genomes = None
def _init_references(self, trans):
avail_genomes = {}
for line in open( os.path.join( trans.app.config.tool_data_path, "twobit.loc" ) ):
if line.startswith("#"): continue
key, path = line.split()
avail_genomes[key] = path
self.available_genomes = avail_genomes
@web.expose
@web.require_login()
@@ -170,12 +178,16 @@ class TracksController( BaseController, UsesVisualization ):
# No vis_id, so visualization is new. User is current user, dbkey must be given.
vis_user = trans.user
vis_dbkey = dbkey
# Get chroms data.
chroms = self._chroms( trans, vis_user, vis_dbkey )
# Check for reference chrom
if self.available_genomes is None: self._init_references(trans)
to_sort = [{ 'chrom': chrom, 'len': length } for chrom, length in chroms.iteritems()]
to_sort.sort(lambda a,b: cmp( split_by_number(a['chrom']), split_by_number(b['chrom']) ))
return to_sort
return { 'reference': vis_dbkey in self.available_genomes, 'chrom_info': to_sort }
def _chroms( self, trans, user, dbkey ):
"""
@@ -204,7 +216,24 @@ class TracksController( BaseController, UsesVisualization ):
fields = line.split("\t")
manifest[fields[0]] = int(fields[1])
return manifest
@web.json
def reference( self, trans, dbkey, chrom, low, high, **kwargs ):
if self.available_genomes is None: self._init_references(trans)
if dbkey not in self.available_genomes:
return None
try:
twobit = TwoBitFile( open(self.available_genomes[dbkey]) )
except IOError:
return None
if chrom in twobit:
return twobit[chrom].get(int(low), int(high))
return None
@web.json
def data( self, trans, dataset_id, chrom, low, high, **kwargs ):
"""
File diff suppressed because one or more lines are too long
+87 -18
View File
@@ -13,6 +13,7 @@ var DENSITY = 200,
CACHED_TILES_LINE = 30,
CACHED_DATA = 5,
CONTEXT = $("<canvas></canvas>").get(0).getContext("2d"),
PX_PER_CHAR = CONTEXT.measureText("A").width,
RIGHT_STRAND, LEFT_STRAND;
var right_img = new Image();
@@ -206,7 +207,7 @@ $.extend( Track.prototype, {
track.data_queue = {};
track.tile_cache.clear();
track.data_cache.clear();
track.content_div.css( "height", "30px" );
// track.content_div.css( "height", "30px" );
if (!track.content_div.text()) {
track.content_div.text(DATA_LOADING);
}
@@ -252,6 +253,7 @@ $.extend( Track.prototype, {
});
var TiledTrack = function() {
this.left_offset = 200;
};
$.extend( TiledTrack.prototype, Track.prototype, {
draw: function() {
@@ -333,6 +335,75 @@ $.extend( LabelTrack.prototype, Track.prototype, {
}
});
var ReferenceTrack = function () {
this.track_type = "ReferenceTrack";
Track.call( this, null, $("#top-labeltrack") );
TiledTrack.call( this );
this.hidden = true;
this.height_px = 12;
this.container_div.addClass( "reference-track" );
this.dummy_canvas = $("<canvas></canvas>").get(0).getContext("2d");
this.data_queue = {};
this.data_cache = new Cache(CACHED_DATA);
this.tile_cache = new Cache(CACHED_TILES_LINE);
};
$.extend( ReferenceTrack.prototype, TiledTrack.prototype, {
get_data: function(resolution, position) {
var track = this,
low = position * DENSITY * resolution,
high = ( position + 1 ) * DENSITY * resolution,
key = resolution + "_" + position;
if (!track.data_queue[key]) {
track.data_queue[key] = true;
$.ajax({ 'url': reference_url, 'dataType': 'json', 'data': { "chrom": this.view.chrom,
"low": low, "high": high, "dbkey": this.view.dbkey },
success: function (seq) {
track.data_cache.set(key, seq);
delete track.data_queue[key];
track.draw();
}, error: function(r, t, e) {
console.log(r, t, e);
}
});
}
},
draw_tile: function( resolution, tile_index, parent_element, w_scale ) {
var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
canvas = $("<canvas class='tile'></canvas>"),
ctx = canvas.get(0).getContext("2d"),
key = resolution + "_" + tile_index;
if (w_scale > PX_PER_CHAR) {
if (this.data_cache.get(key) === undefined) {
this.get_data( resolution, tile_index );
return;
}
var seq = this.data_cache.get(key);
if (seq === null) { return; }
canvas.get(0).width = Math.ceil( tile_length * w_scale + this.left_offset);
canvas.get(0).height = this.height_px;
canvas.css( {
position: "absolute",
top: 0,
left: ( tile_low - this.view.low ) * w_scale + this.left_offset
});
for (var c = 0, str_len = seq.length; c < str_len; c++) {
var c_start = Math.round(c * w_scale);
ctx.fillText(seq[c], c_start + this.left_offset, 10);
}
parent_element.append( canvas );
return canvas;
}
}
});
var LineTrack = function ( name, dataset_id, prefs ) {
this.track_type = "LineTrack";
Track.call( this, name, $("#viewport") );
@@ -400,14 +471,14 @@ $.extend( LineTrack.prototype, TiledTrack.prototype, {
$.ajax({ 'url': data_url, 'dataType': 'json', 'data': { "chrom": this.view.chrom,
"low": low, "high": high, "dataset_id": this.dataset_id,
"resolution": this.view.resolution },
success: function (result) {
data = result.data;
track.data_cache.set(key, data);
delete track.data_queue[key];
track.draw();
}, error: function(r, t, e) {
console.log(r, t, e);
}
success: function (result) {
data = result.data;
track.data_cache.set(key, data);
delete track.data_queue[key];
track.draw();
}, error: function(r, t, e) {
console.log(r, t, e);
}
});
}
},
@@ -435,7 +506,7 @@ $.extend( LineTrack.prototype, TiledTrack.prototype, {
left: ( tile_low - this.view.low ) * w_scale
});
canvas.get(0).width = Math.ceil( tile_length * w_scale );
canvas.get(0).width = Math.ceil( tile_length * w_scale + this.left_offset);
canvas.get(0).height = this.height_px;
var ctx = canvas.get(0).getContext("2d"),
in_path = false,
@@ -567,7 +638,6 @@ var FeatureTrack = function ( name, dataset_id, prefs ) {
this.vertical_detail_px = 10;
this.vertical_nodetail_px = 3;
this.default_font = "9px Monaco, Lucida Console, monospace";
this.left_offset = 200;
this.inc_slots = {};
this.data_queue = {};
this.s_e_by_tile = {};
@@ -718,10 +788,10 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
return highest_slot;
},
rect_or_text: function( ctx, w_scale, px_per_char, tile_low, tile_high, feature_start, name, x, x_len, y_center ) {
rect_or_text: function( ctx, w_scale, tile_low, tile_high, feature_start, name, x, x_len, y_center ) {
ctx.textAlign = "center";
var gap = Math.round(w_scale / 2);
if ( (this.mode === "Pack" || this.mode === "Auto") && name !== undefined && w_scale > px_per_char) {
if ( (this.mode === "Pack" || this.mode === "Auto") && name !== undefined && w_scale > PX_PER_CHAR) {
ctx.fillStyle = this.prefs.block_color;
ctx.fillRect(x, y_center + 1, x_len, 9);
ctx.fillStyle = "#eee";
@@ -790,8 +860,7 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
new_canvas.get(0).height = required_height;
parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
// console.log(( tile_low - this.view.low ) * w_scale, tile_index, w_scale);
var ctx = new_canvas.get(0).getContext("2d"),
px_per_char = ctx.measureText("A").width;
var ctx = new_canvas.get(0).getContext("2d");
ctx.fillStyle = block_color;
ctx.font = this.default_font;
ctx.textAlign = "right";
@@ -857,10 +926,10 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
b2_end = Math.ceil( Math.min(width, Math.max(0, (feature[5][1] - tile_low) * w_scale)) );
if (feature[4][1] >= tile_low && feature[4][0] <= tile_high) {
this.rect_or_text(ctx, w_scale, px_per_char, tile_low, tile_high, feature[4][0], feature[4][2], b1_start + left_offset, b1_end - b1_start, y_center);
this.rect_or_text(ctx, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], b1_start + left_offset, b1_end - b1_start, y_center);
}
if (feature[5][1] >= tile_low && feature[5][0] <= tile_high) {
this.rect_or_text(ctx, w_scale, px_per_char, tile_low, tile_high, feature[5][0], feature[5][2], b2_start + left_offset, b2_end - b2_start, y_center);
this.rect_or_text(ctx, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], b2_start + left_offset, b2_end - b2_start, y_center);
}
if (b2_start > b1_end) {
ctx.fillStyle = "#999";
@@ -868,7 +937,7 @@ $.extend( FeatureTrack.prototype, TiledTrack.prototype, {
}
} else {
ctx.fillStyle = block_color;
this.rect_or_text(ctx, w_scale, px_per_char, tile_low, tile_high, feature_start, feature_name, f_start + left_offset, f_end - f_start, y_center);
this.rect_or_text(ctx, w_scale, tile_low, tile_high, feature_start, feature_name, f_start + left_offset, f_end - f_start, y_center);
}
if (mode !== "Dense" && !no_detail && feature_start > tile_low) {
// Draw label
+11 -7
View File
@@ -30,7 +30,8 @@ ${h.css( "history", "autocomplete_tagging" )}
<%def name="center_panel()">
<div class="unified-panel-header" unselectable="on">
<div class="unified-panel-header-inner" id="title">
<div class="unified-panel-header-inner">
<div style="float:left;" id="title"></div>
<a id="save-button" class="panel-header-button right-float" href="javascript:void(0);">Save</a>
<a id="refresh-button" class="panel-header-button right-float" href="javascript:void(0);" onclick="view.update_options();return false;">Refresh</a>
</div>
@@ -90,12 +91,12 @@ ${h.js( 'galaxy.base', 'galaxy.panels', "json2", "jquery", "jquery.event.drag",
<script type="text/javascript">
var data_url = "${h.url_for( action='data' )}";
var reference_url = "${h.url_for( action='reference' )}";
var view;
$(function() {
%if config:
$("#title").text("${config.get('title') | h}");
view = new View( "${config.get('chrom')}", "${config.get('title') | h}", "${config.get('vis_id')}", "${config.get('dbkey')}" );
%for track in config.get('tracks'):
view.add_track(
@@ -106,7 +107,6 @@ ${h.js( 'galaxy.base', 'galaxy.panels', "json2", "jquery", "jquery.event.drag",
%else:
continue_fn = function() {
view = new View( undefined, $("#new-title").val(), undefined, $("#new-dbkey").val() );
$("#title").text($("#new-title").val());
init();
hide_modal();
};
@@ -133,6 +133,7 @@ ${h.js( 'galaxy.base', 'galaxy.panels', "json2", "jquery", "jquery.event.drag",
// Execute this when everything is ready
function init() {
$("#title").text(view.title + " (" + view.dbkey + ")");
$("ul#sortable-ul").sortable({
update: function(event, ui) {
for (var track_id in view.tracks) {
@@ -327,11 +328,14 @@ ${h.js( 'galaxy.base', 'galaxy.panels', "json2", "jquery", "jquery.event.drag",
data: { dbkey: view.dbkey },
%endif
dataType: "json",
success: function ( data ) {
view.chrom_data = data;
success: function ( result ) {
if (result['reference']) {
view.add_label_track( new ReferenceTrack() );
}
view.chrom_data = result['chrom_info'];
var chrom_options = '<option value="">Select Chrom/Contig</option>';
for (i in data) {
var chrom = data[i]['chrom'];
for (i in view.chrom_data) {
var chrom = view.chrom_data[i]['chrom'];
chrom_options += '<option value="' + chrom + '">' + chrom + '</option>';
}
$("#chrom").html(chrom_options);