galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
March 2011
- 1 participants
- 141 discussions
25 new changesets in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/e97dc2a4d5b8/
changeset: r5295:e97dc2a4d5b8
user: james_taylor
date: 2011-03-29 02:32:43
summary: Extract feature slotting into a new class
affected #: 1 file (648 bytes)
--- a/static/scripts/trackster.js Thu Mar 31 11:27:49 2011 -0400
+++ b/static/scripts/trackster.js Mon Mar 28 20:32:43 2011 -0400
@@ -2236,150 +2236,18 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
-
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
- }
-
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
-
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
- }
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
- }
- */
- return highest_slot + 1;
+
+ return inc_slots.slot_features( features );
},
/**
* Draw summary tree on canvas.
@@ -2641,7 +2509,7 @@
else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
+ slots = this.inc_slots[w_scale].slots;
}
//
@@ -3131,4 +2999,154 @@
}
});
+// ---- To be extracted ------------------------------------------------------
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
http://bitbucket.org/galaxy/galaxy-central/changeset/661a4b169dd7/
changeset: r5296:661a4b169dd7
user: james_taylor
date: 2011-03-29 03:35:29
summary: Extract SummaryTree painter, abstract canvas creation into CanvasManager class
affected #: 1 file (627 bytes)
--- a/static/scripts/trackster.js Mon Mar 28 20:32:43 2011 -0400
+++ b/static/scripts/trackster.js Mon Mar 28 21:35:29 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1949,20 +1950,17 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
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 + track.left_offset, 10);
}
- return canvas;
+ return $(canvas);
}
this.content_div.css("height", "0px");
}
@@ -2253,38 +2251,17 @@
* Draw summary tree on canvas.
*/
draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
+ // Add label to container div when displaying summary tree mode
var max_label = $("<div />").addClass('yaxislabel');
max_label.text(max);
-
max_label.css({ position: "absolute", top: "22px", left: "10px" });
max_label.prependTo(this.container_div);
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
+ // Paint summary tree into canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new SummaryTreePainter( points, delta, max, this.prefs.show_counts );
+ painter.draw( ctx, w_scale, required_height, tile_low, left_offset );
},
/**
* Draw feature.
@@ -2463,10 +2440,6 @@
left_offset = this.left_offset,
slots, required_height;
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
//
// Set mode if Auto.
//
@@ -2515,15 +2488,17 @@
//
// Set up for drawing.
//
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
+ var canvas = this.view.canvas_manager.new_canvas();
+
+ canvas.width = width + left_offset;
+ canvas.height = required_height;
if (result.dataset_type === "summary_tree") {
// Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
+ canvas.height += LABEL_SPACING + CHAR_HEIGHT_PX;
}
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 = canvas.get(0).getContext("2d");
+ var ctx = canvas.getContext("2d");
ctx.fillStyle = this.prefs.block_color;
ctx.font = this.default_font;
ctx.textAlign = "right";
@@ -2535,14 +2510,14 @@
if (mode === "summary_tree") {
this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
tile_low, left_offset);
- return canvas;
+ return $(canvas);
}
//
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
- canvas.css({
+ $(canvas).css({
"border-top": "1px solid red"
});
@@ -2555,7 +2530,7 @@
// If there's no data, return.
if (!result.data) {
- return canvas;
+ return $(canvas);
}
}
@@ -2598,7 +2573,7 @@
width, left_offset, ref_seq);
}
}
- return canvas;
+ return $(canvas);
}
});
@@ -3001,6 +2976,20 @@
// ---- To be extracted ------------------------------------------------------
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
/**
* FeatureSlotter determines slots in which to draw features for vertical
* packing.
@@ -3150,3 +3139,50 @@
*/
return highest_slot + 1;
}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, show_counts ) {
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, tile_low, left_offset ) {
+
+ var
+ points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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] - tile_low) * w_scale );
+ var y = points[i][1];
+
+ if (!y) { continue; }
+ var y_px = y / max * required_height;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
http://bitbucket.org/galaxy/galaxy-central/changeset/aebefaeeda4b/
changeset: r5297:aebefaeeda4b
user: james_taylor
date: 2011-03-29 19:56:44
summary: Extract LinePainter from LineTrack
affected #: 1 file (281 bytes)
--- a/static/scripts/trackster.js Mon Mar 28 21:35:29 2011 -0400
+++ b/static/scripts/trackster.js Tue Mar 29 13:56:44 2011 -0400
@@ -2008,10 +2008,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2039,9 +2043,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2082,99 +2084,23 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
-
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
- return canvas;
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
+ return $(canvas);
}
});
@@ -3186,3 +3112,103 @@
ctx.restore();
}
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
\ No newline at end of file
http://bitbucket.org/galaxy/galaxy-central/changeset/adc2649cb7a7/
changeset: r5298:adc2649cb7a7
user: james_taylor
date: 2011-03-29 19:56:57
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 0 files (0 bytes)
http://bitbucket.org/galaxy/galaxy-central/changeset/d0f1963a9ba2/
changeset: r5299:d0f1963a9ba2
user: james_taylor
date: 2011-03-30 00:40:18
summary: Streamlining feature track drawing in preparation for pulling out a Painter, cleaning up Painter interface (not quite a common interface yet)
affected #: 1 file (35 bytes)
--- a/static/scripts/trackster.js Tue Mar 29 13:56:57 2011 -0400
+++ b/static/scripts/trackster.js Tue Mar 29 18:40:18 2011 -0400
@@ -2174,22 +2174,6 @@
return inc_slots.slot_features( features );
},
/**
- * Draw summary tree on canvas.
- */
- draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
-
- // Add label to container div when displaying summary tree mode
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text(max);
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
-
- // Paint summary tree into canvas
- var ctx = canvas.getContext("2d");
- var painter = new SummaryTreePainter( points, delta, max, this.prefs.show_counts );
- painter.draw( ctx, w_scale, required_height, tile_low, left_offset );
- },
- /**
* Draw feature.
*/
draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset) {
@@ -2350,25 +2334,18 @@
* Draw FeatureTrack tile.
*/
draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
- var track = this;
- var tile_low = tile_index * DENSITY * resolution,
+ var track = this,
+ tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_span = tile_high - tile_low,
- params = { hda_ldda: track.hda_ldda, dataset_id: track.dataset_id,
- resolution: this.view.resolution, mode: this.mode };
-
- //
- // Create/set/compute some useful vars.
- //
- var width = Math.ceil( tile_span * w_scale ),
+ width = Math.ceil( tile_span * w_scale ),
mode = this.mode,
min_height = 25,
left_offset = this.left_offset,
- slots, required_height;
+ slots,
+ required_height;
- //
- // Set mode if Auto.
- //
+ // Set display mode if Auto.
if (mode === "Auto") {
if (result.dataset_type === "summary_tree") {
mode = result.dataset_type;
@@ -2393,19 +2370,43 @@
}
}
}
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Wrapped canvas element is returned
+ return $(canvas);
+ }
+ // Start dealing with row-by-row tracks
+
+ // y_scale is the height per row
var y_scale = this.get_y_scale(mode);
- //
// Pack reads, set required height.
- //
- if (mode === "summary_tree") {
- required_height = this.summary_draw_height;
- }
if (mode === "Dense") {
required_height = min_height;
- }
- else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
+ } else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
slots = this.inc_slots[w_scale].slots;
@@ -2418,10 +2419,7 @@
canvas.width = width + left_offset;
canvas.height = required_height;
- if (result.dataset_type === "summary_tree") {
- // Increase canvas height in order to display max label.
- canvas.height += LABEL_SPACING + CHAR_HEIGHT_PX;
- }
+
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 = canvas.getContext("2d");
@@ -2430,16 +2428,6 @@
ctx.textAlign = "right";
this.container_div.find(".yaxislabel").remove();
- //
- // Draw summary tree. If tree is drawn, canvas is returned.
- //
- if (mode === "summary_tree") {
- this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
- tile_low, left_offset);
- return $(canvas);
- }
-
- //
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
@@ -3071,41 +3059,50 @@
/**
* SummaryTreePainter, a histogram showing number of intervals in a region
*/
-var SummaryTreePainter = function( data, delta, max, show_counts ) {
+var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) {
+ // Data and data properties
this.data = data;
this.delta = delta;
this.max = max;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
this.show_counts = show_counts;
}
-SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, tile_low, left_offset ) {
+SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
- var
- points = this.data, delta = this.delta, max = this.max,
+ var view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
+
+ var points = this.data, delta = this.delta, max = this.max,
// Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
// start. However, height of each rectangle is relative to required_height; hence, the
// max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
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] - tile_low) * w_scale );
+
+ var x = Math.floor( (points[i][0] - view_start) * w_scale );
var y = points[i][1];
if (!y) { continue; }
- var y_px = y / max * required_height;
+ var y_px = y / max * height;
ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
// Draw number count if it can fit the number with some padding, otherwise things clump up
var text_padding_req_x = 4;
if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
ctx.fillStyle = "#666";
ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
+ ctx.fillText(y, x + (delta_x_px/2), 10);
}
}
@@ -3113,7 +3110,9 @@
}
var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ // Data and data properties
this.data = data;
+ // View
this.view_start = view_start;
this.view_end = view_end;
// Drawing prefs
http://bitbucket.org/galaxy/galaxy-central/changeset/ffc9a245604c/
changeset: r5300:ffc9a245604c
user: james_taylor
date: 2011-03-30 00:40:24
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 0 files (0 bytes)
http://bitbucket.org/galaxy/galaxy-central/changeset/dc34db7a3248/
changeset: r5301:dc34db7a3248
user: james_taylor
date: 2011-03-30 19:01:41
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 0 files (0 bytes)
http://bitbucket.org/galaxy/galaxy-central/changeset/64774906acfc/
changeset: r5302:64774906acfc
user: james_taylor
date: 2011-03-30 19:09:42
summary: Fix accidently introduced tabs
affected #: 1 file (1.9 KB)
--- a/static/scripts/trackster.js Wed Mar 30 13:01:41 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 13:09:42 2011 -0400
@@ -2012,10 +2012,10 @@
};
$.extend(LineTrack.prototype, TiledTrack.prototype, {
add_resize_handle: function () {
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- var track = this;
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2086,20 +2086,20 @@
var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- width = Math.ceil( tile_length * w_scale ),
- height = this.height_px;
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
- // Create canvas
+ // Create canvas
var canvas = this.view.canvas_manager.new_canvas();
canvas.width = width,
canvas.height = height;
- // Paint line onto full canvas
- var ctx = canvas.getContext("2d");
- var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
- this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
- painter.draw( ctx, width, height );
-
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
return $(canvas);
}
});
@@ -2170,8 +2170,8 @@
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
}
-
- return inc_slots.slot_features( features );
+
+ return inc_slots.slot_features( features );
},
/**
* Draw feature.
@@ -2335,15 +2335,15 @@
*/
draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_span = tile_high - tile_low,
- width = Math.ceil( tile_span * w_scale ),
+ width = Math.ceil( tile_span * w_scale ),
mode = this.mode,
min_height = 25,
left_offset = this.left_offset,
slots,
- required_height;
+ required_height;
// Set display mode if Auto.
if (mode === "Auto") {
@@ -2370,37 +2370,37 @@
}
}
}
-
- // Drawing the summary tree (feature coverage histogram)
- if ( mode === "summary_tree" ) {
- // Set height of parent_element
- required_height = this.summary_draw_height;
- parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
- // Add label to container div showing maximum count
- // TODO: this shouldn't be done at the tile level
- this.container_div.find(".yaxislabel").remove();
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text( result.max );
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
- // Create canvas
- var canvas = this.view.canvas_manager.new_canvas();
- canvas.width = width + left_offset;
- // Extra padding at top of summary tree
- canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- // Paint summary tree into canvas
- var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
- var ctx = canvas.getContext("2d");
- // Deal with left_offset by translating
- ctx.translate( left_offset, 0 );
- painter.draw( ctx, width, required_height );
- // Wrapped canvas element is returned
- return $(canvas);
- }
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Wrapped canvas element is returned
+ return $(canvas);
+ }
- // Start dealing with row-by-row tracks
+ // Start dealing with row-by-row tracks
- // y_scale is the height per row
+ // y_scale is the height per row
var y_scale = this.get_y_scale(mode);
// Pack reads, set required height.
@@ -2415,8 +2415,8 @@
//
// Set up for drawing.
//
- var canvas = this.view.canvas_manager.new_canvas();
-
+ var canvas = this.view.canvas_manager.new_canvas();
+
canvas.width = width + left_offset;
canvas.height = required_height;
@@ -2934,14 +2934,14 @@
// by only having to look at a smaller subset
// if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
}
// Slot unslotted features.
@@ -2949,106 +2949,106 @@
// Find the first slot such that current feature doesn't overlap any other features in that slot.
// Returns -1 if no slot was found.
var find_slot = function(f_start, f_end) {
- // TODO: Fix constants
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
};
// Do slotting.
for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( feature_start * w_scale ),
- f_end = Math.ceil( feature_end * w_scale ),
- text_len = this.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && this.include_label ) {
- // Add gap for label spacing and extra pack space padding
- // TODO: Fix constants
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
}
// Debugging: view slots data.
/*
for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
}
*/
return highest_slot + 1;
@@ -3074,36 +3074,36 @@
SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
var view_start = this.view_start,
- view_range = this.view_end - this.view_start,
- w_scale = width / view_range;
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
var points = this.data, delta = this.delta, max = this.max,
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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;
-
- ctx.fillStyle = "black";
- ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + (delta_x_px/2), 10);
- }
+
+ 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;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + (delta_x_px/2), 10);
+ }
}
ctx.restore();
@@ -3124,16 +3124,16 @@
LinePainter.prototype.draw = function( ctx, width, height ) {
var
- in_path = false,
- min_value = this.min_value,
- max_value = this.max_value,
- vertical_range = max_value - min_value,
- height_px = height,
- view_start = this.view_start,
- view_range = this.view_end - this.view_start,
- w_scale = width / view_range,
- mode = this.mode,
- data = this.data;
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
ctx.save();
@@ -3142,72 +3142,72 @@
// Line at 0.0
if ( mode !== "Intensity" ) {
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( width, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
}
ctx.beginPath();
ctx.fillStyle = this.color;
var x_scaled, y, delta_x_px;
if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
} else {
- delta_x_px = 10;
+ delta_x_px = 10;
}
for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - view_start) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
}
if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
} else {
- ctx.stroke();
+ ctx.stroke();
}
ctx.restore();
-}
\ No newline at end of file
+}
http://bitbucket.org/galaxy/galaxy-central/changeset/41eabf97d186/
changeset: r5303:41eabf97d186
user: james_taylor
date: 2011-03-31 00:30:22
summary: Extracted feature painters for generic linked features, VCF, and reads. Not all types have been completely tested yet
affected #: 1 file (3.1 KB)
--- a/static/scripts/trackster.js Wed Mar 30 13:09:42 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 18:30:22 2011 -0400
@@ -1960,7 +1960,7 @@
var c_start = Math.round(c * w_scale);
ctx.fillText(seq[c], c_start + track.left_offset, 10);
}
- return $(canvas);
+ return canvas;
}
this.content_div.css("height", "0px");
}
@@ -2100,7 +2100,7 @@
this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
painter.draw( ctx, width, height );
- return $(canvas);
+ return canvas;
}
});
@@ -2150,6 +2150,8 @@
this.tile_cache = new Cache(CACHED_TILES_FEATURE);
this.data_cache = new DataManager(20, this);
this.left_offset = 200;
+
+ this.painter = LinkedFeaturePainter;
};
$.extend(FeatureTrack.prototype, TiledTrack.prototype, {
/**
@@ -2174,141 +2176,6 @@
return inc_slots.slot_features( features );
},
/**
- * Draw feature.
- */
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset) {
- var
- feature_uid = feature[0],
- feature_start = feature[1],
- // -1 b/c intervals are half-open.
- feature_end = feature[2] - 1,
- 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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
- thickness, y_start, thick_start = null, thick_end = null,
- block_color = this.prefs.block_color,
- label_color = this.prefs.label_color;
-
- // Dense mode displays the same for all data.
- if (mode === "Dense") {
- ctx.fillStyle = block_color;
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
- }
- else if (mode === "no_detail") {
- // No details for feature, so only one way to display.
- ctx.fillStyle = block_color;
- // TODO: what should width be here?
- ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
- }
- else { // Mode is either Squish or Pack:
- // Feature details.
- var feature_strand = feature[4],
- feature_ts = feature[5],
- feature_te = feature[6],
- feature_blocks = feature[7];
-
- if (feature_ts && feature_te) {
- thick_start = Math.floor( Math.max(0, (feature_ts - tile_low) * w_scale) );
- thick_end = Math.ceil( Math.min(width, Math.max(0, (feature_te - tile_low) * w_scale)) );
- }
-
- // Set vars that depend on mode.
- var thin_height, thick_height;
- if (mode === "Squish") {
- thin_height = 1;
- thick_height = SQUISH_FEATURE_HEIGHT;
- } else { // mode === "Pack"
- thin_height = 5;
- thick_height = PACK_FEATURE_HEIGHT;
- }
-
- // Draw feature/feature blocks + connectors.
- if (!feature_blocks) {
- // If there are no blocks, treat the feature as one big exon.
- if ( feature.strand ) {
- if (feature.strand === "+") {
- ctx.fillStyle = RIGHT_STRAND_INV;
- } else if (feature.strand === "-") {
- ctx.fillStyle = LEFT_STRAND_INV;
- }
- }
- else { // No strand.
- ctx.fillStyle = block_color;
- }
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thick_height);
- } else {
- // There are feature blocks and mode is either Squish or Pack.
- //
- // Approach: (a) draw whole feature as connector/intron and (b) draw blocks as
- // needed. This ensures that whole feature, regardless of whether it starts with
- // a block, is visible.
- //
-
- // Draw whole feature as connector/intron.
- var cur_y_center, cur_height;
- if (mode === "Squish") {
- 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 = RIGHT_STRAND;
- } else if (feature_strand === "-") {
- ctx.fillStyle = LEFT_STRAND;
- }
- }
- else {
- ctx.fillStyle = CONNECTOR_COLOR;
- cur_y_center += (SQUISH_FEATURE_HEIGHT/2) + 1;
- cur_height = 1;
- }
- }
- ctx.fillRect(f_start + left_offset, cur_y_center, f_end - f_start, cur_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) ),
- // -1 b/c intervals are half-open.
- block_end = Math.ceil( Math.min(width, Math.max((block[1] - 1 - tile_low) * w_scale)) );
-
- // Skip drawing if block not on tile.
- if (block_start > block_end) { continue; }
-
- // Draw thin block.
- ctx.fillStyle = block_color;
- ctx.fillRect(block_start + left_offset, y_center + (thick_height-thin_height)/2 + 1,
- block_end - block_start, thin_height);
-
- // If block intersects with thick region, draw block as thick.
- if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) {
- var block_thick_start = Math.max(block_start, thick_start),
- block_thick_end = Math.min(block_end, thick_end);
- ctx.fillRect(block_thick_start + left_offset, y_center + 1,
- block_thick_end - block_thick_start, thick_height);
- }
- }
- }
-
- // Draw label for Pack mode.
- if (mode === "Pack" && feature_start > tile_low) {
- ctx.fillStyle = label_color;
- if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
- ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING, y_center + 8);
- } else {
- ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING, y_center + 8);
- }
- ctx.fillStyle = block_color;
- }
- }
- },
- /**
* Returns y_scale based on mode.
*/
get_y_scale: function(mode) {
@@ -2394,27 +2261,44 @@
// Deal with left_offset by translating
ctx.translate( left_offset, 0 );
painter.draw( ctx, width, required_height );
- // Wrapped canvas element is returned
- return $(canvas);
+ // Canvas element is returned
+ return canvas;
}
// Start dealing with row-by-row tracks
-
- // y_scale is the height per row
- var y_scale = this.get_y_scale(mode);
-
- // Pack reads, set required height.
- if (mode === "Dense") {
- required_height = min_height;
- } else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
- // Calculate new slots incrementally for this new chunk of data and update height if necessary.
- required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
+
+ // If working with a mode where slotting is neccesary, update the incremental slotting
+ var slots, slots_required = 1;
+ if ( mode === "no_detail" || mode === "Squish" || mode === "Pack" ) {
+ slots_required = this.incremental_slots(w_scale, result.data, mode);
slots = this.inc_slots[w_scale].slots;
}
+
+ // Filter features
+ var filtered = [];
+ if ( result.data ) {
+ for (var i = 0, len = result.data.length; i < len; i++) {
+ var feature = result.data[i];
+ var hide_feature = false;
+ var filter;
+ for (var f = 0, flen = this.filters.length; f < flen; f++) {
+ filter = this.filters[f];
+ filter.update_attrs(feature);
+ if (!filter.keep(feature)) {
+ hide_feature = true;
+ break;
+ }
+ }
+ if (!hide_feature) {
+ filtered.push( feature );
+ }
+ }
+ }
- //
- // Set up for drawing.
- //
+ // Create painter, and canvas of sufficient size to contain all features
+ // 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, ref_seq );
+ var required_height = painter.get_required_height( slots_required );
var canvas = this.view.canvas_manager.new_canvas();
canvas.width = width + left_offset;
@@ -2444,112 +2328,29 @@
// If there's no data, return.
if (!result.data) {
- return $(canvas);
+ return canvas;
}
}
- //
// Set example feature. This is needed so that track can update its UI based on feature attributes.
- //
this.example_feature = (result.data.length ? result.data[0] : undefined);
-
- //
- // Draw elements.
- //
- var data = result.data;
- for (var i = 0, len = data.length; i < len; i++) {
- var feature = data[i],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- // Slot valid only if features are slotted and this feature is slotted;
- // feature may not be due to lack of space.
- slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
-
- // Apply filters to feature.
- var hide_feature = false;
- var filter;
- for (var f = 0; f < this.filters.length; f++) {
- filter = this.filters[f];
- filter.update_attrs(feature);
- if (!filter.keep(feature)) {
- hide_feature = true;
- break;
- }
- }
- if (hide_feature) {
- continue;
- }
+
+ // Draw features
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height, slots );
- // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
- if (is_overlap([feature_start, feature_end], [tile_low, tile_high]) && (mode == "Dense" || slot !== null)) {
- this.draw_element(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale,
- width, left_offset, ref_seq);
- }
- }
- return $(canvas);
+ return canvas;
}
});
var VcfTrack = function(name, view, hda_ldda, dataset_id, prefs, filters) {
FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
this.track_type = "VcfTrack";
+ this.painter = VariantPainter;
};
-$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * Draw a VCF entry.
- */
- draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width) {
- 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_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) ),
- f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
- thickness, y_start, thick_start = null, thick_end = null;
-
- if (no_label) {
- ctx.fillStyle = block_color;
- ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, 1);
- } else { // Show blocks, labels, etc.
- // Unpack.
- var ref_base = feature[4], alt_base = feature[5], qual = feature[6];
-
- // Draw block for entry.
- thickness = 9;
- y_start = 1;
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thickness);
-
- // Add label for entry.
- if (mode !== "Dense" && feature_name !== undefined && feature_start > tile_low) {
- // Draw label
- ctx.fillStyle = label_color;
- if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
- ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + 2 + left_offset, y_center + 8);
- } else {
- ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start - 2 + left_offset, y_center + 8);
- }
- ctx.fillStyle = block_color;
- }
-
- // Show additional data on block.
- var vcf_label = ref_base + " / " + alt_base;
- if (feature_start > tile_low && ctx.measureText(vcf_label).width < (f_end - f_start)) {
- ctx.fillStyle = "white";
- ctx.textAlign = "center";
- ctx.fillText(vcf_label, left_offset + f_start + (f_end-f_start)/2, y_center + 8);
- ctx.fillStyle = block_color;
- }
- }
- }
-});
+$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
+
var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) {
FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
@@ -2573,281 +2374,10 @@
this.prefs = this.track_config.values;
this.track_type = "ReadTrack";
+ this.painter = ReadPainter;
this.make_name_popup_menu();
};
-$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * Returns y_scale based on mode.
- */
- get_y_scale: function(mode) {
- var y_scale;
- if (mode === "summary_tree") {
- // No scale needed.
- }
- if (mode === "Dense") {
- y_scale = DENSE_TRACK_HEIGHT;
- }
- else if (mode === "Squish") {
- y_scale = SQUISH_TRACK_HEIGHT;
- }
- else { // mode === "Pack"
- y_scale = PACK_TRACK_HEIGHT;
- if (this.track_config.values.show_insertions) {
- y_scale *= 2;
- }
- }
- return y_scale;
- },
- /**
- * Draw a single read.
- */
- draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center, ref_seq) {
- ctx.textAlign = "center";
- var track = this,
- tile_region = [tile_low, tile_high],
- base_offset = 0,
- seq_offset = 0,
- gap = 0;
-
- // Keep list of items that need to be drawn on top of initial drawing layer.
- var draw_last = [];
-
- // Gap is needed so that read is offset and hence first base can be drawn on read.
- // TODO-FIX: using this gap offsets reads so that their start is not visually in sync with other tracks.
- if ((mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
- gap = Math.round(w_scale/2);
- }
- if (!cigar) {
- // If no cigar string, then assume all matches
- cigar = [ [0, orig_seq.length] ]
- }
- for (var cig_id = 0, len = cigar.length; cig_id < len; cig_id++) {
- var cig = cigar[cig_id],
- cig_op = "MIDNSHP=X"[cig[0]],
- cig_len = cig[1];
-
- if (cig_op === "H" || cig_op === "S") {
- // Go left if it clips
- base_offset -= cig_len;
- }
- var seq_start = feature_start + base_offset,
- s_start = Math.floor( Math.max(0, (seq_start - tile_low) * w_scale) ),
- s_end = Math.floor( Math.max(0, (seq_start + cig_len - tile_low) * w_scale) );
-
- switch (cig_op) {
- case "H": // Hard clipping.
- // TODO: draw anything?
- // Sequence not present, so do not increment seq_offset.
- break;
- case "S": // Soft clipping.
- case "M": // Match.
- case "=": // Equals.
- var seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region);
- if (seq_tile_overlap !== NO_OVERLAP) {
- // Draw.
- var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
- if (gap > 0) {
- ctx.fillStyle = this.prefs.block_color;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
- ctx.fillStyle = CONNECTOR_COLOR;
- // TODO: this can be made much more efficient by computing the complete sequence
- // to draw and then drawing it.
- for (var c = 0, str_len = seq.length; c < str_len; c++) {
- if (this.track_config.values.show_differences && ref_seq) {
- var ref_char = ref_seq[seq_start - tile_low + c];
- if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
- continue;
- }
- }
- if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
- var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
- }
- }
- } else {
- ctx.fillStyle = this.prefs.block_color;
- // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(s_start + this.left_offset,
- y_center + (this.mode !== "Dense" ? 4 : 5),
- s_end - s_start,
- (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
- }
- }
- seq_offset += cig_len;
- base_offset += cig_len;
- break;
- case "N": // Skipped bases.
- ctx.fillStyle = CONNECTOR_COLOR;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
- //ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
- // No change in seq_offset because sequence not used when skipping.
- base_offset += cig_len;
- break;
- case "D": // Deletion.
- ctx.fillStyle = "red";
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
- // TODO: is this true? No change in seq_offset because sequence not used when skipping.
- base_offset += cig_len;
- break;
- case "P": // TODO: No good way to draw insertions/padding right now, so ignore
- // Sequences not present, so do not increment seq_offset.
- break;
- case "I": // Insertion.
- // Check to see if sequence should be drawn at all by looking at the overlap between
- // the sequence region and the tile region.
- var
- seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
- insert_x_coord = this.left_offset + s_start - gap;
-
- if (seq_tile_overlap !== NO_OVERLAP) {
- var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
- // Insertion point is between the sequence start and the previous base: (-gap) moves
- // back from sequence start to insertion point.
- if (this.track_config.values.show_insertions) {
- //
- // Show inserted sequence above, centered on insertion point.
- //
-
- // Draw sequence.
- // X center is offset + start - <half_sequence_length>
- var x_center = this.left_offset + s_start - (s_end - s_start)/2;
- if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
- // Draw sequence container.
- ctx.fillStyle = "yellow";
- ctx.fillRect(x_center - gap, y_center - 9, s_end - s_start, 9);
- draw_last[draw_last.length] = {type: "triangle", data: [insert_x_coord, y_center + 4, 5]};
- ctx.fillStyle = CONNECTOR_COLOR;
- // Based on overlap b/t sequence and tile, get sequence to be drawn.
- switch(seq_tile_overlap) {
- case(OVERLAP_START):
- seq = seq.slice(tile_low-seq_start);
- break;
- case(OVERLAP_END):
- seq = seq.slice(0, seq_start-tile_high);
- break;
- case(CONTAINED_BY):
- // All of sequence drawn.
- break;
- case(CONTAINS):
- seq = seq.slice(tile_low-seq_start, seq_start-tile_high);
- break;
- }
- // Draw sequence.
- for (var c = 0, str_len = seq.length; c < str_len; c++) {
- var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
- }
- }
- else {
- // Draw block.
- ctx.fillStyle = "yellow";
- // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(x_center, y_center + (this.mode !== "Dense" ? 2 : 5),
- s_end - s_start, (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT));
- }
- }
- else {
- if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
- // Show insertions with a single number at the insertion point.
- draw_last[draw_last.length] = {type: "text", data: [seq.length, insert_x_coord, y_center + 9]};
- }
- else {
- // TODO: probably can merge this case with code above.
- }
- }
- }
- seq_offset += cig_len;
- // No change to base offset because insertions are drawn above sequence/read.
- break;
- case "X":
- // TODO: draw something?
- seq_offset += cig_len;
- break;
- }
- }
-
- //
- // Draw last items.
- //
- ctx.fillStyle = "yellow";
- var item, type, data;
- for (var i = 0; i < draw_last.length; i++) {
- item = draw_last[i];
- type = item["type"];
- data = item["data"];
- if (type === "text") {
- ctx.font = "bold " + DEFAULT_FONT;
- ctx.fillText(data[0], data[1], data[2]);
- ctx.font = DEFAULT_FONT;
- }
- else if (type == "triangle") {
- ctx.drawDownwardEquilateralTriangle(data[0], data[1], data[2]);
- }
- }
- },
- /**
- * Draw a complete read.
- */
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset, ref_seq) {
- // All features need a start, end, and vertical center.
- var feature_uid = feature[0],
- feature_start = feature[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)) ),
- y_center = (mode === "Dense" ? 1 : (1 + slot)) * y_scale,
- block_color = this.prefs.block_color,
- label_color = this.prefs.label_color,
- // Left-gap for label text since we align chrom text to the position tick.
- gap = 0;
-
- // TODO: fix gap usage; also see note on gap in draw_read.
- if ((mode === "Pack" || this.mode === "Auto") && w_scale > CHAR_WIDTH_PX) {
- var gap = Math.round(w_scale/2);
- }
-
- // Draw read.
- ctx.fillStyle = block_color;
- if (feature[5] instanceof Array) {
- // Read is paired.
- var b1_start = Math.floor( Math.max(0, (feature[4][0] - tile_low) * w_scale) ),
- b1_end = Math.ceil( Math.min(width, Math.max(0, (feature[4][1] - tile_low) * w_scale)) ),
- b2_start = Math.floor( Math.max(0, (feature[5][0] - tile_low) * w_scale) ),
- b2_end = Math.ceil( Math.min(width, Math.max(0, (feature[5][1] - tile_low) * w_scale)) );
-
- // Draw left/forward read.
- if (feature[4][1] >= tile_low && feature[4][0] <= tile_high && feature[4][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center, ref_seq);
- }
- // Draw right/reverse read.
- if (feature[5][1] >= tile_low && feature[5][0] <= tile_high && feature[5][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center, ref_seq);
- }
- // Draw connector.
- if (b2_start > b1_end) {
- ctx.fillStyle = CONNECTOR_COLOR;
- ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
- }
- } else {
- // Read is single.
- ctx.fillStyle = block_color;
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center, ref_seq);
- }
- if (mode === "Pack" && feature_start > tile_low) {
- // Draw label.
- ctx.fillStyle = this.prefs.label_color;
- if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
- ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
- } else {
- ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
- }
- ctx.fillStyle = block_color;
- }
- }
-});
+$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
/**
* Feature track that displays data generated from tool.
@@ -2890,6 +2420,18 @@
// ---- To be extracted ------------------------------------------------------
+// ---- Simple extend function for inheritence ----
+
+var extend = function() {
+ var target = arguments[0];
+ for ( var i = 1; i < arguments.length; i++ ) {
+ var other = arguments[i];
+ for ( key in other ) {
+ target[key] = other[key];
+ }
+ }
+}
+
// ---- Canvas management ----
var CanvasManager = function( document ) {
@@ -3211,3 +2753,561 @@
ctx.restore();
}
+
+var FeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ this.prefs = prefs;
+ this.mode = mode;
+}
+
+extend( FeaturePainter.prototype, {
+
+ get_required_height: function( rows_required ) {
+ // 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") {
+ // Calculate new slots incrementally for this new chunk of data and update height if necessary.
+ required_height = rows_required * y_scale;
+ }
+ return required_height;
+ },
+
+ draw: function( ctx, width, height, slots ) {
+
+ var data = this.data, view_start = this.view_start, view_end = this.view_end;
+
+ ctx.save();
+
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.textAlign = "right";
+
+ var view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ y_scale = this.get_row_height();
+
+ for (var i = 0, len = data.length; i < len; i++) {
+ var feature = data[i],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ // Slot valid only if features are slotted and this feature is slotted;
+ // feature may not be due to lack of space.
+ slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
+
+ // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
+ if (is_overlap([feature_start, feature_end], [view_start, view_end]) && (this.mode == "Dense" || slot !== null)) {
+ this.draw_element(ctx, this.mode, feature, slot, view_start, view_end, w_scale, y_scale,
+ width );
+ }
+ }
+
+ ctx.restore();
+ }
+});
+
+var LinkedFeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
+
+ /**
+ * Height of a single row, depends on mode
+ */
+ get_row_height: function() {
+ var mode = this.mode, y_scale;
+ if (mode === "summary_tree") {
+ // No scale needed.
+ }
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "no_detail") {
+ y_scale = NO_DETAIL_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ }
+ return y_scale;
+ },
+
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
+ var
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ // -1 b/c intervals are half-open.
+ feature_end = feature[2] - 1,
+ 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)) ),
+ y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ thickness, y_start, thick_start = null, thick_end = null,
+ block_color = this.prefs.block_color,
+ label_color = this.prefs.label_color;
+
+ // Dense mode displays the same for all data.
+ if (mode === "Dense") {
+ ctx.fillStyle = block_color;
+ ctx.fillRect(f_start, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ }
+ else if (mode === "no_detail") {
+ // No details for feature, so only one way to display.
+ ctx.fillStyle = block_color;
+ // TODO: what should width be here?
+ ctx.fillRect(f_start, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ }
+ else { // Mode is either Squish or Pack:
+ // Feature details.
+ var feature_strand = feature[4],
+ feature_ts = feature[5],
+ feature_te = feature[6],
+ feature_blocks = feature[7];
+
+ if (feature_ts && feature_te) {
+ thick_start = Math.floor( Math.max(0, (feature_ts - tile_low) * w_scale) );
+ thick_end = Math.ceil( Math.min(width, Math.max(0, (feature_te - tile_low) * w_scale)) );
+ }
+
+ // Set vars that depend on mode.
+ var thin_height, thick_height;
+ if (mode === "Squish") {
+ thin_height = 1;
+ thick_height = SQUISH_FEATURE_HEIGHT;
+ } else { // mode === "Pack"
+ thin_height = 5;
+ thick_height = PACK_FEATURE_HEIGHT;
+ }
+
+ // Draw feature/feature blocks + connectors.
+ if (!feature_blocks) {
+ // If there are no blocks, treat the feature as one big exon.
+ if ( feature.strand ) {
+ if (feature.strand === "+") {
+ ctx.fillStyle = RIGHT_STRAND_INV;
+ } else if (feature.strand === "-") {
+ ctx.fillStyle = LEFT_STRAND_INV;
+ }
+ }
+ else { // No strand.
+ ctx.fillStyle = block_color;
+ }
+ ctx.fillRect(f_start, y_center, f_end - f_start, thick_height);
+ } else {
+ // There are feature blocks and mode is either Squish or Pack.
+ //
+ // Approach: (a) draw whole feature as connector/intron and (b) draw blocks as
+ // needed. This ensures that whole feature, regardless of whether it starts with
+ // a block, is visible.
+ //
+
+ // Draw whole feature as connector/intron.
+ var cur_y_center, cur_height;
+ if (mode === "Squish") {
+ 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 = RIGHT_STRAND;
+ } else if (feature_strand === "-") {
+ ctx.fillStyle = LEFT_STRAND;
+ }
+ }
+ 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);
+
+ 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)) );
+
+ // Skip drawing if block not on tile.
+ if (block_start > block_end) { continue; }
+
+ // Draw thin block.
+ ctx.fillStyle = block_color;
+ ctx.fillRect(block_start, y_center + (thick_height-thin_height)/2 + 1,
+ block_end - block_start, thin_height);
+
+ // If block intersects with thick region, draw block as thick.
+ if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) {
+ var block_thick_start = Math.max(block_start, thick_start),
+ block_thick_end = Math.min(block_end, thick_end);
+ ctx.fillRect(block_thick_start, y_center + 1,
+ block_thick_end - block_thick_start, thick_height);
+ }
+ }
+ }
+
+ // Draw label for Pack mode.
+ if (mode === "Pack" && feature_start > tile_low) {
+ ctx.fillStyle = label_color;
+ // FIXME: do this without tile_index
+ var tile_index = 1;
+ if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
+ ctx.textAlign = "left";
+ ctx.fillText(feature_name, f_end + LABEL_SPACING, y_center + 8);
+ } else {
+ ctx.textAlign = "right";
+ ctx.fillText(feature_name, f_start - LABEL_SPACING, y_center + 8);
+ }
+ ctx.fillStyle = block_color;
+ }
+ }
+ }
+});
+
+
+var VariantPainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( VariantPainter.prototype, FeaturePainter.prototype, {
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width) {
+ 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_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) ),
+ f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ),
+ y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ thickness, y_start, thick_start = null, thick_end = null;
+
+ if (no_label) {
+ ctx.fillStyle = block_color;
+ ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, 1);
+ } else { // Show blocks, labels, etc.
+ // Unpack.
+ var ref_base = feature[4], alt_base = feature[5], qual = feature[6];
+
+ // Draw block for entry.
+ thickness = 9;
+ y_start = 1;
+ ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thickness);
+
+ // Add label for entry.
+ if (mode !== "Dense" && feature_name !== undefined && feature_start > tile_low) {
+ // Draw label
+ ctx.fillStyle = label_color;
+ if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
+ ctx.textAlign = "left";
+ ctx.fillText(feature_name, f_end + 2 + left_offset, y_center + 8);
+ } else {
+ ctx.textAlign = "right";
+ ctx.fillText(feature_name, f_start - 2 + left_offset, y_center + 8);
+ }
+ ctx.fillStyle = block_color;
+ }
+
+ // Show additional data on block.
+ var vcf_label = ref_base + " / " + alt_base;
+ if (feature_start > tile_low && ctx.measureText(vcf_label).width < (f_end - f_start)) {
+ ctx.fillStyle = "white";
+ ctx.textAlign = "center";
+ ctx.fillText(vcf_label, left_offset + f_start + (f_end-f_start)/2, y_center + 8);
+ ctx.fillStyle = block_color;
+ }
+ }
+ }
+});
+
+var ReadPainter = function( data, view_start, view_end, prefs, mode, ref_seq ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+ this.ref_seq = ref_seq;
+}
+
+extend( ReadPainter.prototype, FeaturePainter.prototype, {
+ /**
+ * Returns y_scale based on mode.
+ */
+ get_row_height: function() {
+ var y_scale, mode = this.mode;
+ if (mode === "summary_tree") {
+ // No scale needed.
+ }
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ if (this.track_config.values.show_insertions) {
+ y_scale *= 2;
+ }
+ }
+ return y_scale;
+ },
+ /**
+ * Draw a single read.
+ */
+ draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center) {
+ ctx.textAlign = "center";
+ var track = this,
+ tile_region = [tile_low, tile_high],
+ base_offset = 0,
+ seq_offset = 0,
+ gap = 0
+ ref_seq = this.ref_seq;
+
+ // Keep list of items that need to be drawn on top of initial drawing layer.
+ var draw_last = [];
+
+ // Gap is needed so that read is offset and hence first base can be drawn on read.
+ // TODO-FIX: using this gap offsets reads so that their start is not visually in sync with other tracks.
+ if ((mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
+ gap = Math.round(w_scale/2);
+ }
+ if (!cigar) {
+ // If no cigar string, then assume all matches
+ cigar = [ [0, orig_seq.length] ]
+ }
+ for (var cig_id = 0, len = cigar.length; cig_id < len; cig_id++) {
+ var cig = cigar[cig_id],
+ cig_op = "MIDNSHP=X"[cig[0]],
+ cig_len = cig[1];
+
+ if (cig_op === "H" || cig_op === "S") {
+ // Go left if it clips
+ base_offset -= cig_len;
+ }
+ var seq_start = feature_start + base_offset,
+ s_start = Math.floor( Math.max(0, (seq_start - tile_low) * w_scale) ),
+ s_end = Math.floor( Math.max(0, (seq_start + cig_len - tile_low) * w_scale) );
+
+ switch (cig_op) {
+ case "H": // Hard clipping.
+ // TODO: draw anything?
+ // Sequence not present, so do not increment seq_offset.
+ break;
+ case "S": // Soft clipping.
+ case "M": // Match.
+ case "=": // Equals.
+ var seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region);
+ if (seq_tile_overlap !== NO_OVERLAP) {
+ // Draw.
+ var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
+ if (gap > 0) {
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
+ ctx.fillStyle = CONNECTOR_COLOR;
+ // TODO: this can be made much more efficient by computing the complete sequence
+ // to draw and then drawing it.
+ for (var c = 0, str_len = seq.length; c < str_len; c++) {
+ if (this.track_config.values.show_differences && ref_seq) {
+ var ref_char = ref_seq[seq_start - tile_low + c];
+ if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
+ continue;
+ }
+ }
+ if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
+ var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
+ ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
+ }
+ }
+ } else {
+ ctx.fillStyle = this.prefs.block_color;
+ // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
+ ctx.fillRect(s_start + this.left_offset,
+ y_center + (this.mode !== "Dense" ? 4 : 5),
+ s_end - s_start,
+ (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
+ }
+ }
+ seq_offset += cig_len;
+ base_offset += cig_len;
+ break;
+ case "N": // Skipped bases.
+ ctx.fillStyle = CONNECTOR_COLOR;
+ ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
+ //ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
+ // No change in seq_offset because sequence not used when skipping.
+ base_offset += cig_len;
+ break;
+ case "D": // Deletion.
+ ctx.fillStyle = "red";
+ ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
+ // TODO: is this true? No change in seq_offset because sequence not used when skipping.
+ base_offset += cig_len;
+ break;
+ case "P": // TODO: No good way to draw insertions/padding right now, so ignore
+ // Sequences not present, so do not increment seq_offset.
+ break;
+ case "I": // Insertion.
+ // Check to see if sequence should be drawn at all by looking at the overlap between
+ // the sequence region and the tile region.
+ var
+ seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
+ insert_x_coord = this.left_offset + s_start - gap;
+
+ if (seq_tile_overlap !== NO_OVERLAP) {
+ var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
+ // Insertion point is between the sequence start and the previous base: (-gap) moves
+ // back from sequence start to insertion point.
+ if (this.track_config.values.show_insertions) {
+ //
+ // Show inserted sequence above, centered on insertion point.
+ //
+
+ // Draw sequence.
+ // X center is offset + start - <half_sequence_length>
+ var x_center = this.left_offset + s_start - (s_end - s_start)/2;
+ if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
+ // Draw sequence container.
+ ctx.fillStyle = "yellow";
+ ctx.fillRect(x_center - gap, y_center - 9, s_end - s_start, 9);
+ draw_last[draw_last.length] = {type: "triangle", data: [insert_x_coord, y_center + 4, 5]};
+ ctx.fillStyle = CONNECTOR_COLOR;
+ // Based on overlap b/t sequence and tile, get sequence to be drawn.
+ switch(seq_tile_overlap) {
+ case(OVERLAP_START):
+ seq = seq.slice(tile_low-seq_start);
+ break;
+ case(OVERLAP_END):
+ seq = seq.slice(0, seq_start-tile_high);
+ break;
+ case(CONTAINED_BY):
+ // All of sequence drawn.
+ break;
+ case(CONTAINS):
+ seq = seq.slice(tile_low-seq_start, seq_start-tile_high);
+ break;
+ }
+ // Draw sequence.
+ for (var c = 0, str_len = seq.length; c < str_len; c++) {
+ var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
+ ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
+ }
+ }
+ else {
+ // Draw block.
+ ctx.fillStyle = "yellow";
+ // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
+ ctx.fillRect(x_center, y_center + (this.mode !== "Dense" ? 2 : 5),
+ s_end - s_start, (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT));
+ }
+ }
+ else {
+ if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
+ // Show insertions with a single number at the insertion point.
+ draw_last[draw_last.length] = {type: "text", data: [seq.length, insert_x_coord, y_center + 9]};
+ }
+ else {
+ // TODO: probably can merge this case with code above.
+ }
+ }
+ }
+ seq_offset += cig_len;
+ // No change to base offset because insertions are drawn above sequence/read.
+ break;
+ case "X":
+ // TODO: draw something?
+ seq_offset += cig_len;
+ break;
+ }
+ }
+
+ //
+ // Draw last items.
+ //
+ ctx.fillStyle = "yellow";
+ var item, type, data;
+ for (var i = 0; i < draw_last.length; i++) {
+ item = draw_last[i];
+ type = item["type"];
+ data = item["data"];
+ if (type === "text") {
+ ctx.font = "bold " + DEFAULT_FONT;
+ ctx.fillText(data[0], data[1], data[2]);
+ ctx.font = DEFAULT_FONT;
+ }
+ else if (type == "triangle") {
+ ctx.drawDownwardEquilateralTriangle(data[0], data[1], data[2]);
+ }
+ }
+ },
+ /**
+ * Draw a complete read pair
+ */
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
+ // All features need a start, end, and vertical center.
+ var feature_uid = feature[0],
+ feature_start = feature[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)) ),
+ y_center = (mode === "Dense" ? 1 : (1 + slot)) * y_scale,
+ block_color = this.prefs.block_color,
+ label_color = this.prefs.label_color,
+ // Left-gap for label text since we align chrom text to the position tick.
+ gap = 0;
+
+ // TODO: fix gap usage; also see note on gap in draw_read.
+ if ((mode === "Pack" || this.mode === "Auto") && w_scale > CHAR_WIDTH_PX) {
+ var gap = Math.round(w_scale/2);
+ }
+
+ // Draw read.
+ ctx.fillStyle = block_color;
+ if (feature[5] instanceof Array) {
+ // Read is paired.
+ var b1_start = Math.floor( Math.max(0, (feature[4][0] - tile_low) * w_scale) ),
+ b1_end = Math.ceil( Math.min(width, Math.max(0, (feature[4][1] - tile_low) * w_scale)) ),
+ b2_start = Math.floor( Math.max(0, (feature[5][0] - tile_low) * w_scale) ),
+ b2_end = Math.ceil( Math.min(width, Math.max(0, (feature[5][1] - tile_low) * w_scale)) );
+
+ // Draw left/forward read.
+ if (feature[4][1] >= tile_low && feature[4][0] <= tile_high && feature[4][2]) {
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center);
+ }
+ // Draw right/reverse read.
+ if (feature[5][1] >= tile_low && feature[5][0] <= tile_high && feature[5][2]) {
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center);
+ }
+ // Draw connector.
+ if (b2_start > b1_end) {
+ ctx.fillStyle = CONNECTOR_COLOR;
+ ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
+ }
+ } else {
+ // Read is single.
+ ctx.fillStyle = block_color;
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center);
+ }
+ if (mode === "Pack" && feature_start > tile_low) {
+ // Draw label.
+ ctx.fillStyle = this.prefs.label_color;
+ // FIXME: eliminate tile_index
+ var tile_index = 1;
+ if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
+ ctx.textAlign = "left";
+ ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
+ } else {
+ ctx.textAlign = "right";
+ ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
+ }
+ ctx.fillStyle = block_color;
+ }
+ }
+});
http://bitbucket.org/galaxy/galaxy-central/changeset/c4961c6e7a4c/
changeset: r5304:c4961c6e7a4c
user: james_taylor
date: 2011-03-31 00:30:35
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 0 files (0 bytes)
http://bitbucket.org/galaxy/galaxy-central/changeset/4be86b204759/
changeset: r5305:4be86b204759
user: james_taylor
date: 2011-03-31 19:05:24
summary: Remove left_offset stuff from ReadTracks, fix some other stuff related to padding and height calculation, read tracks now work fine
affected #: 1 file (134 bytes)
--- a/static/scripts/trackster.js Wed Mar 30 18:30:35 2011 -0400
+++ b/static/scripts/trackster.js Thu Mar 31 13:05:24 2011 -0400
@@ -2298,7 +2298,8 @@
// Create painter, and canvas of sufficient size to contain all features
// 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, ref_seq );
- var required_height = painter.get_required_height( slots_required );
+ // FIXME: ERROR_PADDING is an ugly gap most of the time
+ var required_height = painter.get_required_height( slots_required ) + ERROR_PADDING;
var canvas = this.view.canvas_manager.new_canvas();
canvas.width = width + left_offset;
@@ -2336,7 +2337,7 @@
this.example_feature = (result.data.length ? result.data[0] : undefined);
// Draw features
- ctx.translate( left_offset, 0 );
+ ctx.translate( left_offset, ERROR_PADDING );
painter.draw( ctx, width, required_height, slots );
return canvas;
@@ -2772,7 +2773,8 @@
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = rows_required * y_scale;
}
- return required_height;
+ // Pad bottom by half a row
+ return required_height + Math.round( y_scale / 2 );
},
draw: function( ctx, width, height, slots ) {
@@ -2846,11 +2848,11 @@
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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
thickness, y_start, thick_start = null, thick_end = null,
block_color = this.prefs.block_color,
label_color = this.prefs.label_color;
-
+
// Dense mode displays the same for all data.
if (mode === "Dense") {
ctx.fillStyle = block_color;
@@ -2989,7 +2991,7 @@
// All features need a start, end, and vertical center.
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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
thickness, y_start, thick_start = null, thick_end = null;
if (no_label) {
@@ -3052,7 +3054,7 @@
}
else { // mode === "Pack"
y_scale = PACK_TRACK_HEIGHT;
- if (this.track_config.values.show_insertions) {
+ if (this.prefs.show_insertions) {
y_scale *= 2;
}
}
@@ -3109,12 +3111,12 @@
var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
if (gap > 0) {
ctx.fillStyle = this.prefs.block_color;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
+ ctx.fillRect(s_start - gap, y_center + 1, s_end - s_start, 9);
ctx.fillStyle = CONNECTOR_COLOR;
// TODO: this can be made much more efficient by computing the complete sequence
// to draw and then drawing it.
for (var c = 0, str_len = seq.length; c < str_len; c++) {
- if (this.track_config.values.show_differences && ref_seq) {
+ if (this.prefs.show_differences && ref_seq) {
var ref_char = ref_seq[seq_start - tile_low + c];
if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
continue;
@@ -3122,13 +3124,13 @@
}
if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
+ ctx.fillText(seq[c], c_start, y_center + 9);
}
}
} else {
ctx.fillStyle = this.prefs.block_color;
// TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(s_start + this.left_offset,
+ ctx.fillRect(s_start,
y_center + (this.mode !== "Dense" ? 4 : 5),
s_end - s_start,
(mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
@@ -3139,14 +3141,14 @@
break;
case "N": // Skipped bases.
ctx.fillStyle = CONNECTOR_COLOR;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
+ ctx.fillRect(s_start - gap, y_center + 5, s_end - s_start, 1);
//ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
// No change in seq_offset because sequence not used when skipping.
base_offset += cig_len;
break;
case "D": // Deletion.
ctx.fillStyle = "red";
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
+ ctx.fillRect(s_start - gap, y_center + 4, s_end - s_start, 3);
// TODO: is this true? No change in seq_offset because sequence not used when skipping.
base_offset += cig_len;
break;
@@ -3158,20 +3160,20 @@
// the sequence region and the tile region.
var
seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
- insert_x_coord = this.left_offset + s_start - gap;
+ insert_x_coord = s_start - gap;
if (seq_tile_overlap !== NO_OVERLAP) {
var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
// Insertion point is between the sequence start and the previous base: (-gap) moves
// back from sequence start to insertion point.
- if (this.track_config.values.show_insertions) {
+ if (this.prefs.show_insertions) {
//
// Show inserted sequence above, centered on insertion point.
//
// Draw sequence.
// X center is offset + start - <half_sequence_length>
- var x_center = this.left_offset + s_start - (s_end - s_start)/2;
+ var x_center = s_start - (s_end - s_start)/2;
if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
// Draw sequence container.
ctx.fillStyle = "yellow";
@@ -3196,7 +3198,7 @@
// Draw sequence.
for (var c = 0, str_len = seq.length; c < str_len; c++) {
var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
+ ctx.fillText(seq[c], c_start - (s_end - s_start)/2, y_center);
}
}
else {
@@ -3288,7 +3290,7 @@
// Draw connector.
if (b2_start > b1_end) {
ctx.fillStyle = CONNECTOR_COLOR;
- ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
+ ctx.dashedLine(b1_end - gap, y_center + 5, b2_start - gap, y_center + 5);
}
} else {
// Read is single.
@@ -3302,10 +3304,10 @@
var tile_index = 1;
if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
+ ctx.fillText(feature_name, f_end + LABEL_SPACING - gap, y_center + 8);
} else {
ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
+ ctx.fillText(feature_name, f_start - LABEL_SPACING - gap, y_center + 8);
}
ctx.fillStyle = block_color;
}
http://bitbucket.org/galaxy/galaxy-central/changeset/88e924542585/
changeset: r5306:88e924542585
user: james_taylor
date: 2011-03-29 02:32:43
summary: Extract feature slotting into a new class
affected #: 1 file (648 bytes)
--- a/static/scripts/trackster.js Mon Mar 28 15:27:14 2011 -0400
+++ b/static/scripts/trackster.js Mon Mar 28 20:32:43 2011 -0400
@@ -2232,150 +2232,18 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
-
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
- }
-
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
-
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
- }
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
- }
- */
- return highest_slot + 1;
+
+ return inc_slots.slot_features( features );
},
/**
* Draw summary tree on canvas.
@@ -2637,7 +2505,7 @@
else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
+ slots = this.inc_slots[w_scale].slots;
}
//
@@ -3126,4 +2994,154 @@
}
});
+// ---- To be extracted ------------------------------------------------------
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
http://bitbucket.org/galaxy/galaxy-central/changeset/643539b68e70/
changeset: r5307:643539b68e70
user: james_taylor
date: 2011-03-29 03:35:29
summary: Extract SummaryTree painter, abstract canvas creation into CanvasManager class
affected #: 1 file (627 bytes)
--- a/static/scripts/trackster.js Mon Mar 28 20:32:43 2011 -0400
+++ b/static/scripts/trackster.js Mon Mar 28 21:35:29 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1945,20 +1946,17 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
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 + track.left_offset, 10);
}
- return canvas;
+ return $(canvas);
}
this.content_div.css("height", "0px");
}
@@ -2249,38 +2247,17 @@
* Draw summary tree on canvas.
*/
draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
+ // Add label to container div when displaying summary tree mode
var max_label = $("<div />").addClass('yaxislabel');
max_label.text(max);
-
max_label.css({ position: "absolute", top: "22px", left: "10px" });
max_label.prependTo(this.container_div);
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
+ // Paint summary tree into canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new SummaryTreePainter( points, delta, max, this.prefs.show_counts );
+ painter.draw( ctx, w_scale, required_height, tile_low, left_offset );
},
/**
* Draw feature.
@@ -2459,10 +2436,6 @@
left_offset = this.left_offset,
slots, required_height;
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
//
// Set mode if Auto.
//
@@ -2511,15 +2484,17 @@
//
// Set up for drawing.
//
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
+ var canvas = this.view.canvas_manager.new_canvas();
+
+ canvas.width = width + left_offset;
+ canvas.height = required_height;
if (result.dataset_type === "summary_tree") {
// Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
+ canvas.height += LABEL_SPACING + CHAR_HEIGHT_PX;
}
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 = canvas.get(0).getContext("2d");
+ var ctx = canvas.getContext("2d");
ctx.fillStyle = this.prefs.block_color;
ctx.font = this.default_font;
ctx.textAlign = "right";
@@ -2531,14 +2506,14 @@
if (mode === "summary_tree") {
this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
tile_low, left_offset);
- return canvas;
+ return $(canvas);
}
//
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
- canvas.css({
+ $(canvas).css({
"border-top": "1px solid red"
});
@@ -2551,7 +2526,7 @@
// If there's no data, return.
if (!result.data) {
- return canvas;
+ return $(canvas);
}
}
@@ -2594,7 +2569,7 @@
width, left_offset, ref_seq);
}
}
- return canvas;
+ return $(canvas);
}
});
@@ -2996,6 +2971,20 @@
// ---- To be extracted ------------------------------------------------------
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
/**
* FeatureSlotter determines slots in which to draw features for vertical
* packing.
@@ -3145,3 +3134,50 @@
*/
return highest_slot + 1;
}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, show_counts ) {
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, tile_low, left_offset ) {
+
+ var
+ points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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] - tile_low) * w_scale );
+ var y = points[i][1];
+
+ if (!y) { continue; }
+ var y_px = y / max * required_height;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
http://bitbucket.org/galaxy/galaxy-central/changeset/183482bc7c2b/
changeset: r5308:183482bc7c2b
user: james_taylor
date: 2011-03-29 19:56:44
summary: Extract LinePainter from LineTrack
affected #: 1 file (281 bytes)
--- a/static/scripts/trackster.js Mon Mar 28 21:35:29 2011 -0400
+++ b/static/scripts/trackster.js Tue Mar 29 13:56:44 2011 -0400
@@ -2004,10 +2004,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2035,9 +2039,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2078,99 +2080,23 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
-
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
- return canvas;
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
+ return $(canvas);
}
});
@@ -3181,3 +3107,103 @@
ctx.restore();
}
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
\ No newline at end of file
http://bitbucket.org/galaxy/galaxy-central/changeset/f48057eeedbc/
changeset: r5309:f48057eeedbc
user: james_taylor
date: 2011-03-29 19:56:57
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 1 file (302 bytes)
--- a/static/scripts/trackster.js Tue Mar 29 13:43:24 2011 -0400
+++ b/static/scripts/trackster.js Tue Mar 29 13:56:57 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1945,20 +1946,17 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
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 + track.left_offset, 10);
}
- return canvas;
+ return $(canvas);
}
this.content_div.css("height", "0px");
}
@@ -2006,10 +2004,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2037,9 +2039,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2080,99 +2080,23 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
-
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
- return canvas;
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
+ return $(canvas);
}
});
@@ -2232,187 +2156,34 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
-
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
- }
-
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
-
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
- }
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
- }
- */
- return highest_slot + 1;
+
+ return inc_slots.slot_features( features );
},
/**
* Draw summary tree on canvas.
*/
draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
+ // Add label to container div when displaying summary tree mode
var max_label = $("<div />").addClass('yaxislabel');
max_label.text(max);
-
max_label.css({ position: "absolute", top: "22px", left: "10px" });
max_label.prependTo(this.container_div);
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
+ // Paint summary tree into canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new SummaryTreePainter( points, delta, max, this.prefs.show_counts );
+ painter.draw( ctx, w_scale, required_height, tile_low, left_offset );
},
/**
* Draw feature.
@@ -2591,10 +2362,6 @@
left_offset = this.left_offset,
slots, required_height;
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
//
// Set mode if Auto.
//
@@ -2637,21 +2404,23 @@
else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
+ slots = this.inc_slots[w_scale].slots;
}
//
// Set up for drawing.
//
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
+ var canvas = this.view.canvas_manager.new_canvas();
+
+ canvas.width = width + left_offset;
+ canvas.height = required_height;
if (result.dataset_type === "summary_tree") {
// Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
+ canvas.height += LABEL_SPACING + CHAR_HEIGHT_PX;
}
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 = canvas.get(0).getContext("2d");
+ var ctx = canvas.getContext("2d");
ctx.fillStyle = this.prefs.block_color;
ctx.font = this.default_font;
ctx.textAlign = "right";
@@ -2663,14 +2432,14 @@
if (mode === "summary_tree") {
this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
tile_low, left_offset);
- return canvas;
+ return $(canvas);
}
//
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
- canvas.css({
+ $(canvas).css({
"border-top": "1px solid red"
});
@@ -2683,7 +2452,7 @@
// If there's no data, return.
if (!result.data) {
- return canvas;
+ return $(canvas);
}
}
@@ -2726,7 +2495,7 @@
width, left_offset, ref_seq);
}
}
- return canvas;
+ return $(canvas);
}
});
@@ -3127,4 +2896,315 @@
}
});
+// ---- To be extracted ------------------------------------------------------
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, show_counts ) {
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, tile_low, left_offset ) {
+
+ var
+ points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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] - tile_low) * w_scale );
+ var y = points[i][1];
+
+ if (!y) { continue; }
+ var y_px = y / max * required_height;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
\ No newline at end of file
http://bitbucket.org/galaxy/galaxy-central/changeset/b543cf3a0901/
changeset: r5310:b543cf3a0901
user: james_taylor
date: 2011-03-30 00:40:18
summary: Streamlining feature track drawing in preparation for pulling out a Painter, cleaning up Painter interface (not quite a common interface yet)
affected #: 1 file (35 bytes)
--- a/static/scripts/trackster.js Tue Mar 29 13:56:57 2011 -0400
+++ b/static/scripts/trackster.js Tue Mar 29 18:40:18 2011 -0400
@@ -2170,22 +2170,6 @@
return inc_slots.slot_features( features );
},
/**
- * Draw summary tree on canvas.
- */
- draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
-
- // Add label to container div when displaying summary tree mode
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text(max);
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
-
- // Paint summary tree into canvas
- var ctx = canvas.getContext("2d");
- var painter = new SummaryTreePainter( points, delta, max, this.prefs.show_counts );
- painter.draw( ctx, w_scale, required_height, tile_low, left_offset );
- },
- /**
* Draw feature.
*/
draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset) {
@@ -2346,25 +2330,18 @@
* Draw FeatureTrack tile.
*/
draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
- var track = this;
- var tile_low = tile_index * DENSITY * resolution,
+ var track = this,
+ tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_span = tile_high - tile_low,
- params = { hda_ldda: track.hda_ldda, dataset_id: track.dataset_id,
- resolution: this.view.resolution, mode: this.mode };
-
- //
- // Create/set/compute some useful vars.
- //
- var width = Math.ceil( tile_span * w_scale ),
+ width = Math.ceil( tile_span * w_scale ),
mode = this.mode,
min_height = 25,
left_offset = this.left_offset,
- slots, required_height;
+ slots,
+ required_height;
- //
- // Set mode if Auto.
- //
+ // Set display mode if Auto.
if (mode === "Auto") {
if (result.dataset_type === "summary_tree") {
mode = result.dataset_type;
@@ -2389,19 +2366,43 @@
}
}
}
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Wrapped canvas element is returned
+ return $(canvas);
+ }
+ // Start dealing with row-by-row tracks
+
+ // y_scale is the height per row
var y_scale = this.get_y_scale(mode);
- //
// Pack reads, set required height.
- //
- if (mode === "summary_tree") {
- required_height = this.summary_draw_height;
- }
if (mode === "Dense") {
required_height = min_height;
- }
- else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
+ } else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
slots = this.inc_slots[w_scale].slots;
@@ -2414,10 +2415,7 @@
canvas.width = width + left_offset;
canvas.height = required_height;
- if (result.dataset_type === "summary_tree") {
- // Increase canvas height in order to display max label.
- canvas.height += LABEL_SPACING + CHAR_HEIGHT_PX;
- }
+
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 = canvas.getContext("2d");
@@ -2426,16 +2424,6 @@
ctx.textAlign = "right";
this.container_div.find(".yaxislabel").remove();
- //
- // Draw summary tree. If tree is drawn, canvas is returned.
- //
- if (mode === "summary_tree") {
- this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
- tile_low, left_offset);
- return $(canvas);
- }
-
- //
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
@@ -3067,41 +3055,50 @@
/**
* SummaryTreePainter, a histogram showing number of intervals in a region
*/
-var SummaryTreePainter = function( data, delta, max, show_counts ) {
+var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) {
+ // Data and data properties
this.data = data;
this.delta = delta;
this.max = max;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
this.show_counts = show_counts;
}
-SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, tile_low, left_offset ) {
+SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
- var
- points = this.data, delta = this.delta, max = this.max,
+ var view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
+
+ var points = this.data, delta = this.delta, max = this.max,
// Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
// start. However, height of each rectangle is relative to required_height; hence, the
// max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
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] - tile_low) * w_scale );
+
+ var x = Math.floor( (points[i][0] - view_start) * w_scale );
var y = points[i][1];
if (!y) { continue; }
- var y_px = y / max * required_height;
+ var y_px = y / max * height;
ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
// Draw number count if it can fit the number with some padding, otherwise things clump up
var text_padding_req_x = 4;
if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
ctx.fillStyle = "#666";
ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
+ ctx.fillText(y, x + (delta_x_px/2), 10);
}
}
@@ -3109,7 +3106,9 @@
}
var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ // Data and data properties
this.data = data;
+ // View
this.view_start = view_start;
this.view_end = view_end;
// Drawing prefs
http://bitbucket.org/galaxy/galaxy-central/changeset/4789655a57db/
changeset: r5311:4789655a57db
user: james_taylor
date: 2011-03-30 00:40:24
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 1 file (337 bytes)
--- a/static/scripts/trackster.js Tue Mar 29 16:27:35 2011 -0400
+++ b/static/scripts/trackster.js Tue Mar 29 18:40:24 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1948,20 +1949,17 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
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 + track.left_offset, 10);
}
- return canvas;
+ return $(canvas);
}
this.content_div.css("height", "0px");
}
@@ -2009,10 +2007,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2040,9 +2042,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2083,99 +2083,23 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
-
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
- return canvas;
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
+ return $(canvas);
}
});
@@ -2235,187 +2159,18 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
-
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
- }
-
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
-
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
- }
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
- }
- */
- return highest_slot + 1;
- },
- /**
- * Draw summary tree on canvas.
- */
- draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
-
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text(max);
-
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
-
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
+
+ return inc_slots.slot_features( features );
},
/**
* Draw feature.
@@ -2578,29 +2333,18 @@
* Draw FeatureTrack tile.
*/
draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
- var track = this;
- var tile_low = tile_index * DENSITY * resolution,
+ var track = this,
+ tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_span = tile_high - tile_low,
- params = { hda_ldda: track.hda_ldda, dataset_id: track.dataset_id,
- resolution: this.view.resolution, mode: this.mode };
-
- //
- // Create/set/compute some useful vars.
- //
- var width = Math.ceil( tile_span * w_scale ),
+ width = Math.ceil( tile_span * w_scale ),
mode = this.mode,
min_height = 25,
left_offset = this.left_offset,
- slots, required_height;
+ slots,
+ required_height;
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- //
- // Set mode if Auto.
- //
+ // Set display mode if Auto.
if (mode === "Auto") {
if (result.dataset_type === "summary_tree") {
mode = result.dataset_type;
@@ -2625,55 +2369,68 @@
}
}
}
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Wrapped canvas element is returned
+ return $(canvas);
+ }
+ // Start dealing with row-by-row tracks
+
+ // y_scale is the height per row
var y_scale = this.get_y_scale(mode);
- //
// Pack reads, set required height.
- //
- if (mode === "summary_tree") {
- required_height = this.summary_draw_height;
- }
if (mode === "Dense") {
required_height = min_height;
- }
- else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
+ } else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
+ slots = this.inc_slots[w_scale].slots;
}
//
// Set up for drawing.
//
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
- if (result.dataset_type === "summary_tree") {
- // Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
- }
+ var canvas = this.view.canvas_manager.new_canvas();
+
+ canvas.width = width + left_offset;
+ canvas.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 = canvas.get(0).getContext("2d");
+ var ctx = canvas.getContext("2d");
ctx.fillStyle = this.prefs.block_color;
ctx.font = this.default_font;
ctx.textAlign = "right";
this.container_div.find(".yaxislabel").remove();
- //
- // Draw summary tree. If tree is drawn, canvas is returned.
- //
- if (mode === "summary_tree") {
- this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
- tile_low, left_offset);
- return canvas;
- }
-
- //
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
- canvas.css({
+ $(canvas).css({
"border-top": "1px solid red"
});
@@ -2686,7 +2443,7 @@
// If there's no data, return.
if (!result.data) {
- return canvas;
+ return $(canvas);
}
}
@@ -2729,7 +2486,7 @@
width, left_offset, ref_seq);
}
}
- return canvas;
+ return $(canvas);
}
});
@@ -3130,4 +2887,326 @@
}
});
+// ---- To be extracted ------------------------------------------------------
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) {
+ // Data and data properties
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
+
+ var view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
+
+ var points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ // Data and data properties
+ this.data = data;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
\ No newline at end of file
http://bitbucket.org/galaxy/galaxy-central/changeset/5b89e71b55ec/
changeset: r5312:5b89e71b55ec
user: james_taylor
date: 2011-03-30 19:01:41
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 1 file (337 bytes)
--- a/static/scripts/trackster.js Wed Mar 30 11:44:08 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 13:01:41 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1949,20 +1950,17 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
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 + track.left_offset, 10);
}
- return canvas;
+ return $(canvas);
}
this.content_div.css("height", "0px");
}
@@ -2010,10 +2008,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2041,9 +2043,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2084,99 +2084,23 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
-
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
- return canvas;
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
+ return $(canvas);
}
});
@@ -2236,187 +2160,18 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
-
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
- }
-
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
-
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
- }
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
- }
- */
- return highest_slot + 1;
- },
- /**
- * Draw summary tree on canvas.
- */
- draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
-
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text(max);
-
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
-
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
+
+ return inc_slots.slot_features( features );
},
/**
* Draw feature.
@@ -2579,29 +2334,18 @@
* Draw FeatureTrack tile.
*/
draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
- var track = this;
- var tile_low = tile_index * DENSITY * resolution,
+ var track = this,
+ tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_span = tile_high - tile_low,
- params = { hda_ldda: track.hda_ldda, dataset_id: track.dataset_id,
- resolution: this.view.resolution, mode: this.mode };
-
- //
- // Create/set/compute some useful vars.
- //
- var width = Math.ceil( tile_span * w_scale ),
+ width = Math.ceil( tile_span * w_scale ),
mode = this.mode,
min_height = 25,
left_offset = this.left_offset,
- slots, required_height;
+ slots,
+ required_height;
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- //
- // Set mode if Auto.
- //
+ // Set display mode if Auto.
if (mode === "Auto") {
if (result.dataset_type === "summary_tree") {
mode = result.dataset_type;
@@ -2626,55 +2370,68 @@
}
}
}
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Wrapped canvas element is returned
+ return $(canvas);
+ }
+ // Start dealing with row-by-row tracks
+
+ // y_scale is the height per row
var y_scale = this.get_y_scale(mode);
- //
// Pack reads, set required height.
- //
- if (mode === "summary_tree") {
- required_height = this.summary_draw_height;
- }
if (mode === "Dense") {
required_height = min_height;
- }
- else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
+ } else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
+ slots = this.inc_slots[w_scale].slots;
}
//
// Set up for drawing.
//
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
- if (result.dataset_type === "summary_tree") {
- // Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
- }
+ var canvas = this.view.canvas_manager.new_canvas();
+
+ canvas.width = width + left_offset;
+ canvas.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 = canvas.get(0).getContext("2d");
+ var ctx = canvas.getContext("2d");
ctx.fillStyle = this.prefs.block_color;
ctx.font = this.default_font;
ctx.textAlign = "right";
this.container_div.find(".yaxislabel").remove();
- //
- // Draw summary tree. If tree is drawn, canvas is returned.
- //
- if (mode === "summary_tree") {
- this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
- tile_low, left_offset);
- return canvas;
- }
-
- //
// If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
// to indicate region where message is applicable
if (result.message) {
- canvas.css({
+ $(canvas).css({
"border-top": "1px solid red"
});
@@ -2687,7 +2444,7 @@
// If there's no data, return.
if (!result.data) {
- return canvas;
+ return $(canvas);
}
}
@@ -2730,7 +2487,7 @@
width, left_offset, ref_seq);
}
}
- return canvas;
+ return $(canvas);
}
});
@@ -3131,4 +2888,326 @@
}
});
+// ---- To be extracted ------------------------------------------------------
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) {
+ // Data and data properties
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
+
+ var view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
+
+ var points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ // Data and data properties
+ this.data = data;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
\ No newline at end of file
http://bitbucket.org/galaxy/galaxy-central/changeset/7366cc533040/
changeset: r5313:7366cc533040
user: james_taylor
date: 2011-03-30 19:09:42
summary: Fix accidently introduced tabs
affected #: 1 file (1.9 KB)
--- a/static/scripts/trackster.js Wed Mar 30 13:01:41 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 13:09:42 2011 -0400
@@ -2012,10 +2012,10 @@
};
$.extend(LineTrack.prototype, TiledTrack.prototype, {
add_resize_handle: function () {
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- var track = this;
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2086,20 +2086,20 @@
var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- width = Math.ceil( tile_length * w_scale ),
- height = this.height_px;
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
- // Create canvas
+ // Create canvas
var canvas = this.view.canvas_manager.new_canvas();
canvas.width = width,
canvas.height = height;
- // Paint line onto full canvas
- var ctx = canvas.getContext("2d");
- var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
- this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
- painter.draw( ctx, width, height );
-
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
+
return $(canvas);
}
});
@@ -2170,8 +2170,8 @@
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
}
-
- return inc_slots.slot_features( features );
+
+ return inc_slots.slot_features( features );
},
/**
* Draw feature.
@@ -2335,15 +2335,15 @@
*/
draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ tile_low = tile_index * DENSITY * resolution,
tile_high = ( tile_index + 1 ) * DENSITY * resolution,
tile_span = tile_high - tile_low,
- width = Math.ceil( tile_span * w_scale ),
+ width = Math.ceil( tile_span * w_scale ),
mode = this.mode,
min_height = 25,
left_offset = this.left_offset,
slots,
- required_height;
+ required_height;
// Set display mode if Auto.
if (mode === "Auto") {
@@ -2370,37 +2370,37 @@
}
}
}
-
- // Drawing the summary tree (feature coverage histogram)
- if ( mode === "summary_tree" ) {
- // Set height of parent_element
- required_height = this.summary_draw_height;
- parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
- // Add label to container div showing maximum count
- // TODO: this shouldn't be done at the tile level
- this.container_div.find(".yaxislabel").remove();
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text( result.max );
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
- // Create canvas
- var canvas = this.view.canvas_manager.new_canvas();
- canvas.width = width + left_offset;
- // Extra padding at top of summary tree
- canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- // Paint summary tree into canvas
- var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
- var ctx = canvas.getContext("2d");
- // Deal with left_offset by translating
- ctx.translate( left_offset, 0 );
- painter.draw( ctx, width, required_height );
- // Wrapped canvas element is returned
- return $(canvas);
- }
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Wrapped canvas element is returned
+ return $(canvas);
+ }
- // Start dealing with row-by-row tracks
+ // Start dealing with row-by-row tracks
- // y_scale is the height per row
+ // y_scale is the height per row
var y_scale = this.get_y_scale(mode);
// Pack reads, set required height.
@@ -2415,8 +2415,8 @@
//
// Set up for drawing.
//
- var canvas = this.view.canvas_manager.new_canvas();
-
+ var canvas = this.view.canvas_manager.new_canvas();
+
canvas.width = width + left_offset;
canvas.height = required_height;
@@ -2934,14 +2934,14 @@
// by only having to look at a smaller subset
// if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
- } else {
- undone.push(i);
- }
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
}
// Slot unslotted features.
@@ -2949,106 +2949,106 @@
// Find the first slot such that current feature doesn't overlap any other features in that slot.
// Returns -1 if no slot was found.
var find_slot = function(f_start, f_end) {
- // TODO: Fix constants
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
- }
- }
- if (!has_overlap) {
- return slot_num;
- }
- }
- return -1;
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
+ }
+ }
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
};
// Do slotting.
for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( feature_start * w_scale ),
- f_end = Math.ceil( feature_end * w_scale ),
- text_len = this.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && this.include_label ) {
- // Add gap for label spacing and extra pack space padding
- // TODO: Fix constants
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
-
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
- }
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
}
// Debugging: view slots data.
/*
for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
}
*/
return highest_slot + 1;
@@ -3074,36 +3074,36 @@
SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
var view_start = this.view_start,
- view_range = this.view_end - this.view_start,
- w_scale = width / view_range;
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
var points = this.data, delta = this.delta, max = this.max,
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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;
-
- ctx.fillStyle = "black";
- ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + (delta_x_px/2), 10);
- }
+
+ 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;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + (delta_x_px/2), 10);
+ }
}
ctx.restore();
@@ -3124,16 +3124,16 @@
LinePainter.prototype.draw = function( ctx, width, height ) {
var
- in_path = false,
- min_value = this.min_value,
- max_value = this.max_value,
- vertical_range = max_value - min_value,
- height_px = height,
- view_start = this.view_start,
- view_range = this.view_end - this.view_start,
- w_scale = width / view_range,
- mode = this.mode,
- data = this.data;
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
ctx.save();
@@ -3142,72 +3142,72 @@
// Line at 0.0
if ( mode !== "Intensity" ) {
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( width, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
}
ctx.beginPath();
ctx.fillStyle = this.color;
var x_scaled, y, delta_x_px;
if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
} else {
- delta_x_px = 10;
+ delta_x_px = 10;
}
for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - view_start) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
+ }
+ }
+ }
}
if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
} else {
- ctx.stroke();
+ ctx.stroke();
}
ctx.restore();
-}
\ No newline at end of file
+}
http://bitbucket.org/galaxy/galaxy-central/changeset/ac381183f760/
changeset: r5314:ac381183f760
user: james_taylor
date: 2011-03-31 00:30:22
summary: Extracted feature painters for generic linked features, VCF, and reads. Not all types have been completely tested yet
affected #: 1 file (3.1 KB)
--- a/static/scripts/trackster.js Wed Mar 30 13:09:42 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 18:30:22 2011 -0400
@@ -1960,7 +1960,7 @@
var c_start = Math.round(c * w_scale);
ctx.fillText(seq[c], c_start + track.left_offset, 10);
}
- return $(canvas);
+ return canvas;
}
this.content_div.css("height", "0px");
}
@@ -2100,7 +2100,7 @@
this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
painter.draw( ctx, width, height );
- return $(canvas);
+ return canvas;
}
});
@@ -2150,6 +2150,8 @@
this.tile_cache = new Cache(CACHED_TILES_FEATURE);
this.data_cache = new DataManager(20, this);
this.left_offset = 200;
+
+ this.painter = LinkedFeaturePainter;
};
$.extend(FeatureTrack.prototype, TiledTrack.prototype, {
/**
@@ -2174,141 +2176,6 @@
return inc_slots.slot_features( features );
},
/**
- * Draw feature.
- */
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset) {
- var
- feature_uid = feature[0],
- feature_start = feature[1],
- // -1 b/c intervals are half-open.
- feature_end = feature[2] - 1,
- 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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
- thickness, y_start, thick_start = null, thick_end = null,
- block_color = this.prefs.block_color,
- label_color = this.prefs.label_color;
-
- // Dense mode displays the same for all data.
- if (mode === "Dense") {
- ctx.fillStyle = block_color;
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
- }
- else if (mode === "no_detail") {
- // No details for feature, so only one way to display.
- ctx.fillStyle = block_color;
- // TODO: what should width be here?
- ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
- }
- else { // Mode is either Squish or Pack:
- // Feature details.
- var feature_strand = feature[4],
- feature_ts = feature[5],
- feature_te = feature[6],
- feature_blocks = feature[7];
-
- if (feature_ts && feature_te) {
- thick_start = Math.floor( Math.max(0, (feature_ts - tile_low) * w_scale) );
- thick_end = Math.ceil( Math.min(width, Math.max(0, (feature_te - tile_low) * w_scale)) );
- }
-
- // Set vars that depend on mode.
- var thin_height, thick_height;
- if (mode === "Squish") {
- thin_height = 1;
- thick_height = SQUISH_FEATURE_HEIGHT;
- } else { // mode === "Pack"
- thin_height = 5;
- thick_height = PACK_FEATURE_HEIGHT;
- }
-
- // Draw feature/feature blocks + connectors.
- if (!feature_blocks) {
- // If there are no blocks, treat the feature as one big exon.
- if ( feature.strand ) {
- if (feature.strand === "+") {
- ctx.fillStyle = RIGHT_STRAND_INV;
- } else if (feature.strand === "-") {
- ctx.fillStyle = LEFT_STRAND_INV;
- }
- }
- else { // No strand.
- ctx.fillStyle = block_color;
- }
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thick_height);
- } else {
- // There are feature blocks and mode is either Squish or Pack.
- //
- // Approach: (a) draw whole feature as connector/intron and (b) draw blocks as
- // needed. This ensures that whole feature, regardless of whether it starts with
- // a block, is visible.
- //
-
- // Draw whole feature as connector/intron.
- var cur_y_center, cur_height;
- if (mode === "Squish") {
- 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 = RIGHT_STRAND;
- } else if (feature_strand === "-") {
- ctx.fillStyle = LEFT_STRAND;
- }
- }
- else {
- ctx.fillStyle = CONNECTOR_COLOR;
- cur_y_center += (SQUISH_FEATURE_HEIGHT/2) + 1;
- cur_height = 1;
- }
- }
- ctx.fillRect(f_start + left_offset, cur_y_center, f_end - f_start, cur_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) ),
- // -1 b/c intervals are half-open.
- block_end = Math.ceil( Math.min(width, Math.max((block[1] - 1 - tile_low) * w_scale)) );
-
- // Skip drawing if block not on tile.
- if (block_start > block_end) { continue; }
-
- // Draw thin block.
- ctx.fillStyle = block_color;
- ctx.fillRect(block_start + left_offset, y_center + (thick_height-thin_height)/2 + 1,
- block_end - block_start, thin_height);
-
- // If block intersects with thick region, draw block as thick.
- if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) {
- var block_thick_start = Math.max(block_start, thick_start),
- block_thick_end = Math.min(block_end, thick_end);
- ctx.fillRect(block_thick_start + left_offset, y_center + 1,
- block_thick_end - block_thick_start, thick_height);
- }
- }
- }
-
- // Draw label for Pack mode.
- if (mode === "Pack" && feature_start > tile_low) {
- ctx.fillStyle = label_color;
- if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
- ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING, y_center + 8);
- } else {
- ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING, y_center + 8);
- }
- ctx.fillStyle = block_color;
- }
- }
- },
- /**
* Returns y_scale based on mode.
*/
get_y_scale: function(mode) {
@@ -2394,27 +2261,44 @@
// Deal with left_offset by translating
ctx.translate( left_offset, 0 );
painter.draw( ctx, width, required_height );
- // Wrapped canvas element is returned
- return $(canvas);
+ // Canvas element is returned
+ return canvas;
}
// Start dealing with row-by-row tracks
-
- // y_scale is the height per row
- var y_scale = this.get_y_scale(mode);
-
- // Pack reads, set required height.
- if (mode === "Dense") {
- required_height = min_height;
- } else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
- // Calculate new slots incrementally for this new chunk of data and update height if necessary.
- required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
+
+ // If working with a mode where slotting is neccesary, update the incremental slotting
+ var slots, slots_required = 1;
+ if ( mode === "no_detail" || mode === "Squish" || mode === "Pack" ) {
+ slots_required = this.incremental_slots(w_scale, result.data, mode);
slots = this.inc_slots[w_scale].slots;
}
+
+ // Filter features
+ var filtered = [];
+ if ( result.data ) {
+ for (var i = 0, len = result.data.length; i < len; i++) {
+ var feature = result.data[i];
+ var hide_feature = false;
+ var filter;
+ for (var f = 0, flen = this.filters.length; f < flen; f++) {
+ filter = this.filters[f];
+ filter.update_attrs(feature);
+ if (!filter.keep(feature)) {
+ hide_feature = true;
+ break;
+ }
+ }
+ if (!hide_feature) {
+ filtered.push( feature );
+ }
+ }
+ }
- //
- // Set up for drawing.
- //
+ // Create painter, and canvas of sufficient size to contain all features
+ // 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, ref_seq );
+ var required_height = painter.get_required_height( slots_required );
var canvas = this.view.canvas_manager.new_canvas();
canvas.width = width + left_offset;
@@ -2444,112 +2328,29 @@
// If there's no data, return.
if (!result.data) {
- return $(canvas);
+ return canvas;
}
}
- //
// Set example feature. This is needed so that track can update its UI based on feature attributes.
- //
this.example_feature = (result.data.length ? result.data[0] : undefined);
-
- //
- // Draw elements.
- //
- var data = result.data;
- for (var i = 0, len = data.length; i < len; i++) {
- var feature = data[i],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- // Slot valid only if features are slotted and this feature is slotted;
- // feature may not be due to lack of space.
- slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
-
- // Apply filters to feature.
- var hide_feature = false;
- var filter;
- for (var f = 0; f < this.filters.length; f++) {
- filter = this.filters[f];
- filter.update_attrs(feature);
- if (!filter.keep(feature)) {
- hide_feature = true;
- break;
- }
- }
- if (hide_feature) {
- continue;
- }
+
+ // Draw features
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height, slots );
- // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
- if (is_overlap([feature_start, feature_end], [tile_low, tile_high]) && (mode == "Dense" || slot !== null)) {
- this.draw_element(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale,
- width, left_offset, ref_seq);
- }
- }
- return $(canvas);
+ return canvas;
}
});
var VcfTrack = function(name, view, hda_ldda, dataset_id, prefs, filters) {
FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
this.track_type = "VcfTrack";
+ this.painter = VariantPainter;
};
-$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * Draw a VCF entry.
- */
- draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width) {
- 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_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) ),
- f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
- thickness, y_start, thick_start = null, thick_end = null;
-
- if (no_label) {
- ctx.fillStyle = block_color;
- ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, 1);
- } else { // Show blocks, labels, etc.
- // Unpack.
- var ref_base = feature[4], alt_base = feature[5], qual = feature[6];
-
- // Draw block for entry.
- thickness = 9;
- y_start = 1;
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thickness);
-
- // Add label for entry.
- if (mode !== "Dense" && feature_name !== undefined && feature_start > tile_low) {
- // Draw label
- ctx.fillStyle = label_color;
- if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
- ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + 2 + left_offset, y_center + 8);
- } else {
- ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start - 2 + left_offset, y_center + 8);
- }
- ctx.fillStyle = block_color;
- }
-
- // Show additional data on block.
- var vcf_label = ref_base + " / " + alt_base;
- if (feature_start > tile_low && ctx.measureText(vcf_label).width < (f_end - f_start)) {
- ctx.fillStyle = "white";
- ctx.textAlign = "center";
- ctx.fillText(vcf_label, left_offset + f_start + (f_end-f_start)/2, y_center + 8);
- ctx.fillStyle = block_color;
- }
- }
- }
-});
+$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
+
var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) {
FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
@@ -2573,281 +2374,10 @@
this.prefs = this.track_config.values;
this.track_type = "ReadTrack";
+ this.painter = ReadPainter;
this.make_name_popup_menu();
};
-$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * Returns y_scale based on mode.
- */
- get_y_scale: function(mode) {
- var y_scale;
- if (mode === "summary_tree") {
- // No scale needed.
- }
- if (mode === "Dense") {
- y_scale = DENSE_TRACK_HEIGHT;
- }
- else if (mode === "Squish") {
- y_scale = SQUISH_TRACK_HEIGHT;
- }
- else { // mode === "Pack"
- y_scale = PACK_TRACK_HEIGHT;
- if (this.track_config.values.show_insertions) {
- y_scale *= 2;
- }
- }
- return y_scale;
- },
- /**
- * Draw a single read.
- */
- draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center, ref_seq) {
- ctx.textAlign = "center";
- var track = this,
- tile_region = [tile_low, tile_high],
- base_offset = 0,
- seq_offset = 0,
- gap = 0;
-
- // Keep list of items that need to be drawn on top of initial drawing layer.
- var draw_last = [];
-
- // Gap is needed so that read is offset and hence first base can be drawn on read.
- // TODO-FIX: using this gap offsets reads so that their start is not visually in sync with other tracks.
- if ((mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
- gap = Math.round(w_scale/2);
- }
- if (!cigar) {
- // If no cigar string, then assume all matches
- cigar = [ [0, orig_seq.length] ]
- }
- for (var cig_id = 0, len = cigar.length; cig_id < len; cig_id++) {
- var cig = cigar[cig_id],
- cig_op = "MIDNSHP=X"[cig[0]],
- cig_len = cig[1];
-
- if (cig_op === "H" || cig_op === "S") {
- // Go left if it clips
- base_offset -= cig_len;
- }
- var seq_start = feature_start + base_offset,
- s_start = Math.floor( Math.max(0, (seq_start - tile_low) * w_scale) ),
- s_end = Math.floor( Math.max(0, (seq_start + cig_len - tile_low) * w_scale) );
-
- switch (cig_op) {
- case "H": // Hard clipping.
- // TODO: draw anything?
- // Sequence not present, so do not increment seq_offset.
- break;
- case "S": // Soft clipping.
- case "M": // Match.
- case "=": // Equals.
- var seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region);
- if (seq_tile_overlap !== NO_OVERLAP) {
- // Draw.
- var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
- if (gap > 0) {
- ctx.fillStyle = this.prefs.block_color;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
- ctx.fillStyle = CONNECTOR_COLOR;
- // TODO: this can be made much more efficient by computing the complete sequence
- // to draw and then drawing it.
- for (var c = 0, str_len = seq.length; c < str_len; c++) {
- if (this.track_config.values.show_differences && ref_seq) {
- var ref_char = ref_seq[seq_start - tile_low + c];
- if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
- continue;
- }
- }
- if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
- var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
- }
- }
- } else {
- ctx.fillStyle = this.prefs.block_color;
- // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(s_start + this.left_offset,
- y_center + (this.mode !== "Dense" ? 4 : 5),
- s_end - s_start,
- (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
- }
- }
- seq_offset += cig_len;
- base_offset += cig_len;
- break;
- case "N": // Skipped bases.
- ctx.fillStyle = CONNECTOR_COLOR;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
- //ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
- // No change in seq_offset because sequence not used when skipping.
- base_offset += cig_len;
- break;
- case "D": // Deletion.
- ctx.fillStyle = "red";
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
- // TODO: is this true? No change in seq_offset because sequence not used when skipping.
- base_offset += cig_len;
- break;
- case "P": // TODO: No good way to draw insertions/padding right now, so ignore
- // Sequences not present, so do not increment seq_offset.
- break;
- case "I": // Insertion.
- // Check to see if sequence should be drawn at all by looking at the overlap between
- // the sequence region and the tile region.
- var
- seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
- insert_x_coord = this.left_offset + s_start - gap;
-
- if (seq_tile_overlap !== NO_OVERLAP) {
- var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
- // Insertion point is between the sequence start and the previous base: (-gap) moves
- // back from sequence start to insertion point.
- if (this.track_config.values.show_insertions) {
- //
- // Show inserted sequence above, centered on insertion point.
- //
-
- // Draw sequence.
- // X center is offset + start - <half_sequence_length>
- var x_center = this.left_offset + s_start - (s_end - s_start)/2;
- if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
- // Draw sequence container.
- ctx.fillStyle = "yellow";
- ctx.fillRect(x_center - gap, y_center - 9, s_end - s_start, 9);
- draw_last[draw_last.length] = {type: "triangle", data: [insert_x_coord, y_center + 4, 5]};
- ctx.fillStyle = CONNECTOR_COLOR;
- // Based on overlap b/t sequence and tile, get sequence to be drawn.
- switch(seq_tile_overlap) {
- case(OVERLAP_START):
- seq = seq.slice(tile_low-seq_start);
- break;
- case(OVERLAP_END):
- seq = seq.slice(0, seq_start-tile_high);
- break;
- case(CONTAINED_BY):
- // All of sequence drawn.
- break;
- case(CONTAINS):
- seq = seq.slice(tile_low-seq_start, seq_start-tile_high);
- break;
- }
- // Draw sequence.
- for (var c = 0, str_len = seq.length; c < str_len; c++) {
- var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
- }
- }
- else {
- // Draw block.
- ctx.fillStyle = "yellow";
- // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(x_center, y_center + (this.mode !== "Dense" ? 2 : 5),
- s_end - s_start, (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT));
- }
- }
- else {
- if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
- // Show insertions with a single number at the insertion point.
- draw_last[draw_last.length] = {type: "text", data: [seq.length, insert_x_coord, y_center + 9]};
- }
- else {
- // TODO: probably can merge this case with code above.
- }
- }
- }
- seq_offset += cig_len;
- // No change to base offset because insertions are drawn above sequence/read.
- break;
- case "X":
- // TODO: draw something?
- seq_offset += cig_len;
- break;
- }
- }
-
- //
- // Draw last items.
- //
- ctx.fillStyle = "yellow";
- var item, type, data;
- for (var i = 0; i < draw_last.length; i++) {
- item = draw_last[i];
- type = item["type"];
- data = item["data"];
- if (type === "text") {
- ctx.font = "bold " + DEFAULT_FONT;
- ctx.fillText(data[0], data[1], data[2]);
- ctx.font = DEFAULT_FONT;
- }
- else if (type == "triangle") {
- ctx.drawDownwardEquilateralTriangle(data[0], data[1], data[2]);
- }
- }
- },
- /**
- * Draw a complete read.
- */
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset, ref_seq) {
- // All features need a start, end, and vertical center.
- var feature_uid = feature[0],
- feature_start = feature[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)) ),
- y_center = (mode === "Dense" ? 1 : (1 + slot)) * y_scale,
- block_color = this.prefs.block_color,
- label_color = this.prefs.label_color,
- // Left-gap for label text since we align chrom text to the position tick.
- gap = 0;
-
- // TODO: fix gap usage; also see note on gap in draw_read.
- if ((mode === "Pack" || this.mode === "Auto") && w_scale > CHAR_WIDTH_PX) {
- var gap = Math.round(w_scale/2);
- }
-
- // Draw read.
- ctx.fillStyle = block_color;
- if (feature[5] instanceof Array) {
- // Read is paired.
- var b1_start = Math.floor( Math.max(0, (feature[4][0] - tile_low) * w_scale) ),
- b1_end = Math.ceil( Math.min(width, Math.max(0, (feature[4][1] - tile_low) * w_scale)) ),
- b2_start = Math.floor( Math.max(0, (feature[5][0] - tile_low) * w_scale) ),
- b2_end = Math.ceil( Math.min(width, Math.max(0, (feature[5][1] - tile_low) * w_scale)) );
-
- // Draw left/forward read.
- if (feature[4][1] >= tile_low && feature[4][0] <= tile_high && feature[4][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center, ref_seq);
- }
- // Draw right/reverse read.
- if (feature[5][1] >= tile_low && feature[5][0] <= tile_high && feature[5][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center, ref_seq);
- }
- // Draw connector.
- if (b2_start > b1_end) {
- ctx.fillStyle = CONNECTOR_COLOR;
- ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
- }
- } else {
- // Read is single.
- ctx.fillStyle = block_color;
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center, ref_seq);
- }
- if (mode === "Pack" && feature_start > tile_low) {
- // Draw label.
- ctx.fillStyle = this.prefs.label_color;
- if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
- ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
- } else {
- ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
- }
- ctx.fillStyle = block_color;
- }
- }
-});
+$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
/**
* Feature track that displays data generated from tool.
@@ -2890,6 +2420,18 @@
// ---- To be extracted ------------------------------------------------------
+// ---- Simple extend function for inheritence ----
+
+var extend = function() {
+ var target = arguments[0];
+ for ( var i = 1; i < arguments.length; i++ ) {
+ var other = arguments[i];
+ for ( key in other ) {
+ target[key] = other[key];
+ }
+ }
+}
+
// ---- Canvas management ----
var CanvasManager = function( document ) {
@@ -3211,3 +2753,561 @@
ctx.restore();
}
+
+var FeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ this.prefs = prefs;
+ this.mode = mode;
+}
+
+extend( FeaturePainter.prototype, {
+
+ get_required_height: function( rows_required ) {
+ // 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") {
+ // Calculate new slots incrementally for this new chunk of data and update height if necessary.
+ required_height = rows_required * y_scale;
+ }
+ return required_height;
+ },
+
+ draw: function( ctx, width, height, slots ) {
+
+ var data = this.data, view_start = this.view_start, view_end = this.view_end;
+
+ ctx.save();
+
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.textAlign = "right";
+
+ var view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ y_scale = this.get_row_height();
+
+ for (var i = 0, len = data.length; i < len; i++) {
+ var feature = data[i],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ // Slot valid only if features are slotted and this feature is slotted;
+ // feature may not be due to lack of space.
+ slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
+
+ // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
+ if (is_overlap([feature_start, feature_end], [view_start, view_end]) && (this.mode == "Dense" || slot !== null)) {
+ this.draw_element(ctx, this.mode, feature, slot, view_start, view_end, w_scale, y_scale,
+ width );
+ }
+ }
+
+ ctx.restore();
+ }
+});
+
+var LinkedFeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
+
+ /**
+ * Height of a single row, depends on mode
+ */
+ get_row_height: function() {
+ var mode = this.mode, y_scale;
+ if (mode === "summary_tree") {
+ // No scale needed.
+ }
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "no_detail") {
+ y_scale = NO_DETAIL_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ }
+ return y_scale;
+ },
+
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
+ var
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ // -1 b/c intervals are half-open.
+ feature_end = feature[2] - 1,
+ 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)) ),
+ y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ thickness, y_start, thick_start = null, thick_end = null,
+ block_color = this.prefs.block_color,
+ label_color = this.prefs.label_color;
+
+ // Dense mode displays the same for all data.
+ if (mode === "Dense") {
+ ctx.fillStyle = block_color;
+ ctx.fillRect(f_start, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ }
+ else if (mode === "no_detail") {
+ // No details for feature, so only one way to display.
+ ctx.fillStyle = block_color;
+ // TODO: what should width be here?
+ ctx.fillRect(f_start, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ }
+ else { // Mode is either Squish or Pack:
+ // Feature details.
+ var feature_strand = feature[4],
+ feature_ts = feature[5],
+ feature_te = feature[6],
+ feature_blocks = feature[7];
+
+ if (feature_ts && feature_te) {
+ thick_start = Math.floor( Math.max(0, (feature_ts - tile_low) * w_scale) );
+ thick_end = Math.ceil( Math.min(width, Math.max(0, (feature_te - tile_low) * w_scale)) );
+ }
+
+ // Set vars that depend on mode.
+ var thin_height, thick_height;
+ if (mode === "Squish") {
+ thin_height = 1;
+ thick_height = SQUISH_FEATURE_HEIGHT;
+ } else { // mode === "Pack"
+ thin_height = 5;
+ thick_height = PACK_FEATURE_HEIGHT;
+ }
+
+ // Draw feature/feature blocks + connectors.
+ if (!feature_blocks) {
+ // If there are no blocks, treat the feature as one big exon.
+ if ( feature.strand ) {
+ if (feature.strand === "+") {
+ ctx.fillStyle = RIGHT_STRAND_INV;
+ } else if (feature.strand === "-") {
+ ctx.fillStyle = LEFT_STRAND_INV;
+ }
+ }
+ else { // No strand.
+ ctx.fillStyle = block_color;
+ }
+ ctx.fillRect(f_start, y_center, f_end - f_start, thick_height);
+ } else {
+ // There are feature blocks and mode is either Squish or Pack.
+ //
+ // Approach: (a) draw whole feature as connector/intron and (b) draw blocks as
+ // needed. This ensures that whole feature, regardless of whether it starts with
+ // a block, is visible.
+ //
+
+ // Draw whole feature as connector/intron.
+ var cur_y_center, cur_height;
+ if (mode === "Squish") {
+ 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 = RIGHT_STRAND;
+ } else if (feature_strand === "-") {
+ ctx.fillStyle = LEFT_STRAND;
+ }
+ }
+ 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);
+
+ 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)) );
+
+ // Skip drawing if block not on tile.
+ if (block_start > block_end) { continue; }
+
+ // Draw thin block.
+ ctx.fillStyle = block_color;
+ ctx.fillRect(block_start, y_center + (thick_height-thin_height)/2 + 1,
+ block_end - block_start, thin_height);
+
+ // If block intersects with thick region, draw block as thick.
+ if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) {
+ var block_thick_start = Math.max(block_start, thick_start),
+ block_thick_end = Math.min(block_end, thick_end);
+ ctx.fillRect(block_thick_start, y_center + 1,
+ block_thick_end - block_thick_start, thick_height);
+ }
+ }
+ }
+
+ // Draw label for Pack mode.
+ if (mode === "Pack" && feature_start > tile_low) {
+ ctx.fillStyle = label_color;
+ // FIXME: do this without tile_index
+ var tile_index = 1;
+ if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
+ ctx.textAlign = "left";
+ ctx.fillText(feature_name, f_end + LABEL_SPACING, y_center + 8);
+ } else {
+ ctx.textAlign = "right";
+ ctx.fillText(feature_name, f_start - LABEL_SPACING, y_center + 8);
+ }
+ ctx.fillStyle = block_color;
+ }
+ }
+ }
+});
+
+
+var VariantPainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( VariantPainter.prototype, FeaturePainter.prototype, {
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width) {
+ 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_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) ),
+ f_end = Math.ceil( Math.min(width, Math.max(0, (feature_end - tile_low) * w_scale)) ),
+ y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ thickness, y_start, thick_start = null, thick_end = null;
+
+ if (no_label) {
+ ctx.fillStyle = block_color;
+ ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, 1);
+ } else { // Show blocks, labels, etc.
+ // Unpack.
+ var ref_base = feature[4], alt_base = feature[5], qual = feature[6];
+
+ // Draw block for entry.
+ thickness = 9;
+ y_start = 1;
+ ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thickness);
+
+ // Add label for entry.
+ if (mode !== "Dense" && feature_name !== undefined && feature_start > tile_low) {
+ // Draw label
+ ctx.fillStyle = label_color;
+ if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
+ ctx.textAlign = "left";
+ ctx.fillText(feature_name, f_end + 2 + left_offset, y_center + 8);
+ } else {
+ ctx.textAlign = "right";
+ ctx.fillText(feature_name, f_start - 2 + left_offset, y_center + 8);
+ }
+ ctx.fillStyle = block_color;
+ }
+
+ // Show additional data on block.
+ var vcf_label = ref_base + " / " + alt_base;
+ if (feature_start > tile_low && ctx.measureText(vcf_label).width < (f_end - f_start)) {
+ ctx.fillStyle = "white";
+ ctx.textAlign = "center";
+ ctx.fillText(vcf_label, left_offset + f_start + (f_end-f_start)/2, y_center + 8);
+ ctx.fillStyle = block_color;
+ }
+ }
+ }
+});
+
+var ReadPainter = function( data, view_start, view_end, prefs, mode, ref_seq ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+ this.ref_seq = ref_seq;
+}
+
+extend( ReadPainter.prototype, FeaturePainter.prototype, {
+ /**
+ * Returns y_scale based on mode.
+ */
+ get_row_height: function() {
+ var y_scale, mode = this.mode;
+ if (mode === "summary_tree") {
+ // No scale needed.
+ }
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ if (this.track_config.values.show_insertions) {
+ y_scale *= 2;
+ }
+ }
+ return y_scale;
+ },
+ /**
+ * Draw a single read.
+ */
+ draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center) {
+ ctx.textAlign = "center";
+ var track = this,
+ tile_region = [tile_low, tile_high],
+ base_offset = 0,
+ seq_offset = 0,
+ gap = 0
+ ref_seq = this.ref_seq;
+
+ // Keep list of items that need to be drawn on top of initial drawing layer.
+ var draw_last = [];
+
+ // Gap is needed so that read is offset and hence first base can be drawn on read.
+ // TODO-FIX: using this gap offsets reads so that their start is not visually in sync with other tracks.
+ if ((mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
+ gap = Math.round(w_scale/2);
+ }
+ if (!cigar) {
+ // If no cigar string, then assume all matches
+ cigar = [ [0, orig_seq.length] ]
+ }
+ for (var cig_id = 0, len = cigar.length; cig_id < len; cig_id++) {
+ var cig = cigar[cig_id],
+ cig_op = "MIDNSHP=X"[cig[0]],
+ cig_len = cig[1];
+
+ if (cig_op === "H" || cig_op === "S") {
+ // Go left if it clips
+ base_offset -= cig_len;
+ }
+ var seq_start = feature_start + base_offset,
+ s_start = Math.floor( Math.max(0, (seq_start - tile_low) * w_scale) ),
+ s_end = Math.floor( Math.max(0, (seq_start + cig_len - tile_low) * w_scale) );
+
+ switch (cig_op) {
+ case "H": // Hard clipping.
+ // TODO: draw anything?
+ // Sequence not present, so do not increment seq_offset.
+ break;
+ case "S": // Soft clipping.
+ case "M": // Match.
+ case "=": // Equals.
+ var seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region);
+ if (seq_tile_overlap !== NO_OVERLAP) {
+ // Draw.
+ var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
+ if (gap > 0) {
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
+ ctx.fillStyle = CONNECTOR_COLOR;
+ // TODO: this can be made much more efficient by computing the complete sequence
+ // to draw and then drawing it.
+ for (var c = 0, str_len = seq.length; c < str_len; c++) {
+ if (this.track_config.values.show_differences && ref_seq) {
+ var ref_char = ref_seq[seq_start - tile_low + c];
+ if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
+ continue;
+ }
+ }
+ if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
+ var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
+ ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
+ }
+ }
+ } else {
+ ctx.fillStyle = this.prefs.block_color;
+ // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
+ ctx.fillRect(s_start + this.left_offset,
+ y_center + (this.mode !== "Dense" ? 4 : 5),
+ s_end - s_start,
+ (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
+ }
+ }
+ seq_offset += cig_len;
+ base_offset += cig_len;
+ break;
+ case "N": // Skipped bases.
+ ctx.fillStyle = CONNECTOR_COLOR;
+ ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
+ //ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
+ // No change in seq_offset because sequence not used when skipping.
+ base_offset += cig_len;
+ break;
+ case "D": // Deletion.
+ ctx.fillStyle = "red";
+ ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
+ // TODO: is this true? No change in seq_offset because sequence not used when skipping.
+ base_offset += cig_len;
+ break;
+ case "P": // TODO: No good way to draw insertions/padding right now, so ignore
+ // Sequences not present, so do not increment seq_offset.
+ break;
+ case "I": // Insertion.
+ // Check to see if sequence should be drawn at all by looking at the overlap between
+ // the sequence region and the tile region.
+ var
+ seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
+ insert_x_coord = this.left_offset + s_start - gap;
+
+ if (seq_tile_overlap !== NO_OVERLAP) {
+ var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
+ // Insertion point is between the sequence start and the previous base: (-gap) moves
+ // back from sequence start to insertion point.
+ if (this.track_config.values.show_insertions) {
+ //
+ // Show inserted sequence above, centered on insertion point.
+ //
+
+ // Draw sequence.
+ // X center is offset + start - <half_sequence_length>
+ var x_center = this.left_offset + s_start - (s_end - s_start)/2;
+ if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
+ // Draw sequence container.
+ ctx.fillStyle = "yellow";
+ ctx.fillRect(x_center - gap, y_center - 9, s_end - s_start, 9);
+ draw_last[draw_last.length] = {type: "triangle", data: [insert_x_coord, y_center + 4, 5]};
+ ctx.fillStyle = CONNECTOR_COLOR;
+ // Based on overlap b/t sequence and tile, get sequence to be drawn.
+ switch(seq_tile_overlap) {
+ case(OVERLAP_START):
+ seq = seq.slice(tile_low-seq_start);
+ break;
+ case(OVERLAP_END):
+ seq = seq.slice(0, seq_start-tile_high);
+ break;
+ case(CONTAINED_BY):
+ // All of sequence drawn.
+ break;
+ case(CONTAINS):
+ seq = seq.slice(tile_low-seq_start, seq_start-tile_high);
+ break;
+ }
+ // Draw sequence.
+ for (var c = 0, str_len = seq.length; c < str_len; c++) {
+ var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
+ ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
+ }
+ }
+ else {
+ // Draw block.
+ ctx.fillStyle = "yellow";
+ // TODO: This is a pretty hack-ish way to fill rectangle based on mode.
+ ctx.fillRect(x_center, y_center + (this.mode !== "Dense" ? 2 : 5),
+ s_end - s_start, (mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT));
+ }
+ }
+ else {
+ if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
+ // Show insertions with a single number at the insertion point.
+ draw_last[draw_last.length] = {type: "text", data: [seq.length, insert_x_coord, y_center + 9]};
+ }
+ else {
+ // TODO: probably can merge this case with code above.
+ }
+ }
+ }
+ seq_offset += cig_len;
+ // No change to base offset because insertions are drawn above sequence/read.
+ break;
+ case "X":
+ // TODO: draw something?
+ seq_offset += cig_len;
+ break;
+ }
+ }
+
+ //
+ // Draw last items.
+ //
+ ctx.fillStyle = "yellow";
+ var item, type, data;
+ for (var i = 0; i < draw_last.length; i++) {
+ item = draw_last[i];
+ type = item["type"];
+ data = item["data"];
+ if (type === "text") {
+ ctx.font = "bold " + DEFAULT_FONT;
+ ctx.fillText(data[0], data[1], data[2]);
+ ctx.font = DEFAULT_FONT;
+ }
+ else if (type == "triangle") {
+ ctx.drawDownwardEquilateralTriangle(data[0], data[1], data[2]);
+ }
+ }
+ },
+ /**
+ * Draw a complete read pair
+ */
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
+ // All features need a start, end, and vertical center.
+ var feature_uid = feature[0],
+ feature_start = feature[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)) ),
+ y_center = (mode === "Dense" ? 1 : (1 + slot)) * y_scale,
+ block_color = this.prefs.block_color,
+ label_color = this.prefs.label_color,
+ // Left-gap for label text since we align chrom text to the position tick.
+ gap = 0;
+
+ // TODO: fix gap usage; also see note on gap in draw_read.
+ if ((mode === "Pack" || this.mode === "Auto") && w_scale > CHAR_WIDTH_PX) {
+ var gap = Math.round(w_scale/2);
+ }
+
+ // Draw read.
+ ctx.fillStyle = block_color;
+ if (feature[5] instanceof Array) {
+ // Read is paired.
+ var b1_start = Math.floor( Math.max(0, (feature[4][0] - tile_low) * w_scale) ),
+ b1_end = Math.ceil( Math.min(width, Math.max(0, (feature[4][1] - tile_low) * w_scale)) ),
+ b2_start = Math.floor( Math.max(0, (feature[5][0] - tile_low) * w_scale) ),
+ b2_end = Math.ceil( Math.min(width, Math.max(0, (feature[5][1] - tile_low) * w_scale)) );
+
+ // Draw left/forward read.
+ if (feature[4][1] >= tile_low && feature[4][0] <= tile_high && feature[4][2]) {
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center);
+ }
+ // Draw right/reverse read.
+ if (feature[5][1] >= tile_low && feature[5][0] <= tile_high && feature[5][2]) {
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center);
+ }
+ // Draw connector.
+ if (b2_start > b1_end) {
+ ctx.fillStyle = CONNECTOR_COLOR;
+ ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
+ }
+ } else {
+ // Read is single.
+ ctx.fillStyle = block_color;
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center);
+ }
+ if (mode === "Pack" && feature_start > tile_low) {
+ // Draw label.
+ ctx.fillStyle = this.prefs.label_color;
+ // FIXME: eliminate tile_index
+ var tile_index = 1;
+ if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
+ ctx.textAlign = "left";
+ ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
+ } else {
+ ctx.textAlign = "right";
+ ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
+ }
+ ctx.fillStyle = block_color;
+ }
+ }
+});
http://bitbucket.org/galaxy/galaxy-central/changeset/ce135f55f510/
changeset: r5315:ce135f55f510
user: james_taylor
date: 2011-03-31 00:30:35
summary: Automated merge with https://bitbucket.org/galaxy/galaxy-central
affected #: 1 file (4.7 KB)
--- a/static/scripts/trackster.js Wed Mar 30 17:20:10 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 18:30:35 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1949,13 +1950,10 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
for (var c = 0, str_len = seq.length; c < str_len; c++) {
@@ -2010,10 +2008,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2041,9 +2043,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2084,98 +2084,22 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
return canvas;
}
});
@@ -2226,6 +2150,8 @@
this.tile_cache = new Cache(CACHED_TILES_FEATURE);
this.data_cache = new DataManager(20, this);
this.left_offset = 200;
+
+ this.painter = LinkedFeaturePainter;
};
$.extend(FeatureTrack.prototype, TiledTrack.prototype, {
/**
@@ -2236,192 +2162,682 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
+ return inc_slots.slot_features( features );
+ },
+ /**
+ * Returns y_scale based on mode.
+ */
+ get_y_scale: function(mode) {
+ var y_scale;
+ if (mode === "summary_tree") {
+ // No scale needed.
+ }
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "no_detail") {
+ y_scale = NO_DETAIL_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ }
+ return y_scale;
+ },
+ /**
+ * Draw FeatureTrack tile.
+ */
+ draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
+ var track = this,
+ tile_low = tile_index * DENSITY * resolution,
+ tile_high = ( tile_index + 1 ) * DENSITY * resolution,
+ tile_span = tile_high - tile_low,
+ width = Math.ceil( tile_span * w_scale ),
+ mode = this.mode,
+ min_height = 25,
+ left_offset = this.left_offset,
+ slots,
+ required_height;
+
+ // Set display mode if Auto.
+ if (mode === "Auto") {
+ if (result.dataset_type === "summary_tree") {
+ mode = result.dataset_type;
+ } else if (result.extra_info === "no_detail") {
+ mode = "no_detail";
} else {
- undone.push(i);
+ // Choose b/t Squish and Pack.
+ // Proxy measures for using Squish:
+ // (a) error message re: limiting number of features shown;
+ // (b) X number of features shown;
+ // (c) size of view shown.
+ // TODO: cannot use (a) and (b) because it requires coordinating mode across tiles;
+ // fix this so that tiles are redrawn as necessary to use the same mode.
+ //if ( (result.message && result.message.match(/^Only the first [\d]+/)) ||
+ // (result.data && result.data.length > 2000) ||
+ var data = result.data;
+ if ( (data.length && data.length < 4) ||
+ (this.view.high - this.view.low > MIN_SQUISH_VIEW_WIDTH) ) {
+ mode = "Squish";
+ } else {
+ mode = "Pack";
+ }
+ }
+ }
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Canvas element is returned
+ return canvas;
+ }
+
+ // Start dealing with row-by-row tracks
+
+ // If working with a mode where slotting is neccesary, update the incremental slotting
+ var slots, slots_required = 1;
+ if ( mode === "no_detail" || mode === "Squish" || mode === "Pack" ) {
+ slots_required = this.incremental_slots(w_scale, result.data, mode);
+ slots = this.inc_slots[w_scale].slots;
+ }
+
+ // Filter features
+ var filtered = [];
+ if ( result.data ) {
+ for (var i = 0, len = result.data.length; i < len; i++) {
+ var feature = result.data[i];
+ var hide_feature = false;
+ var filter;
+ for (var f = 0, flen = this.filters.length; f < flen; f++) {
+ filter = this.filters[f];
+ filter.update_attrs(feature);
+ if (!filter.keep(feature)) {
+ hide_feature = true;
+ break;
+ }
+ }
+ if (!hide_feature) {
+ filtered.push( feature );
+ }
}
}
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
+ // Create painter, and canvas of sufficient size to contain all features
+ // 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, ref_seq );
+ var required_height = painter.get_required_height( slots_required );
+ var canvas = this.view.canvas_manager.new_canvas();
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
+ canvas.width = width + left_offset;
+ canvas.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 = canvas.getContext("2d");
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.font = this.default_font;
+ ctx.textAlign = "right";
+ this.container_div.find(".yaxislabel").remove();
+
+ // If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
+ // to indicate region where message is applicable
+ if (result.message) {
+ $(canvas).css({
+ "border-top": "1px solid red"
+ });
+
+ ctx.fillStyle = "red";
+ ctx.textAlign = "left";
+ var old_base = ctx.textBaseline;
+ ctx.textBaseline = "top";
+ ctx.fillText(result.message, left_offset, 0);
+ ctx.textBaseline = old_base;
+
+ // If there's no data, return.
+ if (!result.data) {
+ return canvas;
+ }
+ }
+
+ // Set example feature. This is needed so that track can update its UI based on feature attributes.
+ this.example_feature = (result.data.length ? result.data[0] : undefined);
+
+ // Draw features
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height, slots );
+
+ return canvas;
+ }
+});
+
+var VcfTrack = function(name, view, hda_ldda, dataset_id, prefs, filters) {
+ FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
+ this.track_type = "VcfTrack";
+ this.painter = VariantPainter;
+};
+
+$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
+
+
+var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) {
+ FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
+
+ this.track_config = new TrackConfig( {
+ track: this,
+ params: [
+ { key: 'block_color', label: 'Block color', type: 'color', default_value: '#444' },
+ { key: 'label_color', label: 'Label color', type: 'color', default_value: 'black' },
+ { key: 'show_insertions', label: 'Show insertions', type: 'bool', default_value: false },
+ { key: 'show_differences', label: 'Show differences only', type: 'bool', default_value: true },
+ { key: 'show_counts', label: 'Show summary counts', type: 'bool', default_value: true },
+ { key: 'mode', type: 'string', default_value: this.mode, hidden: true },
+ ],
+ saved_values: prefs,
+ onchange: function() {
+ this.track.tile_cache.clear();
+ this.track.draw();
+ }
+ });
+ this.prefs = this.track_config.values;
+
+ this.track_type = "ReadTrack";
+ this.painter = ReadPainter;
+ this.make_name_popup_menu();
+};
+$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
+
+/**
+ * Feature track that displays data generated from tool.
+ */
+var ToolDataFeatureTrack = function(name, view, hda_ldda, dataset_id, prefs, filters, parent_track) {
+ FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters, {}, parent_track);
+ this.track_type = "ToolDataFeatureTrack";
+
+ // Set up track to fetch initial data from raw data URL when the dataset--not the converted datasets--
+ // is ready.
+ this.data_url = raw_data_url;
+ this.data_query_wait = 1000;
+ this.dataset_check_url = dataset_state_url;
+};
+
+$.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
+ /**
+ * For this track type, the predraw init sets up postdraw init.
+ */
+ predraw_init: function() {
+ // Postdraw init: once data has been fetched, reset data url, wait time and start indexing.
+ var track = this;
+ var post_init = function() {
+ if (track.data_cache.size() === 0) {
+ // Track still drawing initial data, so do nothing.
+ setTimeout(post_init, 300);
+ }
+ else {
+ // Track drawing done: reset dataset check, data URL, wait time and get dataset state to start
+ // indexing.
+ track.data_url = default_data_url;
+ track.data_query_wait = DEFAULT_DATA_QUERY_WAIT;
+ track.dataset_state_url = converted_datasets_state_url;
+ $.getJSON(track.dataset_state_url, {dataset_id : track.dataset_id, hda_ldda: track.hda_ldda}, function(track_data) {});
+ }
+ };
+ post_init();
+ }
+});
+
+// ---- To be extracted ------------------------------------------------------
+
+// ---- Simple extend function for inheritence ----
+
+var extend = function() {
+ var target = arguments[0];
+ for ( var i = 1; i < arguments.length; i++ ) {
+ var other = arguments[i];
+ for ( key in other ) {
+ target[key] = other[key];
+ }
+ }
+}
+
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
}
}
- if (!has_overlap) {
- return slot_num;
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) {
+ // Data and data properties
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
+
+ var view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
+
+ var points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ // Data and data properties
+ this.data = data;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
}
}
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
+
+var FeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ this.prefs = prefs;
+ this.mode = mode;
+}
+
+extend( FeaturePainter.prototype, {
+
+ get_required_height: function( rows_required ) {
+ // 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") {
+ // Calculate new slots incrementally for this new chunk of data and update height if necessary.
+ required_height = rows_required * y_scale;
+ }
+ return required_height;
+ },
+
+ draw: function( ctx, width, height, slots ) {
+
+ var data = this.data, view_start = this.view_start, view_end = this.view_end;
+
+ ctx.save();
+
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.textAlign = "right";
+
+ var view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ y_scale = this.get_row_height();
+
+ for (var i = 0, len = data.length; i < len; i++) {
+ var feature = data[i],
feature_uid = feature[0],
feature_start = feature[1],
feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
+ // Slot valid only if features are slotted and this feature is slotted;
+ // feature may not be due to lack of space.
+ slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
+ if (is_overlap([feature_start, feature_end], [view_start, view_end]) && (this.mode == "Dense" || slot !== null)) {
+ this.draw_element(ctx, this.mode, feature, slot, view_start, view_end, w_scale, y_scale,
+ width );
}
}
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
+
+ ctx.restore();
+ }
+});
+
+var LinkedFeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
+
+ /**
+ * Height of a single row, depends on mode
+ */
+ get_row_height: function() {
+ var mode = this.mode, y_scale;
+ if (mode === "summary_tree") {
+ // No scale needed.
}
- */
- return highest_slot + 1;
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "no_detail") {
+ y_scale = NO_DETAIL_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ }
+ return y_scale;
},
- /**
- * Draw summary tree on canvas.
- */
- draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
-
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text(max);
-
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
-
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
- },
- /**
- * Draw feature.
- */
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset) {
+
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
var
feature_uid = feature[0],
feature_start = feature[1],
@@ -2438,13 +2854,13 @@
// Dense mode displays the same for all data.
if (mode === "Dense") {
ctx.fillStyle = block_color;
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ ctx.fillRect(f_start, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
}
else if (mode === "no_detail") {
// No details for feature, so only one way to display.
ctx.fillStyle = block_color;
// TODO: what should width be here?
- ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ ctx.fillRect(f_start, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
}
else { // Mode is either Squish or Pack:
// Feature details.
@@ -2481,7 +2897,7 @@
else { // No strand.
ctx.fillStyle = block_color;
}
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thick_height);
+ ctx.fillRect(f_start, y_center, f_end - f_start, thick_height);
} else {
// There are feature blocks and mode is either Squish or Pack.
//
@@ -2513,7 +2929,7 @@
cur_height = 1;
}
}
- ctx.fillRect(f_start + left_offset, cur_y_center, f_end - f_start, cur_height);
+ ctx.fillRect(f_start, cur_y_center, f_end - f_start, cur_height);
for (var k = 0, k_len = feature_blocks.length; k < k_len; k++) {
var block = feature_blocks[k],
@@ -2526,14 +2942,14 @@
// Draw thin block.
ctx.fillStyle = block_color;
- ctx.fillRect(block_start + left_offset, y_center + (thick_height-thin_height)/2 + 1,
+ ctx.fillRect(block_start, y_center + (thick_height-thin_height)/2 + 1,
block_end - block_start, thin_height);
// If block intersects with thick region, draw block as thick.
if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) {
var block_thick_start = Math.max(block_start, thick_start),
block_thick_end = Math.min(block_end, thick_end);
- ctx.fillRect(block_thick_start + left_offset, y_center + 1,
+ ctx.fillRect(block_thick_start, y_center + 1,
block_thick_end - block_thick_start, thick_height);
}
}
@@ -2542,207 +2958,27 @@
// Draw label for Pack mode.
if (mode === "Pack" && feature_start > tile_low) {
ctx.fillStyle = label_color;
+ // FIXME: do this without tile_index
+ var tile_index = 1;
if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING, y_center + 8);
+ ctx.fillText(feature_name, f_end + LABEL_SPACING, y_center + 8);
} else {
ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING, y_center + 8);
+ ctx.fillText(feature_name, f_start - LABEL_SPACING, y_center + 8);
}
ctx.fillStyle = block_color;
}
}
- },
- /**
- * Returns y_scale based on mode.
- */
- get_y_scale: function(mode) {
- var y_scale;
- if (mode === "summary_tree") {
- // No scale needed.
- }
- if (mode === "Dense") {
- y_scale = DENSE_TRACK_HEIGHT;
- }
- else if (mode === "no_detail") {
- y_scale = NO_DETAIL_TRACK_HEIGHT;
- }
- else if (mode === "Squish") {
- y_scale = SQUISH_TRACK_HEIGHT;
- }
- else { // mode === "Pack"
- y_scale = PACK_TRACK_HEIGHT;
- }
- return y_scale;
- },
- /**
- * Draw FeatureTrack tile.
- */
- draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
- var track = this;
- var tile_low = tile_index * DENSITY * resolution,
- tile_high = ( tile_index + 1 ) * DENSITY * resolution,
- tile_span = tile_high - tile_low,
- params = { hda_ldda: track.hda_ldda, dataset_id: track.dataset_id,
- resolution: this.view.resolution, mode: this.mode };
-
- //
- // Create/set/compute some useful vars.
- //
- var width = Math.ceil( tile_span * w_scale ),
- mode = this.mode,
- min_height = 25,
- left_offset = this.left_offset,
- slots, required_height;
-
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- //
- // Set mode if Auto.
- //
- if (mode === "Auto") {
- if (result.dataset_type === "summary_tree") {
- mode = result.dataset_type;
- } else if (result.extra_info === "no_detail") {
- mode = "no_detail";
- } else {
- // Choose b/t Squish and Pack.
- // Proxy measures for using Squish:
- // (a) error message re: limiting number of features shown;
- // (b) X number of features shown;
- // (c) size of view shown.
- // TODO: cannot use (a) and (b) because it requires coordinating mode across tiles;
- // fix this so that tiles are redrawn as necessary to use the same mode.
- //if ( (result.message && result.message.match(/^Only the first [\d]+/)) ||
- // (result.data && result.data.length > 2000) ||
- var data = result.data;
- if ( (data.length && data.length < 4) ||
- (this.view.high - this.view.low > MIN_SQUISH_VIEW_WIDTH) ) {
- mode = "Squish";
- } else {
- mode = "Pack";
- }
- }
- }
-
- var y_scale = this.get_y_scale(mode);
-
- //
- // Pack reads, set required height.
- //
- if (mode === "summary_tree") {
- required_height = this.summary_draw_height;
- }
- if (mode === "Dense") {
- required_height = min_height;
- }
- else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
- // Calculate new slots incrementally for this new chunk of data and update height if necessary.
- required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
- }
-
- //
- // Set up for drawing.
- //
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
- if (result.dataset_type === "summary_tree") {
- // Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
- }
- 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 = canvas.get(0).getContext("2d");
- ctx.fillStyle = this.prefs.block_color;
- ctx.font = this.default_font;
- ctx.textAlign = "right";
- this.container_div.find(".yaxislabel").remove();
-
- //
- // Draw summary tree. If tree is drawn, canvas is returned.
- //
- if (mode === "summary_tree") {
- this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
- tile_low, left_offset);
- return canvas;
- }
-
- //
- // If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
- // to indicate region where message is applicable
- if (result.message) {
- canvas.css({
- "border-top": "1px solid red"
- });
-
- ctx.fillStyle = "red";
- ctx.textAlign = "left";
- var old_base = ctx.textBaseline;
- ctx.textBaseline = "top";
- ctx.fillText(result.message, left_offset, 0);
- ctx.textBaseline = old_base;
-
- // If there's no data, return.
- if (!result.data) {
- return canvas;
- }
- }
-
- //
- // Set example feature. This is needed so that track can update its UI based on feature attributes.
- //
- this.example_feature = (result.data.length ? result.data[0] : undefined);
-
- //
- // Draw elements.
- //
- var data = result.data;
- for (var i = 0, len = data.length; i < len; i++) {
- var feature = data[i],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- // Slot valid only if features are slotted and this feature is slotted;
- // feature may not be due to lack of space.
- slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
-
- // Apply filters to feature.
- var hide_feature = false;
- var filter;
- for (var f = 0; f < this.filters.length; f++) {
- filter = this.filters[f];
- filter.update_attrs(feature);
- if (!filter.keep(feature)) {
- hide_feature = true;
- break;
- }
- }
- if (hide_feature) {
- continue;
- }
-
- // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
- if (is_overlap([feature_start, feature_end], [tile_low, tile_high]) && (mode == "Dense" || slot !== null)) {
- this.draw_element(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale,
- width, left_offset, ref_seq);
- }
- }
- return canvas;
}
});
-var VcfTrack = function(name, view, hda_ldda, dataset_id, prefs, filters) {
- FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
- this.track_type = "VcfTrack";
-};
-$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * Draw a VCF entry.
- */
+var VariantPainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( VariantPainter.prototype, FeaturePainter.prototype, {
draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width) {
var feature = data[i],
feature_uid = feature[0],
@@ -2794,36 +3030,17 @@
}
});
-var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) {
- FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
-
- this.track_config = new TrackConfig( {
- track: this,
- params: [
- { key: 'block_color', label: 'Block color', type: 'color', default_value: '#444' },
- { key: 'label_color', label: 'Label color', type: 'color', default_value: 'black' },
- { key: 'show_insertions', label: 'Show insertions', type: 'bool', default_value: false },
- { key: 'show_differences', label: 'Show differences only', type: 'bool', default_value: true },
- { key: 'show_counts', label: 'Show summary counts', type: 'bool', default_value: true },
- { key: 'mode', type: 'string', default_value: this.mode, hidden: true },
- ],
- saved_values: prefs,
- onchange: function() {
- this.track.tile_cache.clear();
- this.track.draw();
- }
- });
- this.prefs = this.track_config.values;
-
- this.track_type = "ReadTrack";
- this.make_name_popup_menu();
-};
-$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
+var ReadPainter = function( data, view_start, view_end, prefs, mode, ref_seq ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+ this.ref_seq = ref_seq;
+}
+
+extend( ReadPainter.prototype, FeaturePainter.prototype, {
/**
* Returns y_scale based on mode.
*/
- get_y_scale: function(mode) {
- var y_scale;
+ get_row_height: function() {
+ var y_scale, mode = this.mode;
if (mode === "summary_tree") {
// No scale needed.
}
@@ -2844,13 +3061,14 @@
/**
* Draw a single read.
*/
- draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center, ref_seq) {
+ draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center) {
ctx.textAlign = "center";
var track = this,
tile_region = [tile_low, tile_high],
base_offset = 0,
seq_offset = 0,
- gap = 0;
+ gap = 0
+ ref_seq = this.ref_seq;
// Keep list of items that need to be drawn on top of initial drawing layer.
var draw_last = [];
@@ -3029,9 +3247,9 @@
}
},
/**
- * Draw a complete read.
+ * Draw a complete read pair
*/
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset, ref_seq) {
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
// All features need a start, end, and vertical center.
var feature_uid = feature[0],
feature_start = feature[1],
@@ -3061,11 +3279,11 @@
// Draw left/forward read.
if (feature[4][1] >= tile_low && feature[4][0] <= tile_high && feature[4][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center, ref_seq);
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center);
}
// Draw right/reverse read.
if (feature[5][1] >= tile_low && feature[5][0] <= tile_high && feature[5][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center, ref_seq);
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center);
}
// Draw connector.
if (b2_start > b1_end) {
@@ -3075,11 +3293,13 @@
} else {
// Read is single.
ctx.fillStyle = block_color;
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center, ref_seq);
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center);
}
if (mode === "Pack" && feature_start > tile_low) {
// Draw label.
ctx.fillStyle = this.prefs.label_color;
+ // FIXME: eliminate tile_index
+ var tile_index = 1;
if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
ctx.textAlign = "left";
ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
@@ -3091,44 +3311,3 @@
}
}
});
-
-/**
- * Feature track that displays data generated from tool.
- */
-var ToolDataFeatureTrack = function(name, view, hda_ldda, dataset_id, prefs, filters, parent_track) {
- FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters, {}, parent_track);
- this.track_type = "ToolDataFeatureTrack";
-
- // Set up track to fetch initial data from raw data URL when the dataset--not the converted datasets--
- // is ready.
- this.data_url = raw_data_url;
- this.data_query_wait = 1000;
- this.dataset_check_url = dataset_state_url;
-};
-
-$.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * For this track type, the predraw init sets up postdraw init.
- */
- predraw_init: function() {
- // Postdraw init: once data has been fetched, reset data url, wait time and start indexing.
- var track = this;
- var post_init = function() {
- if (track.data_cache.size() === 0) {
- // Track still drawing initial data, so do nothing.
- setTimeout(post_init, 300);
- }
- else {
- // Track drawing done: reset dataset check, data URL, wait time and get dataset state to start
- // indexing.
- track.data_url = default_data_url;
- track.data_query_wait = DEFAULT_DATA_QUERY_WAIT;
- track.dataset_state_url = converted_datasets_state_url;
- $.getJSON(track.dataset_state_url, {dataset_id : track.dataset_id, hda_ldda: track.hda_ldda}, function(track_data) {});
- }
- };
- post_init();
- }
-});
-
-
http://bitbucket.org/galaxy/galaxy-central/changeset/5e792d8de77d/
changeset: r5316:5e792d8de77d
user: dan
date: 2011-03-31 16:16:40
summary: Fix for wrapping input values for change_format and label tags when tool is using check_values="false". Fixes issue seen when e.g. using an output label in a data_source tool.
affected #: 1 file (309 bytes)
--- a/lib/galaxy/tools/actions/__init__.py Wed Mar 30 18:30:35 2011 -0400
+++ b/lib/galaxy/tools/actions/__init__.py Thu Mar 31 10:16:40 2011 -0400
@@ -133,16 +133,18 @@
else:
new_list.append( value )
return new_list
- def wrap_values( inputs, input_values ):
+ def wrap_values( inputs, input_values, skip_missing_values = False ):
# Wrap tool inputs as necessary
for input in inputs.itervalues():
+ if input.name not in input_values and skip_missing_values:
+ continue
if isinstance( input, Repeat ):
for d in input_values[ input.name ]:
- wrap_values( input.inputs, d )
+ wrap_values( input.inputs, d, skip_missing_values = skip_missing_values )
elif isinstance( input, Conditional ):
values = input_values[ input.name ]
current = values[ "__current_case__" ]
- wrap_values( input.cases[current].inputs, values )
+ wrap_values( input.cases[current].inputs, values, skip_missing_values = skip_missing_values )
elif isinstance( input, DataToolParameter ):
input_values[ input.name ] = \
galaxy.tools.DatasetFilenameWrapper( input_values[ input.name ],
@@ -254,7 +256,7 @@
if output.change_format:
if params is None:
params = make_dict_copy( incoming )
- wrap_values( tool.inputs, params )
+ wrap_values( tool.inputs, params, skip_missing_values = not tool.check_values )
for change_elem in output.change_format:
for when_elem in change_elem.findall( 'when' ):
check = when_elem.get( 'input', None )
@@ -304,7 +306,7 @@
# <outputs>
# <data format="input" name="output" label="Blat on ${<input_param>.name}" />
# </outputs>
- wrap_values( tool.inputs, params )
+ wrap_values( tool.inputs, params, skip_missing_values = not tool.check_values )
#tool (only needing to be set once) and on_string (set differently for each label) are overwritten for each output dataset label being determined
params['tool'] = tool
params['on_string'] = on_text
http://bitbucket.org/galaxy/galaxy-central/changeset/129a624fcace/
changeset: r5317:129a624fcace
user: james_taylor
date: 2011-03-31 20:08:00
summary: Merge
affected #: 2 files (143 bytes)
--- a/lib/galaxy/web/api/workflows.py Thu Mar 31 10:16:40 2011 -0400
+++ b/lib/galaxy/web/api/workflows.py Thu Mar 31 14:08:00 2011 -0400
@@ -57,7 +57,7 @@
inputs = {}
for step in latest_workflow.steps:
if step.type == 'data_input':
- inputs[step.id] = {'label':"Input Dataset", 'value':""}
+ inputs[step.id] = {'label':step.tool_inputs['name'], 'value':""}
else:
pass
# Eventually, allow regular tool parameters to be inserted and modified at runtime.
--- a/static/scripts/trackster.js Thu Mar 31 10:16:40 2011 -0400
+++ b/static/scripts/trackster.js Thu Mar 31 14:08:00 2011 -0400
@@ -2298,7 +2298,8 @@
// Create painter, and canvas of sufficient size to contain all features
// 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, ref_seq );
- var required_height = painter.get_required_height( slots_required );
+ // FIXME: ERROR_PADDING is an ugly gap most of the time
+ var required_height = painter.get_required_height( slots_required ) + ERROR_PADDING;
var canvas = this.view.canvas_manager.new_canvas();
canvas.width = width + left_offset;
@@ -2336,7 +2337,7 @@
this.example_feature = (result.data.length ? result.data[0] : undefined);
// Draw features
- ctx.translate( left_offset, 0 );
+ ctx.translate( left_offset, ERROR_PADDING );
painter.draw( ctx, width, required_height, slots );
return canvas;
@@ -2772,7 +2773,8 @@
// Calculate new slots incrementally for this new chunk of data and update height if necessary.
required_height = rows_required * y_scale;
}
- return required_height;
+ // Pad bottom by half a row
+ return required_height + Math.round( y_scale / 2 );
},
draw: function( ctx, width, height, slots ) {
@@ -2846,11 +2848,11 @@
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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
thickness, y_start, thick_start = null, thick_end = null,
block_color = this.prefs.block_color,
label_color = this.prefs.label_color;
-
+
// Dense mode displays the same for all data.
if (mode === "Dense") {
ctx.fillStyle = block_color;
@@ -2989,7 +2991,7 @@
// All features need a start, end, and vertical center.
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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
thickness, y_start, thick_start = null, thick_end = null;
if (no_label) {
@@ -3052,7 +3054,7 @@
}
else { // mode === "Pack"
y_scale = PACK_TRACK_HEIGHT;
- if (this.track_config.values.show_insertions) {
+ if (this.prefs.show_insertions) {
y_scale *= 2;
}
}
@@ -3109,12 +3111,12 @@
var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
if (gap > 0) {
ctx.fillStyle = this.prefs.block_color;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
+ ctx.fillRect(s_start - gap, y_center + 1, s_end - s_start, 9);
ctx.fillStyle = CONNECTOR_COLOR;
// TODO: this can be made much more efficient by computing the complete sequence
// to draw and then drawing it.
for (var c = 0, str_len = seq.length; c < str_len; c++) {
- if (this.track_config.values.show_differences && ref_seq) {
+ if (this.prefs.show_differences && ref_seq) {
var ref_char = ref_seq[seq_start - tile_low + c];
if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
continue;
@@ -3122,13 +3124,13 @@
}
if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
+ ctx.fillText(seq[c], c_start, y_center + 9);
}
}
} else {
ctx.fillStyle = this.prefs.block_color;
// TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(s_start + this.left_offset,
+ ctx.fillRect(s_start,
y_center + (this.mode !== "Dense" ? 4 : 5),
s_end - s_start,
(mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
@@ -3139,14 +3141,14 @@
break;
case "N": // Skipped bases.
ctx.fillStyle = CONNECTOR_COLOR;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
+ ctx.fillRect(s_start - gap, y_center + 5, s_end - s_start, 1);
//ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
// No change in seq_offset because sequence not used when skipping.
base_offset += cig_len;
break;
case "D": // Deletion.
ctx.fillStyle = "red";
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
+ ctx.fillRect(s_start - gap, y_center + 4, s_end - s_start, 3);
// TODO: is this true? No change in seq_offset because sequence not used when skipping.
base_offset += cig_len;
break;
@@ -3158,20 +3160,20 @@
// the sequence region and the tile region.
var
seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
- insert_x_coord = this.left_offset + s_start - gap;
+ insert_x_coord = s_start - gap;
if (seq_tile_overlap !== NO_OVERLAP) {
var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
// Insertion point is between the sequence start and the previous base: (-gap) moves
// back from sequence start to insertion point.
- if (this.track_config.values.show_insertions) {
+ if (this.prefs.show_insertions) {
//
// Show inserted sequence above, centered on insertion point.
//
// Draw sequence.
// X center is offset + start - <half_sequence_length>
- var x_center = this.left_offset + s_start - (s_end - s_start)/2;
+ var x_center = s_start - (s_end - s_start)/2;
if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
// Draw sequence container.
ctx.fillStyle = "yellow";
@@ -3196,7 +3198,7 @@
// Draw sequence.
for (var c = 0, str_len = seq.length; c < str_len; c++) {
var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
+ ctx.fillText(seq[c], c_start - (s_end - s_start)/2, y_center);
}
}
else {
@@ -3288,7 +3290,7 @@
// Draw connector.
if (b2_start > b1_end) {
ctx.fillStyle = CONNECTOR_COLOR;
- ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
+ ctx.dashedLine(b1_end - gap, y_center + 5, b2_start - gap, y_center + 5);
}
} else {
// Read is single.
@@ -3302,10 +3304,10 @@
var tile_index = 1;
if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
+ ctx.fillText(feature_name, f_end + LABEL_SPACING - gap, y_center + 8);
} else {
ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
+ ctx.fillText(feature_name, f_start - LABEL_SPACING - gap, y_center + 8);
}
ctx.fillStyle = block_color;
}
http://bitbucket.org/galaxy/galaxy-central/changeset/82bbc491ab90/
changeset: r5318:82bbc491ab90
user: kanwei
date: 2011-04-01 00:58:25
summary: Merge james' trackster changes
affected #: 1 file (4.5 KB)
--- a/static/scripts/trackster.js Thu Mar 31 18:58:02 2011 -0400
+++ b/static/scripts/trackster.js Thu Mar 31 18:58:25 2011 -0400
@@ -372,6 +372,7 @@
this.min_separation = 30;
this.has_changes = false;
this.init( callback );
+ this.canvas_manager = new CanvasManager( container.get(0).ownerDocument );
this.reset();
};
$.extend( View.prototype, {
@@ -1976,13 +1977,10 @@
track.content_div.css("height", "0px");
return;
}
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- var ctx = canvas.get(0).getContext("2d");
- canvas.get(0).width = Math.ceil( tile_length * w_scale + track.left_offset);
- canvas.get(0).height = track.height_px;
+ var canvas = this.view.canvas_manager.new_canvas();
+ var ctx = canvas.getContext("2d");
+ canvas.width = Math.ceil( tile_length * w_scale + track.left_offset);
+ canvas.height = track.height_px;
ctx.font = DEFAULT_FONT;
ctx.textAlign = "center";
for (var c = 0, str_len = seq.length; c < str_len; c++) {
@@ -2037,10 +2035,14 @@
this.height_px = this.track_config.values.height;
this.vertical_range = this.track_config.values.max_value - this.track_config.values.min_value;
- // Add control for resizing
- // Trickery here to deal with the hovering drag handle, can probably be
- // pulled out and reused.
- (function( track ){
+ this.add_resize_handle();
+};
+$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ add_resize_handle: function () {
+ // Add control for resizing
+ // Trickery here to deal with the hovering drag handle, can probably be
+ // pulled out and reused.
+ var track = this;
var in_handle = false;
var in_drag = false;
var drag_control = $( "<div class='track-resize'>" )
@@ -2068,9 +2070,7 @@
if ( ! in_handle ) { drag_control.hide(); }
track.track_config.values.height = track.height_px;
}).appendTo( track.container_div );
- })(this);
-};
-$.extend(LineTrack.prototype, TiledTrack.prototype, {
+ },
predraw_init: function() {
var track = this,
track_id = track.view.tracks.indexOf(track);
@@ -2111,98 +2111,22 @@
return;
}
- var track = this,
- tile_low = tile_index * DENSITY * resolution,
+ var tile_low = tile_index * DENSITY * resolution,
tile_length = DENSITY * resolution,
- key = resolution + "_" + tile_index,
- params = { hda_ldda: this.hda_ldda, dataset_id: this.dataset_id, resolution: this.view.resolution };
+ width = Math.ceil( tile_length * w_scale ),
+ height = this.height_px;
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width,
+ canvas.height = height;
- var canvas = document.createElement("canvas"),
- data = result.data;
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- canvas.get(0).width = Math.ceil( tile_length * w_scale );
- canvas.get(0).height = track.height_px;
- var ctx = canvas.get(0).getContext("2d"),
- in_path = false,
- min_value = track.prefs.min_value,
- max_value = track.prefs.max_value,
- vertical_range = track.vertical_range,
- total_frequency = track.total_frequency,
- height_px = track.height_px,
- mode = track.mode;
-
- // Pixel position of 0 on the y axis
- var y_zero = Math.round( height_px + min_value / vertical_range * height_px );
-
- // Line at 0.0
- ctx.beginPath();
- ctx.moveTo( 0, y_zero );
- ctx.lineTo( tile_length * w_scale, y_zero );
- // ctx.lineWidth = 0.5;
- ctx.fillStyle = "#aaa";
- ctx.stroke();
+ // Paint line onto full canvas
+ var ctx = canvas.getContext("2d");
+ var painter = new LinePainter( result.data, tile_low, tile_low + tile_length,
+ this.prefs.min_value, this.prefs.max_value, this.prefs.color, this.mode );
+ painter.draw( ctx, width, height );
- ctx.beginPath();
- ctx.fillStyle = track.prefs.color;
- var x_scaled, y, delta_x_px;
- if (data.length > 1) {
- delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
- } else {
- delta_x_px = 10;
- }
- for (var i = 0, len = data.length; i < len; i++) {
- x_scaled = Math.round((data[i][0] - tile_low) * w_scale);
- y = data[i][1];
- if (y === null) {
- if (in_path && mode === "Filled") {
- ctx.lineTo(x_scaled, height_px);
- }
- in_path = false;
- continue;
- }
- if (y < min_value) {
- y = min_value;
- } else if (y > max_value) {
- y = max_value;
- }
-
- if (mode === "Histogram") {
- // y becomes the bar height in pixels, which is the negated for canvas coords
- y = Math.round( y / vertical_range * height_px );
- ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
- } else if (mode === "Intensity" ) {
- y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
- ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
- ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
- } else {
- // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
- y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
- // console.log(canvas.get(0).height, canvas.get(0).width);
- if (in_path) {
- ctx.lineTo(x_scaled, y);
- } else {
- in_path = true;
- if (mode === "Filled") {
- ctx.moveTo(x_scaled, height_px);
- ctx.lineTo(x_scaled, y);
- } else {
- ctx.moveTo(x_scaled, y);
- }
- }
- }
- }
- if (mode === "Filled") {
- if (in_path) {
- ctx.lineTo( x_scaled, y_zero );
- ctx.lineTo( 0, y_zero );
- }
- ctx.fill();
- } else {
- ctx.stroke();
- }
return canvas;
}
});
@@ -2253,6 +2177,8 @@
this.tile_cache = new Cache(CACHED_TILES_FEATURE);
this.data_cache = new DataManager(20, this);
this.left_offset = 200;
+
+ this.painter = LinkedFeaturePainter;
};
$.extend(FeatureTrack.prototype, TiledTrack.prototype, {
/**
@@ -2263,192 +2189,684 @@
* Returns the number of slots used to pack features.
*/
incremental_slots: function(level, features, mode) {
- //
+
// Get/create incremental slots for level. If display mode changed,
// need to create new slots.
- //
+
var inc_slots = this.inc_slots[level];
if (!inc_slots || (inc_slots.mode !== mode)) {
- inc_slots = {};
- inc_slots.w_scale = level;
+ inc_slots = new FeatureSlotter( level, mode === "Pack", function ( x ) { return CONTEXT.measureText( x ) } );
inc_slots.mode = mode;
this.inc_slots[level] = inc_slots;
- this.start_end_dct[level] = {};
}
- //
- // If feature already exists in slots (from previously seen tiles), use the same slot,
- // otherwise if not seen, add to "undone" list for slot calculation.
- //
- var w_scale = inc_slots.w_scale,
- undone = [], slotted = [],
- highest_slot = 0, // To measure how big to draw canvas
- max_low = this.view.max_low;
- // TODO: Should calculate zoom tile index, which will improve performance
- // by only having to look at a smaller subset
- // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
- for (var i = 0, len = features.length; i < len; i++) {
- var feature = features[i],
- feature_uid = feature[0];
- if (inc_slots[feature_uid] !== undefined) {
- highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
- slotted.push(inc_slots[feature_uid]);
+ return inc_slots.slot_features( features );
+ },
+ /**
+ * Returns y_scale based on mode.
+ */
+ get_y_scale: function(mode) {
+ var y_scale;
+ if (mode === "summary_tree") {
+ // No scale needed.
+ }
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "no_detail") {
+ y_scale = NO_DETAIL_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ }
+ return y_scale;
+ },
+ /**
+ * Draw FeatureTrack tile.
+ */
+ draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
+ var track = this,
+ tile_low = tile_index * DENSITY * resolution,
+ tile_high = ( tile_index + 1 ) * DENSITY * resolution,
+ tile_span = tile_high - tile_low,
+ width = Math.ceil( tile_span * w_scale ),
+ mode = this.mode,
+ min_height = 25,
+ left_offset = this.left_offset,
+ slots,
+ required_height;
+
+ // Set display mode if Auto.
+ if (mode === "Auto") {
+ if (result.dataset_type === "summary_tree") {
+ mode = result.dataset_type;
+ } else if (result.extra_info === "no_detail") {
+ mode = "no_detail";
} else {
- undone.push(i);
+ // Choose b/t Squish and Pack.
+ // Proxy measures for using Squish:
+ // (a) error message re: limiting number of features shown;
+ // (b) X number of features shown;
+ // (c) size of view shown.
+ // TODO: cannot use (a) and (b) because it requires coordinating mode across tiles;
+ // fix this so that tiles are redrawn as necessary to use the same mode.
+ //if ( (result.message && result.message.match(/^Only the first [\d]+/)) ||
+ // (result.data && result.data.length > 2000) ||
+ var data = result.data;
+ if ( (data.length && data.length < 4) ||
+ (this.view.high - this.view.low > MIN_SQUISH_VIEW_WIDTH) ) {
+ mode = "Squish";
+ } else {
+ mode = "Pack";
+ }
+ }
+ }
+
+ // Drawing the summary tree (feature coverage histogram)
+ if ( mode === "summary_tree" ) {
+ // Set height of parent_element
+ required_height = this.summary_draw_height;
+ parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px");
+ // Add label to container div showing maximum count
+ // TODO: this shouldn't be done at the tile level
+ this.container_div.find(".yaxislabel").remove();
+ var max_label = $("<div />").addClass('yaxislabel');
+ max_label.text( result.max );
+ max_label.css({ position: "absolute", top: "22px", left: "10px" });
+ max_label.prependTo(this.container_div);
+ // Create canvas
+ var canvas = this.view.canvas_manager.new_canvas();
+ canvas.width = width + left_offset;
+ // Extra padding at top of summary tree
+ canvas.height = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ // Paint summary tree into canvas
+ var painter = new SummaryTreePainter( result.data, result.delta, result.max, tile_low, tile_high, this.prefs.show_counts );
+ var ctx = canvas.getContext("2d");
+ // Deal with left_offset by translating
+ ctx.translate( left_offset, 0 );
+ painter.draw( ctx, width, required_height );
+ // Canvas element is returned
+ return canvas;
+ }
+
+ // Start dealing with row-by-row tracks
+
+ // If working with a mode where slotting is neccesary, update the incremental slotting
+ var slots, slots_required = 1;
+ if ( mode === "no_detail" || mode === "Squish" || mode === "Pack" ) {
+ slots_required = this.incremental_slots(w_scale, result.data, mode);
+ slots = this.inc_slots[w_scale].slots;
+ }
+
+ // Filter features
+ var filtered = [];
+ if ( result.data ) {
+ for (var i = 0, len = result.data.length; i < len; i++) {
+ var feature = result.data[i];
+ var hide_feature = false;
+ var filter;
+ for (var f = 0, flen = this.filters.length; f < flen; f++) {
+ filter = this.filters[f];
+ filter.update_attrs(feature);
+ if (!filter.keep(feature)) {
+ hide_feature = true;
+ break;
+ }
+ }
+ if (!hide_feature) {
+ filtered.push( feature );
+ }
}
}
- //
- // Slot unslotted features.
- //
- var start_end_dct = this.start_end_dct[level];
+ // Create painter, and canvas of sufficient size to contain all features
+ // 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, ref_seq );
+ // FIXME: ERROR_PADDING is an ugly gap most of the time
+ var required_height = painter.get_required_height( slots_required ) + ERROR_PADDING;
+ var canvas = this.view.canvas_manager.new_canvas();
- // Find the first slot such that current feature doesn't overlap any other features in that slot.
- // Returns -1 if no slot was found.
- var find_slot = function(f_start, f_end) {
- for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
- var has_overlap = false,
- slot = start_end_dct[slot_num];
- if (slot !== undefined) {
- // Iterate through features already in slot to see if current feature will fit.
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- var s_e = slot[k];
- if (f_end > s_e[0] && f_start < s_e[1]) {
- // There is overlap
- has_overlap = true;
- break;
- }
+ canvas.width = width + left_offset;
+ canvas.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 = canvas.getContext("2d");
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.font = this.default_font;
+ ctx.textAlign = "right";
+ this.container_div.find(".yaxislabel").remove();
+
+ // If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
+ // to indicate region where message is applicable
+ if (result.message) {
+ $(canvas).css({
+ "border-top": "1px solid red"
+ });
+
+ ctx.fillStyle = "red";
+ ctx.textAlign = "left";
+ var old_base = ctx.textBaseline;
+ ctx.textBaseline = "top";
+ ctx.fillText(result.message, left_offset, 0);
+ ctx.textBaseline = old_base;
+
+ // If there's no data, return.
+ if (!result.data) {
+ return canvas;
+ }
+ }
+
+ // Set example feature. This is needed so that track can update its UI based on feature attributes.
+ this.example_feature = (result.data.length ? result.data[0] : undefined);
+
+ // Draw features
+ ctx.translate( left_offset, ERROR_PADDING );
+ painter.draw( ctx, width, required_height, slots );
+
+ return canvas;
+ }
+});
+
+var VcfTrack = function(name, view, hda_ldda, dataset_id, prefs, filters) {
+ FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
+ this.track_type = "VcfTrack";
+ this.painter = VariantPainter;
+};
+
+$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
+
+
+var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) {
+ FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
+
+ this.track_config = new TrackConfig( {
+ track: this,
+ params: [
+ { key: 'block_color', label: 'Block color', type: 'color', default_value: '#444' },
+ { key: 'label_color', label: 'Label color', type: 'color', default_value: 'black' },
+ { key: 'show_insertions', label: 'Show insertions', type: 'bool', default_value: false },
+ { key: 'show_differences', label: 'Show differences only', type: 'bool', default_value: true },
+ { key: 'show_counts', label: 'Show summary counts', type: 'bool', default_value: true },
+ { key: 'mode', type: 'string', default_value: this.mode, hidden: true },
+ ],
+ saved_values: prefs,
+ onchange: function() {
+ this.track.tile_cache.clear();
+ this.track.draw();
+ }
+ });
+ this.prefs = this.track_config.values;
+
+ this.track_type = "ReadTrack";
+ this.painter = ReadPainter;
+ this.make_name_popup_menu();
+};
+$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype);
+
+/**
+ * Feature track that displays data generated from tool.
+ */
+var ToolDataFeatureTrack = function(name, view, hda_ldda, dataset_id, prefs, filters, parent_track) {
+ FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters, {}, parent_track);
+ this.track_type = "ToolDataFeatureTrack";
+
+ // Set up track to fetch initial data from raw data URL when the dataset--not the converted datasets--
+ // is ready.
+ this.data_url = raw_data_url;
+ this.data_query_wait = 1000;
+ this.dataset_check_url = dataset_state_url;
+};
+
+$.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
+ /**
+ * For this track type, the predraw init sets up postdraw init.
+ */
+ predraw_init: function() {
+ // Postdraw init: once data has been fetched, reset data url, wait time and start indexing.
+ var track = this;
+ var post_init = function() {
+ if (track.data_cache.size() === 0) {
+ // Track still drawing initial data, so do nothing.
+ setTimeout(post_init, 300);
+ }
+ else {
+ // Track drawing done: reset dataset check, data URL, wait time and get dataset state to start
+ // indexing.
+ track.data_url = default_data_url;
+ track.data_query_wait = DEFAULT_DATA_QUERY_WAIT;
+ track.dataset_state_url = converted_datasets_state_url;
+ $.getJSON(track.dataset_state_url, {dataset_id : track.dataset_id, hda_ldda: track.hda_ldda}, function(track_data) {});
+ }
+ };
+ post_init();
+ }
+});
+
+// ---- To be extracted ------------------------------------------------------
+
+// ---- Simple extend function for inheritence ----
+
+var extend = function() {
+ var target = arguments[0];
+ for ( var i = 1; i < arguments.length; i++ ) {
+ var other = arguments[i];
+ for ( key in other ) {
+ target[key] = other[key];
+ }
+ }
+}
+
+// ---- Canvas management ----
+
+var CanvasManager = function( document ) {
+ this.document = document;
+}
+
+CanvasManager.prototype.new_canvas = function() {
+ var canvas = this.document.createElement("canvas");
+ if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
+ return canvas;
+}
+
+// ---- Feature Packing ----
+
+/**
+ * FeatureSlotter determines slots in which to draw features for vertical
+ * packing.
+ *
+ * This implementation is incremental, any feature assigned a slot will be
+ * retained for slotting future features.
+ */
+var FeatureSlotter = function ( w_scale, include_label, measureText ) {
+ this.slots = {};
+ this.start_end_dct = {};
+ this.w_scale = w_scale;
+ this.include_label = include_label;
+ this.measureText = measureText;
+}
+
+/**
+ * Slot a set of features, `this.slots` will be updated with slots by id, and
+ * the largest slot required for the passed set of features is returned
+ */
+FeatureSlotter.prototype.slot_features = function( features ) {
+ var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct,
+ undone = [], slotted = [], highest_slot = 0;
+
+ // If feature already exists in slots (from previously seen tiles), use the same slot,
+ // otherwise if not seen, add to "undone" list for slot calculation.
+
+ // TODO: Should calculate zoom tile index, which will improve performance
+ // by only having to look at a smaller subset
+ // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; }
+ for (var i = 0, len = features.length; i < len; i++) {
+ var feature = features[i],
+ feature_uid = feature[0];
+ if (inc_slots[feature_uid] !== undefined) {
+ highest_slot = Math.max(highest_slot, inc_slots[feature_uid]);
+ slotted.push(inc_slots[feature_uid]);
+ } else {
+ undone.push(i);
+ }
+ }
+
+ // Slot unslotted features.
+
+ // Find the first slot such that current feature doesn't overlap any other features in that slot.
+ // Returns -1 if no slot was found.
+ var find_slot = function(f_start, f_end) {
+ // TODO: Fix constants
+ for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) {
+ var has_overlap = false,
+ slot = start_end_dct[slot_num];
+ if (slot !== undefined) {
+ // Iterate through features already in slot to see if current feature will fit.
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ var s_e = slot[k];
+ if (f_end > s_e[0] && f_start < s_e[1]) {
+ // There is overlap
+ has_overlap = true;
+ break;
}
}
- if (!has_overlap) {
- return slot_num;
+ }
+ if (!has_overlap) {
+ return slot_num;
+ }
+ }
+ return -1;
+ };
+
+ // Do slotting.
+ for (var i = 0, len = undone.length; i < len; i++) {
+ var feature = features[undone[i]],
+ feature_uid = feature[0],
+ feature_start = feature[1],
+ feature_end = feature[2],
+ feature_name = feature[3],
+ // Where to start, end drawing on screen.
+ f_start = Math.floor( feature_start * w_scale ),
+ f_end = Math.ceil( feature_end * w_scale ),
+ text_len = this.measureText(feature_name).width,
+ text_align;
+
+ // Update start, end drawing locations to include feature name.
+ // Try to put the name on the left, if not, put on right.
+ if (feature_name !== undefined && this.include_label ) {
+ // Add gap for label spacing and extra pack space padding
+ // TODO: Fix constants
+ text_len += (LABEL_SPACING + PACK_SPACING);
+ if (f_start - text_len >= 0) {
+ f_start -= text_len;
+ text_align = "left";
+ } else {
+ f_end += text_len;
+ text_align = "right";
+ }
+ }
+
+ // Find slot.
+ var slot_num = find_slot(f_start, f_end);
+ /*
+ if (slot_num < 0) {
+
+ TODO: this is not yet working --
+ console.log(feature_uid, "looking for slot with text on the right");
+ // Slot not found. If text was on left, try on right and see
+ // if slot can be found.
+ // TODO: are there any checks we need to do to ensure that text
+ // will fit on tile?
+ if (text_align === "left") {
+ f_start -= text_len;
+ f_end -= text_len;
+ text_align = "right";
+ slot_num = find_slot(f_start, f_end);
+ }
+ if (slot_num >= 0) {
+ console.log(feature_uid, "found slot with text on the right");
+ }
+
+ }
+ */
+ // Do slotting.
+ if (slot_num >= 0) {
+ // Add current feature to slot.
+ if (start_end_dct[slot_num] === undefined) {
+ start_end_dct[slot_num] = [];
+ }
+ start_end_dct[slot_num].push([f_start, f_end]);
+ inc_slots[feature_uid] = slot_num;
+ highest_slot = Math.max(highest_slot, slot_num);
+ }
+ else {
+ // TODO: remove this warning when skipped features are handled.
+ // Show warning for skipped feature.
+ //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ }
+ }
+
+ // Debugging: view slots data.
+ /*
+ for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
+ var slot = start_end_dct[i];
+ if (slot !== undefined) {
+ console.log(i, "*************");
+ for (var k = 0, k_len = slot.length; k < k_len; k++) {
+ console.log("\t", slot[k][0], slot[k][1]);
+ }
+ }
+ }
+ */
+ return highest_slot + 1;
+}
+
+// ---- Painters ----
+
+/**
+ * SummaryTreePainter, a histogram showing number of intervals in a region
+ */
+var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) {
+ // Data and data properties
+ this.data = data;
+ this.delta = delta;
+ this.max = max;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.show_counts = show_counts;
+}
+
+SummaryTreePainter.prototype.draw = function( ctx, width, height ) {
+
+ var view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range;
+
+ var points = this.data, delta = this.delta, max = this.max,
+ // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
+ // start. However, height of each rectangle is relative to required_height; hence, the
+ // max rectangle is required_height.
+ base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX;
+ 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;
+
+ ctx.fillStyle = "black";
+ ctx.fillRect( x, base_y - y_px, delta_x_px, y_px );
+
+ // Draw number count if it can fit the number with some padding, otherwise things clump up
+ var text_padding_req_x = 4;
+ if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
+ ctx.fillStyle = "#666";
+ ctx.textAlign = "center";
+ ctx.fillText(y, x + (delta_x_px/2), 10);
+ }
+ }
+
+ ctx.restore();
+}
+
+var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) {
+ // Data and data properties
+ this.data = data;
+ // View
+ this.view_start = view_start;
+ this.view_end = view_end;
+ // Drawing prefs
+ this.min_value = min_value;
+ this.max_value = max_value;
+ this.color = color;
+ this.mode = mode;
+}
+
+LinePainter.prototype.draw = function( ctx, width, height ) {
+ var
+ in_path = false,
+ min_value = this.min_value,
+ max_value = this.max_value,
+ vertical_range = max_value - min_value,
+ height_px = height,
+ view_start = this.view_start,
+ view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ mode = this.mode,
+ data = this.data;
+
+ ctx.save();
+
+ // Pixel position of 0 on the y axis
+ var y_zero = Math.round( height + min_value / vertical_range * height );
+
+ // Line at 0.0
+ if ( mode !== "Intensity" ) {
+ ctx.beginPath();
+ ctx.moveTo( 0, y_zero );
+ ctx.lineTo( width, y_zero );
+ // ctx.lineWidth = 0.5;
+ ctx.fillStyle = "#aaa";
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ ctx.fillStyle = this.color;
+ var x_scaled, y, delta_x_px;
+ if (data.length > 1) {
+ delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale);
+ } else {
+ delta_x_px = 10;
+ }
+ for (var i = 0, len = data.length; i < len; i++) {
+ x_scaled = Math.round((data[i][0] - view_start) * w_scale);
+ y = data[i][1];
+ if (y === null) {
+ if (in_path && mode === "Filled") {
+ ctx.lineTo(x_scaled, height_px);
+ }
+ in_path = false;
+ continue;
+ }
+ if (y < min_value) {
+ y = min_value;
+ } else if (y > max_value) {
+ y = max_value;
+ }
+
+ if (mode === "Histogram") {
+ // y becomes the bar height in pixels, which is the negated for canvas coords
+ y = Math.round( y / vertical_range * height_px );
+ ctx.fillRect(x_scaled, y_zero, delta_x_px, - y );
+ } else if (mode === "Intensity" ) {
+ y = 255 - Math.floor( (y - min_value) / vertical_range * 255 );
+ ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")";
+ ctx.fillRect(x_scaled, 0, delta_x_px, height_px);
+ } else {
+ // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px);
+ y = Math.round( height_px - (y - min_value) / vertical_range * height_px );
+ // console.log(canvas.get(0).height, canvas.get(0).width);
+ if (in_path) {
+ ctx.lineTo(x_scaled, y);
+ } else {
+ in_path = true;
+ if (mode === "Filled") {
+ ctx.moveTo(x_scaled, height_px);
+ ctx.lineTo(x_scaled, y);
+ } else {
+ ctx.moveTo(x_scaled, y);
}
}
- return -1;
- };
-
- // Do slotting.
- for (var i = 0, len = undone.length; i < len; i++) {
- var feature = features[undone[i]],
+ }
+ }
+ if (mode === "Filled") {
+ if (in_path) {
+ ctx.lineTo( x_scaled, y_zero );
+ ctx.lineTo( 0, y_zero );
+ }
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+
+ ctx.restore();
+}
+
+var FeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ this.data = data;
+ this.view_start = view_start;
+ this.view_end = view_end;
+ this.prefs = prefs;
+ this.mode = mode;
+}
+
+extend( FeaturePainter.prototype, {
+
+ get_required_height: function( rows_required ) {
+ // 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") {
+ // Calculate new slots incrementally for this new chunk of data and update height if necessary.
+ required_height = rows_required * y_scale;
+ }
+ // Pad bottom by half a row
+ return required_height + Math.round( y_scale / 2 );
+ },
+
+ draw: function( ctx, width, height, slots ) {
+
+ var data = this.data, view_start = this.view_start, view_end = this.view_end;
+
+ ctx.save();
+
+ ctx.fillStyle = this.prefs.block_color;
+ ctx.textAlign = "right";
+
+ var view_range = this.view_end - this.view_start,
+ w_scale = width / view_range,
+ y_scale = this.get_row_height();
+
+ for (var i = 0, len = data.length; i < len; i++) {
+ var feature = data[i],
feature_uid = feature[0],
feature_start = feature[1],
feature_end = feature[2],
- feature_name = feature[3],
- // Where to start, end drawing on screen.
- f_start = Math.floor( (feature_start - max_low) * w_scale ),
- f_end = Math.ceil( (feature_end - max_low) * w_scale ),
- text_len = CONTEXT.measureText(feature_name).width,
- text_align;
-
- // Update start, end drawing locations to include feature name.
- // Try to put the name on the left, if not, put on right.
- if (feature_name !== undefined && mode === "Pack") {
- // Add gap for label spacing and extra pack space padding
- text_len += (LABEL_SPACING + PACK_SPACING);
- if (f_start - text_len >= 0) {
- f_start -= text_len;
- text_align = "left";
- } else {
- f_end += text_len;
- text_align = "right";
- }
- }
-
- // Find slot.
- var slot_num = find_slot(f_start, f_end);
- /*
- if (slot_num < 0) {
+ // Slot valid only if features are slotted and this feature is slotted;
+ // feature may not be due to lack of space.
+ slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
- TODO: this is not yet working --
- console.log(feature_uid, "looking for slot with text on the right");
- // Slot not found. If text was on left, try on right and see
- // if slot can be found.
- // TODO: are there any checks we need to do to ensure that text
- // will fit on tile?
- if (text_align === "left") {
- f_start -= text_len;
- f_end -= text_len;
- text_align = "right";
- slot_num = find_slot(f_start, f_end);
- }
- if (slot_num >= 0) {
- console.log(feature_uid, "found slot with text on the right");
- }
-
- }
- */
- // Do slotting.
- if (slot_num >= 0) {
- // Add current feature to slot.
- if (start_end_dct[slot_num] === undefined) {
- start_end_dct[slot_num] = [];
- }
- start_end_dct[slot_num].push([f_start, f_end]);
- inc_slots[feature_uid] = slot_num;
- highest_slot = Math.max(highest_slot, slot_num);
- }
- else {
- // TODO: remove this warning when skipped features are handled.
- // Show warning for skipped feature.
- //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end);
+ // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
+ if (is_overlap([feature_start, feature_end], [view_start, view_end]) && (this.mode == "Dense" || slot !== null)) {
+ this.draw_element(ctx, this.mode, feature, slot, view_start, view_end, w_scale, y_scale,
+ width );
}
}
-
- // Debugging: view slots data.
- /*
- for (var i = 0; i < MAX_FEATURE_DEPTH; i++) {
- var slot = start_end_dct[i];
- if (slot !== undefined) {
- console.log(i, "*************");
- for (var k = 0, k_len = slot.length; k < k_len; k++) {
- console.log("\t", slot[k][0], slot[k][1]);
- }
- }
+
+ ctx.restore();
+ }
+});
+
+var LinkedFeaturePainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, {
+
+ /**
+ * Height of a single row, depends on mode
+ */
+ get_row_height: function() {
+ var mode = this.mode, y_scale;
+ if (mode === "summary_tree") {
+ // No scale needed.
}
- */
- return highest_slot + 1;
+ if (mode === "Dense") {
+ y_scale = DENSE_TRACK_HEIGHT;
+ }
+ else if (mode === "no_detail") {
+ y_scale = NO_DETAIL_TRACK_HEIGHT;
+ }
+ else if (mode === "Squish") {
+ y_scale = SQUISH_TRACK_HEIGHT;
+ }
+ else { // mode === "Pack"
+ y_scale = PACK_TRACK_HEIGHT;
+ }
+ return y_scale;
},
- /**
- * Draw summary tree on canvas.
- */
- draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) {
- var
- // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases
- // start. However, height of each rectangle is relative to required_height; hence, the
- // max rectangle is required_height.
- base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX;
- delta_x_px = Math.ceil(delta * w_scale);
-
- var max_label = $("<div />").addClass('yaxislabel');
- max_label.text(max);
-
- max_label.css({ position: "absolute", top: "22px", left: "10px" });
- max_label.prependTo(this.container_div);
-
- var ctx = canvas.get(0).getContext("2d");
- for (var i = 0, len = points.length; i < len; i++) {
- var x = Math.floor( (points[i][0] - tile_low) * w_scale );
- var y = points[i][1];
-
- if (!y) { continue; }
- var y_px = y / max * required_height;
-
- ctx.fillStyle = "black";
- ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px);
-
- // Draw number count if it can fit the number with some padding, otherwise things clump up
- var text_padding_req_x = 4;
- if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) {
- ctx.fillStyle = "#666";
- ctx.textAlign = "center";
- ctx.fillText(y, x + left_offset + (delta_x_px/2), 10);
- }
- }
- },
- /**
- * Draw feature.
- */
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset) {
+
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
var
feature_uid = feature[0],
feature_start = feature[1],
@@ -2457,21 +2875,21 @@
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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
thickness, y_start, thick_start = null, thick_end = null,
block_color = this.prefs.block_color,
label_color = this.prefs.label_color;
-
+
// Dense mode displays the same for all data.
if (mode === "Dense") {
ctx.fillStyle = block_color;
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ ctx.fillRect(f_start, y_center, f_end - f_start, DENSE_FEATURE_HEIGHT);
}
else if (mode === "no_detail") {
// No details for feature, so only one way to display.
ctx.fillStyle = block_color;
// TODO: what should width be here?
- ctx.fillRect(f_start + left_offset, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
+ ctx.fillRect(f_start, y_center + 5, f_end - f_start, DENSE_FEATURE_HEIGHT);
}
else { // Mode is either Squish or Pack:
// Feature details.
@@ -2508,7 +2926,7 @@
else { // No strand.
ctx.fillStyle = block_color;
}
- ctx.fillRect(f_start + left_offset, y_center, f_end - f_start, thick_height);
+ ctx.fillRect(f_start, y_center, f_end - f_start, thick_height);
} else {
// There are feature blocks and mode is either Squish or Pack.
//
@@ -2540,7 +2958,7 @@
cur_height = 1;
}
}
- ctx.fillRect(f_start + left_offset, cur_y_center, f_end - f_start, cur_height);
+ ctx.fillRect(f_start, cur_y_center, f_end - f_start, cur_height);
for (var k = 0, k_len = feature_blocks.length; k < k_len; k++) {
var block = feature_blocks[k],
@@ -2553,14 +2971,14 @@
// Draw thin block.
ctx.fillStyle = block_color;
- ctx.fillRect(block_start + left_offset, y_center + (thick_height-thin_height)/2 + 1,
+ ctx.fillRect(block_start, y_center + (thick_height-thin_height)/2 + 1,
block_end - block_start, thin_height);
// If block intersects with thick region, draw block as thick.
if (thick_start !== undefined && !(block_start > thick_end || block_end < thick_start) ) {
var block_thick_start = Math.max(block_start, thick_start),
block_thick_end = Math.min(block_end, thick_end);
- ctx.fillRect(block_thick_start + left_offset, y_center + 1,
+ ctx.fillRect(block_thick_start, y_center + 1,
block_thick_end - block_thick_start, thick_height);
}
}
@@ -2569,207 +2987,27 @@
// Draw label for Pack mode.
if (mode === "Pack" && feature_start > tile_low) {
ctx.fillStyle = label_color;
+ // FIXME: do this without tile_index
+ var tile_index = 1;
if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING, y_center + 8);
+ ctx.fillText(feature_name, f_end + LABEL_SPACING, y_center + 8);
} else {
ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING, y_center + 8);
+ ctx.fillText(feature_name, f_start - LABEL_SPACING, y_center + 8);
}
ctx.fillStyle = block_color;
}
}
- },
- /**
- * Returns y_scale based on mode.
- */
- get_y_scale: function(mode) {
- var y_scale;
- if (mode === "summary_tree") {
- // No scale needed.
- }
- if (mode === "Dense") {
- y_scale = DENSE_TRACK_HEIGHT;
- }
- else if (mode === "no_detail") {
- y_scale = NO_DETAIL_TRACK_HEIGHT;
- }
- else if (mode === "Squish") {
- y_scale = SQUISH_TRACK_HEIGHT;
- }
- else { // mode === "Pack"
- y_scale = PACK_TRACK_HEIGHT;
- }
- return y_scale;
- },
- /**
- * Draw FeatureTrack tile.
- */
- draw_tile: function(result, resolution, tile_index, parent_element, w_scale, ref_seq) {
- var track = this;
- var tile_low = tile_index * DENSITY * resolution,
- tile_high = ( tile_index + 1 ) * DENSITY * resolution,
- tile_span = tile_high - tile_low,
- params = { hda_ldda: track.hda_ldda, dataset_id: track.dataset_id,
- resolution: this.view.resolution, mode: this.mode };
-
- //
- // Create/set/compute some useful vars.
- //
- var width = Math.ceil( tile_span * w_scale ),
- mode = this.mode,
- min_height = 25,
- left_offset = this.left_offset,
- slots, required_height;
-
- var canvas = document.createElement("canvas");
- if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK
- canvas = $(canvas);
-
- //
- // Set mode if Auto.
- //
- if (mode === "Auto") {
- if (result.dataset_type === "summary_tree") {
- mode = result.dataset_type;
- } else if (result.extra_info === "no_detail") {
- mode = "no_detail";
- } else {
- // Choose b/t Squish and Pack.
- // Proxy measures for using Squish:
- // (a) error message re: limiting number of features shown;
- // (b) X number of features shown;
- // (c) size of view shown.
- // TODO: cannot use (a) and (b) because it requires coordinating mode across tiles;
- // fix this so that tiles are redrawn as necessary to use the same mode.
- //if ( (result.message && result.message.match(/^Only the first [\d]+/)) ||
- // (result.data && result.data.length > 2000) ||
- var data = result.data;
- if ( (data.length && data.length < 4) ||
- (this.view.high - this.view.low > MIN_SQUISH_VIEW_WIDTH) ) {
- mode = "Squish";
- } else {
- mode = "Pack";
- }
- }
- }
-
- var y_scale = this.get_y_scale(mode);
-
- //
- // Pack reads, set required height.
- //
- if (mode === "summary_tree") {
- required_height = this.summary_draw_height;
- }
- if (mode === "Dense") {
- required_height = min_height;
- }
- else if (mode === "no_detail" || mode === "Squish" || mode === "Pack") {
- // Calculate new slots incrementally for this new chunk of data and update height if necessary.
- required_height = this.incremental_slots(w_scale, result.data, mode) * y_scale + min_height;
- slots = this.inc_slots[w_scale];
- }
-
- //
- // Set up for drawing.
- //
- canvas.get(0).width = width + left_offset;
- canvas.get(0).height = required_height;
- if (result.dataset_type === "summary_tree") {
- // Increase canvas height in order to display max label.
- canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX;
- }
- 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 = canvas.get(0).getContext("2d");
- ctx.fillStyle = this.prefs.block_color;
- ctx.font = this.default_font;
- ctx.textAlign = "right";
- this.container_div.find(".yaxislabel").remove();
-
- //
- // Draw summary tree. If tree is drawn, canvas is returned.
- //
- if (mode === "summary_tree") {
- this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height,
- tile_low, left_offset);
- return canvas;
- }
-
- //
- // If there is a message, draw it on canvas so that it moves around with canvas, and make the border red
- // to indicate region where message is applicable
- if (result.message) {
- canvas.css({
- "border-top": "1px solid red"
- });
-
- ctx.fillStyle = "red";
- ctx.textAlign = "left";
- var old_base = ctx.textBaseline;
- ctx.textBaseline = "top";
- ctx.fillText(result.message, left_offset, 0);
- ctx.textBaseline = old_base;
-
- // If there's no data, return.
- if (!result.data) {
- return canvas;
- }
- }
-
- //
- // Set example feature. This is needed so that track can update its UI based on feature attributes.
- //
- this.example_feature = (result.data.length ? result.data[0] : undefined);
-
- //
- // Draw elements.
- //
- var data = result.data;
- for (var i = 0, len = data.length; i < len; i++) {
- var feature = data[i],
- feature_uid = feature[0],
- feature_start = feature[1],
- feature_end = feature[2],
- // Slot valid only if features are slotted and this feature is slotted;
- // feature may not be due to lack of space.
- slot = (slots && slots[feature_uid] !== undefined ? slots[feature_uid] : null);
-
- // Apply filters to feature.
- var hide_feature = false;
- var filter;
- for (var f = 0; f < this.filters.length; f++) {
- filter = this.filters[f];
- filter.update_attrs(feature);
- if (!filter.keep(feature)) {
- hide_feature = true;
- break;
- }
- }
- if (hide_feature) {
- continue;
- }
-
- // Draw feature if there's overlap and mode is dense or feature is slotted (as it must be for all non-dense modes).
- if (is_overlap([feature_start, feature_end], [tile_low, tile_high]) && (mode == "Dense" || slot !== null)) {
- this.draw_element(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale,
- width, left_offset, ref_seq);
- }
- }
- return canvas;
}
});
-var VcfTrack = function(name, view, hda_ldda, dataset_id, prefs, filters) {
- FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
- this.track_type = "VcfTrack";
-};
-$.extend(VcfTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * Draw a VCF entry.
- */
+var VariantPainter = function( data, view_start, view_end, prefs, mode ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+}
+
+extend( VariantPainter.prototype, FeaturePainter.prototype, {
draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width) {
var feature = data[i],
feature_uid = feature[0],
@@ -2780,7 +3018,7 @@
// All features need a start, end, and vertical center.
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)) ),
- y_center = ERROR_PADDING + (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
+ y_center = (mode === "Dense" ? 0 : (0 + slot)) * y_scale,
thickness, y_start, thick_start = null, thick_end = null;
if (no_label) {
@@ -2821,36 +3059,17 @@
}
});
-var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) {
- FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters);
-
- this.track_config = new TrackConfig( {
- track: this,
- params: [
- { key: 'block_color', label: 'Block color', type: 'color', default_value: '#444' },
- { key: 'label_color', label: 'Label color', type: 'color', default_value: 'black' },
- { key: 'show_insertions', label: 'Show insertions', type: 'bool', default_value: false },
- { key: 'show_differences', label: 'Show differences only', type: 'bool', default_value: true },
- { key: 'show_counts', label: 'Show summary counts', type: 'bool', default_value: true },
- { key: 'mode', type: 'string', default_value: this.mode, hidden: true },
- ],
- saved_values: prefs,
- onchange: function() {
- this.track.tile_cache.clear();
- this.track.draw();
- }
- });
- this.prefs = this.track_config.values;
-
- this.track_type = "ReadTrack";
- this.make_name_popup_menu();
-};
-$.extend(ReadTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
+var ReadPainter = function( data, view_start, view_end, prefs, mode, ref_seq ) {
+ FeaturePainter.call( this, data, view_start, view_end, prefs, mode );
+ this.ref_seq = ref_seq;
+}
+
+extend( ReadPainter.prototype, FeaturePainter.prototype, {
/**
* Returns y_scale based on mode.
*/
- get_y_scale: function(mode) {
- var y_scale;
+ get_row_height: function() {
+ var y_scale, mode = this.mode;
if (mode === "summary_tree") {
// No scale needed.
}
@@ -2862,7 +3081,7 @@
}
else { // mode === "Pack"
y_scale = PACK_TRACK_HEIGHT;
- if (this.track_config.values.show_insertions) {
+ if (this.prefs.show_insertions) {
y_scale *= 2;
}
}
@@ -2871,13 +3090,14 @@
/**
* Draw a single read.
*/
- draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center, ref_seq) {
+ draw_read: function(ctx, mode, w_scale, tile_low, tile_high, feature_start, cigar, orig_seq, y_center) {
ctx.textAlign = "center";
var track = this,
tile_region = [tile_low, tile_high],
base_offset = 0,
seq_offset = 0,
- gap = 0;
+ gap = 0
+ ref_seq = this.ref_seq;
// Keep list of items that need to be drawn on top of initial drawing layer.
var draw_last = [];
@@ -2918,12 +3138,12 @@
var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
if (gap > 0) {
ctx.fillStyle = this.prefs.block_color;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 1, s_end - s_start, 9);
+ ctx.fillRect(s_start - gap, y_center + 1, s_end - s_start, 9);
ctx.fillStyle = CONNECTOR_COLOR;
// TODO: this can be made much more efficient by computing the complete sequence
// to draw and then drawing it.
for (var c = 0, str_len = seq.length; c < str_len; c++) {
- if (this.track_config.values.show_differences && ref_seq) {
+ if (this.prefs.show_differences && ref_seq) {
var ref_char = ref_seq[seq_start - tile_low + c];
if (!ref_char || ref_char.toLowerCase() === seq[c].toLowerCase()) {
continue;
@@ -2931,13 +3151,13 @@
}
if (seq_start + c >= tile_low && seq_start + c <= tile_high) {
var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset, y_center + 9);
+ ctx.fillText(seq[c], c_start, y_center + 9);
}
}
} else {
ctx.fillStyle = this.prefs.block_color;
// TODO: This is a pretty hack-ish way to fill rectangle based on mode.
- ctx.fillRect(s_start + this.left_offset,
+ ctx.fillRect(s_start,
y_center + (this.mode !== "Dense" ? 4 : 5),
s_end - s_start,
(mode !== "Dense" ? SQUISH_FEATURE_HEIGHT : DENSE_FEATURE_HEIGHT) );
@@ -2948,14 +3168,14 @@
break;
case "N": // Skipped bases.
ctx.fillStyle = CONNECTOR_COLOR;
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 5, s_end - s_start, 1);
+ ctx.fillRect(s_start - gap, y_center + 5, s_end - s_start, 1);
//ctx.dashedLine(s_start + this.left_offset, y_center + 5, this.left_offset + s_end, y_center + 5);
// No change in seq_offset because sequence not used when skipping.
base_offset += cig_len;
break;
case "D": // Deletion.
ctx.fillStyle = "red";
- ctx.fillRect(s_start + this.left_offset - gap, y_center + 4, s_end - s_start, 3);
+ ctx.fillRect(s_start - gap, y_center + 4, s_end - s_start, 3);
// TODO: is this true? No change in seq_offset because sequence not used when skipping.
base_offset += cig_len;
break;
@@ -2967,20 +3187,20 @@
// the sequence region and the tile region.
var
seq_tile_overlap = compute_overlap([seq_start, seq_start + cig_len], tile_region),
- insert_x_coord = this.left_offset + s_start - gap;
+ insert_x_coord = s_start - gap;
if (seq_tile_overlap !== NO_OVERLAP) {
var seq = orig_seq.slice(seq_offset, seq_offset + cig_len);
// Insertion point is between the sequence start and the previous base: (-gap) moves
// back from sequence start to insertion point.
- if (this.track_config.values.show_insertions) {
+ if (this.prefs.show_insertions) {
//
// Show inserted sequence above, centered on insertion point.
//
// Draw sequence.
// X center is offset + start - <half_sequence_length>
- var x_center = this.left_offset + s_start - (s_end - s_start)/2;
+ var x_center = s_start - (s_end - s_start)/2;
if ( (mode === "Pack" || this.mode === "Auto") && orig_seq !== undefined && w_scale > CHAR_WIDTH_PX) {
// Draw sequence container.
ctx.fillStyle = "yellow";
@@ -3005,7 +3225,7 @@
// Draw sequence.
for (var c = 0, str_len = seq.length; c < str_len; c++) {
var c_start = Math.floor( Math.max(0, (seq_start + c - tile_low) * w_scale) );
- ctx.fillText(seq[c], c_start + this.left_offset - (s_end - s_start)/2, y_center);
+ ctx.fillText(seq[c], c_start - (s_end - s_start)/2, y_center);
}
}
else {
@@ -3056,9 +3276,9 @@
}
},
/**
- * Draw a complete read.
+ * Draw a complete read pair
*/
- draw_element: function(ctx, tile_index, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width, left_offset, ref_seq) {
+ draw_element: function(ctx, mode, feature, slot, tile_low, tile_high, w_scale, y_scale, width ) {
// All features need a start, end, and vertical center.
var feature_uid = feature[0],
feature_start = feature[1],
@@ -3088,74 +3308,35 @@
// Draw left/forward read.
if (feature[4][1] >= tile_low && feature[4][0] <= tile_high && feature[4][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center, ref_seq);
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[4][0], feature[4][2], feature[4][3], y_center);
}
// Draw right/reverse read.
if (feature[5][1] >= tile_low && feature[5][0] <= tile_high && feature[5][2]) {
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center, ref_seq);
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature[5][0], feature[5][2], feature[5][3], y_center);
}
// Draw connector.
if (b2_start > b1_end) {
ctx.fillStyle = CONNECTOR_COLOR;
- ctx.dashedLine(b1_end + left_offset - gap, y_center + 5, left_offset + b2_start - gap, y_center + 5);
+ ctx.dashedLine(b1_end - gap, y_center + 5, b2_start - gap, y_center + 5);
}
} else {
// Read is single.
ctx.fillStyle = block_color;
- this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center, ref_seq);
+ this.draw_read(ctx, mode, w_scale, tile_low, tile_high, feature_start, feature[4], feature[5], y_center);
}
if (mode === "Pack" && feature_start > tile_low) {
// Draw label.
ctx.fillStyle = this.prefs.label_color;
+ // FIXME: eliminate tile_index
+ var tile_index = 1;
if (tile_index === 0 && f_start - ctx.measureText(feature_name).width < 0) {
ctx.textAlign = "left";
- ctx.fillText(feature_name, f_end + left_offset + LABEL_SPACING - gap, y_center + 8);
+ ctx.fillText(feature_name, f_end + LABEL_SPACING - gap, y_center + 8);
} else {
ctx.textAlign = "right";
- ctx.fillText(feature_name, f_start + left_offset - LABEL_SPACING - gap, y_center + 8);
+ ctx.fillText(feature_name, f_start - LABEL_SPACING - gap, y_center + 8);
}
ctx.fillStyle = block_color;
}
}
});
-
-/**
- * Feature track that displays data generated from tool.
- */
-var ToolDataFeatureTrack = function(name, view, hda_ldda, dataset_id, prefs, filters, parent_track) {
- FeatureTrack.call(this, name, view, hda_ldda, dataset_id, prefs, filters, {}, parent_track);
- this.track_type = "ToolDataFeatureTrack";
-
- // Set up track to fetch initial data from raw data URL when the dataset--not the converted datasets--
- // is ready.
- this.data_url = raw_data_url;
- this.data_query_wait = 1000;
- this.dataset_check_url = dataset_state_url;
-};
-
-$.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prototype, {
- /**
- * For this track type, the predraw init sets up postdraw init.
- */
- predraw_init: function() {
- // Postdraw init: once data has been fetched, reset data url, wait time and start indexing.
- var track = this;
- var post_init = function() {
- if (track.data_cache.size() === 0) {
- // Track still drawing initial data, so do nothing.
- setTimeout(post_init, 300);
- }
- else {
- // Track drawing done: reset dataset check, data URL, wait time and get dataset state to start
- // indexing.
- track.data_url = default_data_url;
- track.data_query_wait = DEFAULT_DATA_QUERY_WAIT;
- track.dataset_state_url = converted_datasets_state_url;
- $.getJSON(track.dataset_state_url, {dataset_id : track.dataset_id, hda_ldda: track.hda_ldda}, function(track_data) {});
- }
- };
- post_init();
- }
-});
-
-
http://bitbucket.org/galaxy/galaxy-central/changeset/a67e3e47db5d/
changeset: r5319:a67e3e47db5d
user: kanwei
date: 2011-04-01 01:01:12
summary: pack js
affected #: 1 file (2.2 KB)
--- a/static/scripts/packed/trackster.js Thu Mar 31 18:58:25 2011 -0400
+++ b/static/scripts/packed/trackster.js Thu Mar 31 19:01:12 2011 -0400
@@ -1,1 +1,1 @@
-CanvasRenderingContext2D.prototype.dashedLine=function(c,j,b,h,f){if(f===undefined){f=4}var e=b-c;var d=h-j;var g=Math.floor(Math.sqrt(e*e+d*d)/f);var l=e/g;var k=d/g;var a;for(a=0;a<g;a++,c+=l,j+=k){if(a%2!==0){continue}this.fillRect(c,j,f,1)}};CanvasRenderingContext2D.prototype.drawDownwardEquilateralTriangle=function(b,a,e){var d=b-e/2,c=b+e/2,f=a-Math.sqrt(e*3/2);this.beginPath();this.moveTo(d,f);this.lineTo(c,f);this.lineTo(b,a);this.lineTo(d,f);this.strokeStyle=this.fillStyle;this.fill();this.stroke();this.closePath()};function sortable(a,b){a.bind("drag",{handle:b,relative:true},function(h,j){var g=$(this).parent();var f=g.children();var c;for(c=0;c<f.length;c++){if(j.offsetY<$(f.get(c)).position().top){break}}if(c===f.length){if(this!==f.get(c-1)){g.append(this)}}else{if(this!==f.get(c)){$(this).insertBefore(f.get(c))}}})}var NO_OVERLAP=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005;function compute_overlap(e,b){var g=e[0],f=e[1],d=b[0],c=b[1],a;if(g<d){if(f<d){a=NO_OVERLAP}else{if(f<=c){a=OVERLAP_START}else{a=CONTAINS}}}else{if(g>c){a=NO_OVERLAP}else{if(f<=c){a=CONTAINED_BY}else{a=OVERLAP_END}}}return a}function is_overlap(b,a){return(compute_overlap(b,a)!==NO_OVERLAP)}function get_slider_step(c,a){var b=a-c;return(b<=1?0.01:(b<=1000?1:5))}var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=1,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,ERROR_PADDING=10,LABEL_SPACING=2,PACK_SPACING=5,MIN_SQUISH_VIEW_WIDTH=12000,DEFAULT_FONT="9px Monaco, Lucida Console, monospace",DENSITY=200,FEATURE_LEVELS=10,MAX_FEATURE_DEPTH=100,DEFAULT_DATA_QUERY_WAIT=5000,MAX_CHROMS_SELECTABLE=100,CONNECTOR_COLOR="#ccc",DATA_ERROR="There was an error in indexing this dataset. ",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_CANNOT_RUN_TOOL="Tool cannot be rerun: ",DATA_LOADING="Loading data...",DATA_OK="Ready for display",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=5,CACHED_DATA=5,DUMMY_CANVAS=document.createElement("canvas"),RIGHT_STRAND,LEFT_STRAND;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(DUMMY_CANVAS)}var CONTEXT=DUMMY_CANVAS.getContext("2d");CONTEXT.font=DEFAULT_FONT;var CHAR_WIDTH_PX=CONTEXT.measureText("A").width,CHAR_HEIGHT_PX=9;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};function round_1000(a){return Math.round(a*1000)/1000}var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!==-1){this.move_key_to_end(b,a)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},move_key_to_end:function(b,a){this.key_ary.splice(a,1);this.key_ary.push(b)},clear:function(){this.obj_cache={};this.key_ary=[]},size:function(){return this.key_ary.length}});var DataManager=function(b,a,c){Cache.call(this,b);this.track=a;this.subset=(c!==undefined?c:true)};$.extend(DataManager.prototype,Cache.prototype,{load_data:function(h,j,d,g,a,f){var c={chrom:h,low:j,high:d,mode:g,resolution:a,dataset_id:this.track.dataset_id,hda_ldda:this.track.hda_ldda};$.extend(c,f);var k=[];for(var e=0;e<this.track.filters.length;e++){k[k.length]=this.track.filters[e].name}c.filter_cols=JSON.stringify(k);var b=this;return $.getJSON(this.track.data_url,c,function(l){b.set_data(j,d,g,l)})},get_data:function(j,k,c,h,b,f){var l=this.get(this.gen_key(k,c,h));if(l){return l}if(this.subset){var m,g,a,e,h,l;for(var d=0;d<this.key_ary.length;d++){m=this.key_ary[d];g=this.split_key(m);a=g[0];e=g[1];if(k>=a&&c<=e){l=this.obj_cache[m];if(l.dataset_type!=="summary_tree"&&l.extra_info!=="no_detail"){this.move_key_to_end(m,d);return l}}}}return this.load_data(j,k,c,h,b,f)},set_data:function(b,c,d,a){return this.set(this.gen_key(b,c,d),a)},gen_key:function(a,c,d){var b=a+"_"+c+"_"+d;return b},split_key:function(a){return a.split("_")}});var View=function(a,d,c,b,e){this.container=a;this.chrom=null;this.vis_id=c;this.dbkey=b;this.title=d;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.num_tracks=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init(e);this.reset()};$.extend(View.prototype,{init:function(d){var c=this.container,a=this;this.top_container=$("<div/>").addClass("top-container").appendTo(c);this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(c);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(c);this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("<div/>").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.intro_div=$("<div/>").addClass("intro").text("Select a chrom from the dropdown below").hide();this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("<div/>").addClass("nav-container").prependTo(this.top_container);this.nav=$("<div/>").addClass("nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("<a href='javascript:void(0);'>Close Overview</a>").addClass("overview-close").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var b=function(f){if(f.type==="focusout"||(f.keyCode||f.which)===13||(f.keyCode||f.which)===27){if((f.keyCode||f.which)!==27){a.go_to($(this).val())}$(this).hide();$(this).val("");a.location_span.show();a.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",b).appendTo(this.nav_controls);this.location_span=$("<span/>").addClass("location").appendTo(this.nav_controls);this.location_span.bind("click",function(){a.location_span.hide();a.chrom_select.hide();a.nav_input.val(a.chrom+":"+a.low+"-"+a.high);a.nav_input.css("display","inline-block");a.nav_input.select();a.nav_input.focus()});if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("<a id='zoom-out' />").click(function(){a.zoom_out();a.redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a id='zoom-in' />").click(function(){a.zoom_in();a.redraw()}).appendTo(this.nav_controls);this.load_chroms({low:0},d);this.chrom_select.bind("change",function(){a.change_chrom(a.chrom_select.val())});this.intro_div.show();this.content_div.bind("click",function(f){$(this).find("input").trigger("blur")});this.content_div.bind("dblclick",function(f){a.zoom_in(f.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(f,g){this.current_x=g.offsetX}).bind("drag",function(f,h){var j=h.offsetX-this.current_x;this.current_x=h.offsetX;var g=Math.round(j/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-g)});this.overview_close.bind("click",function(){for(var f=0,e=a.tracks.length;f<e;f++){a.tracks[f].is_overview=false}$(this).siblings().filter("canvas").remove();$(this).parent().css("height",a.overview_box.height());a.overview_highlight.hide();$(this).hide()});this.viewport_container.bind("draginit",function(f,g){if(f.clientX>a.viewport_container.width()-16){return false}}).bind("dragstart",function(f,g){g.original_low=a.low;g.current_height=f.clientY;g.current_x=g.offsetX}).bind("drag",function(h,k){var f=$(this);var l=k.offsetX-k.current_x;var g=f.scrollTop()-(h.clientY-k.current_height);f.scrollTop(g);k.current_height=h.clientY;k.current_x=k.offsetX;var j=Math.round(l/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}).bind("mousewheel",function(h,k,g,f){if(g){var j=Math.round(-g/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}});this.top_labeltrack.bind("dragstart",function(f,g){return $("<div />").css({height:a.content_div.height()+a.top_labeltrack.height()+a.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(k,l){$(l.proxy).css({left:Math.min(k.pageX,l.startX),width:Math.abs(k.pageX-l.startX)});var g=Math.min(k.pageX,l.startX)-a.container.offset().left,f=Math.max(k.pageX,l.startX)-a.container.offset().left,j=(a.high-a.low),h=a.viewport_container.width();a.update_location(Math.round(g/h*j)+a.low,Math.round(f/h*j)+a.low)}).bind("dragend",function(l,m){var g=Math.min(l.pageX,m.startX),f=Math.max(l.pageX,m.startX),j=(a.high-a.low),h=a.viewport_container.width(),k=a.low;a.low=Math.round(g/h*j)+k;a.high=Math.round(f/h*j)+k;$(m.proxy).remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack));$(window).bind("resize",function(){a.resize_window()});$(document).bind("redraw",function(){a.redraw()});this.reset();$(window).trigger("resize")},update_location:function(a,b){this.location_span.text(commatize(a)+" - "+commatize(b));this.nav_input.val(this.chrom+":"+commatize(a)+"-"+commatize(b))},load_chroms:function(b,c){b.num=MAX_CHROMS_SELECTABLE;$.extend(b,(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}));var a=this;$.ajax({url:chrom_url,data:b,dataType:"json",success:function(e){if(e.chrom_info.length===0){alert("Invalid chromosome: "+b.chrom);return}if(e.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=e.chrom_info;var h='<option value="">Select Chrom/Contig</option>';for(var g=0,d=a.chrom_data.length;g<d;g++){var f=a.chrom_data[g].chrom;h+='<option value="'+f+'">'+f+"</option>"}if(e.prev_chroms){h+='<option value="previous">Previous '+MAX_CHROMS_SELECTABLE+"</option>"}if(e.next_chroms){h+='<option value="next">Next '+MAX_CHROMS_SELECTABLE+"</option>"}a.chrom_select.html(h);if(c){c()}a.chrom_start_index=e.start_index},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}})},change_chrom:function(e,b,g){if(!e||e==="None"){return}var d=this;if(e==="previous"){d.load_chroms({low:this.chrom_start_index-MAX_CHROMS_SELECTABLE});return}if(e==="next"){d.load_chroms({low:this.chrom_start_index+MAX_CHROMS_SELECTABLE});return}var f=$.grep(d.chrom_data,function(j,k){return j.chrom===e})[0];if(f===undefined){d.load_chroms({chrom:e},function(){d.change_chrom(e,b,g)});return}else{if(e!==d.chrom){d.chrom=e;if(!d.chrom){d.intro_div.show()}else{d.intro_div.hide()}d.chrom_select.val(d.chrom);d.max_high=f.len-1;d.reset();d.redraw(true);for(var h=0,a=d.tracks.length;h<a;h++){var c=d.tracks[h];if(c.init){c.init()}}}if(b!==undefined&&g!==undefined){d.low=Math.max(b,0);d.high=Math.min(g,d.max_high)}d.reset_overview();d.redraw()}},go_to:function(f){var k=this,a,d,b=f.split(":"),h=b[0],j=b[1];if(j!==undefined){try{var g=j.split("-");a=parseInt(g[0].replace(/,/g,""),10);d=parseInt(g[1].replace(/,/g,""),10)}catch(c){return false}}k.change_chrom(h,a,d)},move_fraction:function(c){var a=this;var b=a.high-a.low;this.move_delta(c*b)},move_delta:function(c){var a=this;var b=a.high-a.low;if(a.low-c<a.max_low){a.low=a.max_low;a.high=a.max_low+b}else{if(a.high-c>a.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_track:function(a){a.view=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);sortable(a.container_div,".draghandle");this.track_id_counter+=1;this.num_tracks+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)];this.num_tracks-=1},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(h){var g=this.high-this.low,f=this.low,b=this.high;if(f<this.max_low){f=this.max_low}if(b>this.max_high){b=this.max_high}if(this.high!==0&&g<this.min_separation){b=f+this.min_separation}this.low=Math.floor(f);this.high=Math.ceil(b);this.resolution=Math.pow(10,Math.ceil(Math.log((this.high-this.low)/200)/Math.LN10));this.zoom_res=Math.pow(FEATURE_LEVELS,Math.max(0,Math.ceil(Math.log(this.resolution,FEATURE_LEVELS)/Math.log(FEATURE_LEVELS))));var a=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var e=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var j=13;this.overview_box.css({left:a,width:Math.max(j,e)}).show();if(e<j){this.overview_box.css("left",a-(j-e)/2)}if(this.overview_highlight){this.overview_highlight.css({left:a,width:e})}this.update_location(this.low,this.high);if(!h){for(var c=0,d=this.tracks.length;c<d;c++){if(this.tracks[c]&&this.tracks[c].enabled){this.tracks[c].draw()}}for(c=0,d=this.label_tracks.length;c<d;c++){this.label_tracks[c].draw()}}},zoom_in:function(b,c){if(this.max_high===0||this.high-this.low<this.min_separation){return}var d=this.high-this.low,e=d/2+this.low,a=(d/this.zoom_factor)/2;if(b){e=b/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(e-a);this.high=Math.round(e+a);this.redraw()},zoom_out:function(){if(this.max_high===0){return}var b=this.high-this.low,c=b/2+this.low,a=(b*this.zoom_factor)/2;this.low=Math.round(c-a);this.high=Math.round(c+a);this.redraw()},resize_window:function(){this.viewport_container.height(this.container.height()-this.top_container.height()-this.bottom_container.height());this.nav_container.width(this.container.width());this.redraw()},reset_overview:function(){this.overview_viewport.find("canvas").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide()}});var Tool=function(g){this.name=g.name;this.params=[];var a=g.params;for(var e=0;e<a.length;e++){var d=a[e],c=d.name,b=d.label,f=d.type;if(f==="int"||f==="float"){this.params[this.params.length]=new NumberParameter(c,b,d.min,d.max,d.value)}else{if(f=="select"){this.params[this.params.length]=new SelectParameter(c,b,d.options)}else{console.log("WARNING: unrecognized tool parameter type:",c,f)}}}};$.extend(Tool.prototype,{get_param_values_dict:function(){var b={};for(var a=0;a<this.params.length;a++){var c=this.params[a];b[c.name]=JSON.stringify(c.value)}return b},get_param_values:function(){var b=[];for(var a=0;a<this.params.length;a++){b[a]=this.params[a].value}return b}});var NumberParameter=function(c,b,e,a,d){this.name=c;this.label=b;this.min=e;this.max=a;this.value=d};var SelectParameter=function(c,b,a){this.name=c;this.label=b;this.options=a;this.value=(a.length!==0?a[0][1]:null)};var Filter=function(b,a,c){this.name=b;this.index=a;this.value=c};var NumberFilter=function(b,a){this.name=b;this.index=a;this.low=-Number.MAX_VALUE;this.high=Number.MAX_VALUE;this.min=Number.MAX_VALUE;this.max=-Number.MAX_VALUE;this.slider=null;this.slider_label=null};$.extend(NumberFilter.prototype,{applies_to:function(a){if(a.length>this.index){return true}return false},keep:function(a){if(!this.applies_to(a)){return true}return(a[this.index]>=this.low&&a[this.index]<=this.high)},update_attrs:function(b){var a=false;if(!this.applies_to(b)){return a}if(b[this.index]<this.min){this.min=Math.floor(b[this.index]);a=true}if(b[this.index]>this.max){this.max=Math.ceil(b[this.index]);a=true}return a},update_ui_elt:function(){var b=this.slider.slider("option","min"),a=this.slider.slider("option","max");if(this.min<b||this.max>a){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var get_filters_from_dict=function(a){var g=[];for(var d=0;d<a.length;d++){var f=a[d];var c=f.name,e=f.type,b=f.index;if(e==="int"||e==="float"){g[d]=new NumberFilter(c,b)}else{g[d]=new Filter(c,b,e)}}return g};var TrackConfig=function(a){this.track=a.track;this.params=a.params;this.values={};if(a.saved_values){this.restore_values(a.saved_values)}this.onchange=a.onchange};$.extend(TrackConfig.prototype,{restore_values:function(a){var b=this;$.each(this.params,function(c,d){if(a[d.key]!==undefined){b.values[d.key]=a[d.key]}else{b.values[d.key]=d.default_value}})},build_form:function(){var b=this;var a=$("<div />");$.each(this.params,function(f,d){if(!d.hidden){var c="param_"+f;var l=$("<div class='form-row' />").appendTo(a);l.append($("<label />").attr("for",c).text(d.label+":"));if(d.type==="bool"){l.append($('<input type="checkbox" />').attr("id",c).attr("name",c).attr("checked",b.values[d.key]))}else{if(d.type==="color"){var h=b.values[d.key];var g=$("<input />").attr("id",c).attr("name",c).val(h);var j=$("<div class='tipsy tipsy-north' style='position: absolute;' />").hide();var e=$("<div style='background-color: black; padding: 10px;'></div>").appendTo(j);var k=$("<div/>").appendTo(e).farbtastic({width:100,height:100,callback:g,color:h});$("<div />").append(g).append(j).appendTo(l).bind("click",function(m){j.css({left:$(this).position().left+($(g).width()/2)-60,top:$(this).position().top+$(this.height)}).show();$(document).bind("click.color-picker",function(){j.hide();$(document).unbind("click.color-picker")});m.stopPropagation()})}else{l.append($("<input />").attr("id",c).attr("name",c).val(b.values[d.key]))}}}});return a},update_from_form:function(a){var c=this;var b=false;$.each(this.params,function(d,f){if(!f.hidden){var g="param_"+d;var e=a.find("#"+g).val();if(f.type==="float"){e=parseFloat(e)}else{if(f.type==="int"){e=parseInt(e)}else{if(f.type==="bool"){e=a.find("#"+g).is(":checked")}}}if(e!==c.values[f.key]){c.values[f.key]=e;b=true}}});if(b){this.onchange()}}});var Tile=function(a,b,c){this.track=a;this.canvas=b;this.histo_max=c};var Track=function(b,a,e,c,d){this.name=b;this.view=a;this.parent_element=e;this.data_url=(c?c:default_data_url);this.data_url_extra_params={};this.data_query_wait=(d?d:DEFAULT_DATA_QUERY_WAIT);this.dataset_check_url=converted_datasets_state_url;this.container_div=$("<div />").addClass("track").css("position","relative");if(!this.hidden){this.header_div=$("<div class='track-header' />").appendTo(this.container_div);if(this.view.editor){this.drag_div=$("<div class='draghandle' />").appendTo(this.header_div)}this.name_div=$("<div class='menubutton popup' />").appendTo(this.header_div);this.name_div.text(this.name);this.name_div.attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase())}this.content_div=$("<div class='track-content'>").appendTo(this.container_div);this.parent_element.append(this.container_div)};$.extend(Track.prototype,{init:function(){var a=this;a.enabled=false;a.tile_cache.clear();a.data_cache.clear();a.initial_canvas=undefined;a.content_div.css("height","auto");a.container_div.removeClass("nodata error pending");if(!a.dataset_id){return}$.getJSON(converted_datasets_state_url,{hda_ldda:a.hda_ldda,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){if(!b||b==="error"||b.kind==="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR);if(b.message){var d=a.view.tracks.indexOf(a);var c=$(" <a href='javascript:void(0);'></a>").text("View error").bind("click",function(){show_modal("Trackster Error","<pre>"+b.message+"</pre>",{Close:hide_modal})});a.content_div.append(c)}}else{if(b==="no converter"){a.container_div.addClass("error");a.content_div.text(DATA_NOCONVERTER)}else{if(b==="no data"||(b.data!==undefined&&(b.data===null||b.data.length===0))){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(b==="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},a.data_query_wait)}else{if(b.status==="data"){if(b.valid_chroms){a.valid_chroms=b.valid_chroms;a.make_name_popup_menu()}a.content_div.text(DATA_OK);if(a.view.chrom){a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.enabled=true;$.when(a.predraw_init()).done(function(){a.draw()})}}}}}}})},predraw_init:function(){},update_name:function(a){this.old_name=this.name;this.name=a;this.name_div.text(this.name)},revert_name:function(){this.name=this.old_name;this.name_div.text(this.name)}});var TiledTrack=function(b,h,o){var c=this,p=c.view;this.filters=(b!==undefined?get_filters_from_dict(b):[]);this.filters_available=false;this.filters_visible=false;this.tool=(h!==undefined&&obj_length(h)>0?new Tool(h):undefined);this.parent_track=o;this.child_tracks=[];if(c.hidden){return}var k=function(r,s,t){r.click(function(){var u=s.text();max=parseFloat(t.slider("option","max")),input_size=(max<=1?4:max<=1000000?max.toString().length:6),multi_value=false;if(t.slider("option","values")){input_size=2*input_size+1;multi_value=true}s.text("");$("<input type='text'/>").attr("size",input_size).attr("maxlength",input_size).attr("value",u).appendTo(s).focus().select().click(function(v){v.stopPropagation()}).blur(function(){$(this).remove();s.text(u)}).keyup(function(z){if(z.keyCode===27){$(this).trigger("blur")}else{if(z.keyCode===13){var x=t.slider("option","min"),v=t.slider("option","max"),y=function(A){return(isNaN(A)||A>v||A<x)},w=$(this).val();if(!multi_value){w=parseFloat(w);if(y(w)){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}else{w=w.split("-");w=[parseFloat(w[0]),parseFloat(w[1])];if(y(w[0])||y(w[1])){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}t.slider((multi_value?"values":"value"),w)}}})})};if(this.parent_track){this.header_div.find(".draghandle").removeClass("draghandle").addClass("child-track-icon").addClass("icon-button");this.parent_element.addClass("child-track");this.tool=undefined}this.filters_div=$("<div/>").addClass("filters").hide();this.header_div.after(this.filters_div);this.filters_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});$.each(this.filters,function(x,s){var u=$("<div/>").addClass("slider-row").appendTo(c.filters_div);var r=$("<div/>").addClass("slider-label").appendTo(u);var z=$("<span/>").addClass("slider-name").text(s.name+" ").appendTo(r);var t=$("<span/>");var v=$("<span/>").addClass("slider-value").appendTo(r).append("[").append(t).append("]");var y=$("<div/>").addClass("slider").appendTo(u);s.control_element=$("<div/>").attr("id",s.name+"-filter-control").appendTo(y);var w=[0,0];s.control_element.slider({range:true,min:Number.MAX_VALUE,max:-Number.MIN_VALUE,values:[0,0],slide:function(A,B){w=B.values;t.text(B.values[0]+"-"+B.values[1]);setTimeout(function(){if(B.values[0]==w[0]&&B.values[1]==w[1]){var C=B.values;t.text(C[0]+"-"+C[1]);s.low=C[0];s.high=C[1];c.draw(true,true)}},50)},change:function(A,B){s.control_element.slider("option","slide").call(s.control_element,A,B)}});s.slider=s.control_element;s.slider_label=t;k(v,t,s.control_element);$("<div style='clear: both;'/>").appendTo(u)});if(this.tool){this.dynamic_tool_div=$("<div/>").addClass("dynamic-tool").hide();this.header_div.after(this.dynamic_tool_div);this.dynamic_tool_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});var n=$("<div class='tool-name'>").appendTo(this.dynamic_tool_div).text(this.tool.name);var m=this.tool.params;var c=this;$.each(this.tool.params,function(A,s){if(s instanceof NumberParameter){var x=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(x);var C=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var t=$("<span/>").text(s.value);var y=$("<span/>").addClass("slider-value").appendTo(u).append("[").append(t).append("]");var B=$("<div/>").addClass("slider").appendTo(x);var r=$("<div id='"+s.name+"-param-control'>").appendTo(B);r.slider({min:s.min,max:s.max,step:get_slider_step(s.min,s.max),value:s.value,slide:function(F,H){var G=H.value;s.value=G;if(0<G&&G<1){G=parseFloat(G).toFixed(2)}t.text(G)},change:function(F,G){r.slider("option","slide").call(r,F,G)}});k(y,t,r);$("<div style='clear: both;'/>").appendTo(x)}else{if(s instanceof SelectParameter){var x=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(x);var C=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var E=$("<div/>").addClass("slider").appendTo(x);var w=$("<select/>").appendTo(E);w.change(function(){s.value=$(this).val()});var D=w.attr("options");for(var z=0;z<s.options.length;z++){var v=s.options[z];D[D.length]=new Option(v[0],v[1])}$("<div style='clear: both;'/>").appendTo(x)}}});var q=$("<div>").addClass("slider-row").appendTo(this.dynamic_tool_div);var l=$("<input type='submit'>").attr("value","Run").appendTo(q);var c=this;l.click(function(){c.run_tool()})}c.child_tracks_container=$("<div/>").addClass("child-tracks-container").hide();c.container_div.append(c.child_tracks_container);if(c.display_modes!==undefined){if(c.mode_div===undefined){c.mode_div=$("<div class='right-float menubutton popup' />").appendTo(c.header_div);var e=(c.track_config&&c.track_config.values.mode?c.track_config.values.mode:c.display_modes[0]);c.mode=e;c.mode_div.text(e);var d=function(r){c.mode_div.text(r);c.mode=r;c.track_config.values.mode=r;c.tile_cache.clear();c.draw()};var a={};for(var f=0,j=c.display_modes.length;f<j;f++){var g=c.display_modes[f];a[g]=function(r){return function(){d(r)}}(g)}make_popupmenu(c.mode_div,a)}else{c.mode_div.hide()}}this.make_name_popup_menu()};$.extend(TiledTrack.prototype,Track.prototype,{make_name_popup_menu:function(){var b=this;var a={};a["Edit configuration"]=function(){var h=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},f=function(){b.track_config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},g=function(j){if((j.keyCode||j.which)===27){h()}else{if((j.keyCode||j.which)===13){f()}}};$(window).bind("keypress.check_enter_esc",g);show_modal("Configure Track",b.track_config.build_form(),{Cancel:h,OK:f})};if(b.filters_available>0){var e=(b.filters_div.is(":visible")?"Hide filters":"Show filters");a[e]=function(){b.filters_visible=(b.filters_div.is(":visible"));b.filters_div.toggle();b.make_name_popup_menu()}}if(b.tool){var e=(b.dynamic_tool_div.is(":visible")?"Hide tool":"Show tool");a[e]=function(){if(!b.dynamic_tool_div.is(":visible")){b.update_name(b.name+b.tool_region_and_parameters_str())}else{menu_option_text="Show dynamic tool";b.revert_name()}b.dynamic_tool_div.toggle();b.make_name_popup_menu()}}if(b.valid_chroms){a["List chrom/contigs with data"]=function(){show_modal("Chrom/contigs with data","<p>"+b.valid_chroms.join("<br/>")+"</p>",{Close:function(){hide_modal()}})}}var c=view;var d=function(){$("#no-tracks").show()};if(this.parent_track){c=this.parent_track;d=function(){}}a.Remove=function(){c.remove_track(b);if(c.num_tracks===0){d()}};make_popupmenu(b.name_div,a)},draw:function(a,d){var s=this.view.low,g=this.view.high,j=g-s,l=this.view.container.width(),f=l/j,m=this.view.resolution,e=$("<div style='position: relative;'></div>");if(!d){this.content_div.children().remove()}this.content_div.append(e);this.max_height=0;var o=Math.floor(s/m/DENSITY);var c={};while((o*DENSITY*m)<g){var r=l+"_"+f+"_"+o;var h=this.tile_cache.get(r);var p=o*DENSITY*this.view.resolution;var b=p+DENSITY*this.view.resolution;if(!a&&h){this.show_tile(h,e,p,f)}else{this.delayed_draw(a,r,p,b,o,m,e,f,c)}o+=1}var k=this;var q=setInterval(function(){if(obj_length(c)===0){clearInterval(q);if(d){var v=k.content_div.children();var u=false;for(var w=v.length-1,t=0;w>=t;w--){var z=$(v[w]);if(u){z.remove()}else{if(z.children().length!==0){u=true}}}}for(var y=0;y<k.filters.length;y++){k.filters[y].update_ui_elt()}var x=false;if(k.example_feature){for(var y=0;y<k.filters.length;y++){if(k.filters[y].applies_to(k.example_feature)){x=true;break}}}if(k.filters_available!==x){k.filters_available=x;if(!k.filters_available){k.filters_div.hide()}k.make_name_popup_menu()}}},50);for(var n=0;n<this.child_tracks.length;n++){this.child_tracks[n].draw(a,d)}},delayed_draw:function(b,j,g,l,c,e,k,m,f){var d=this;var h=function(u,n,p,o,s,t,q){returned_tile=d.draw_tile(n,p,o,s,t,q);var r=$("<div class='track-tile'>").prepend(returned_tile);tile_element=r;d.tile_cache.set(j,tile_element);d.show_tile(tile_element,s,g,t);delete f[u]};var a=setTimeout(function(){if(g<=d.view.high&&l>=d.view.low){var n=(b?undefined:d.tile_cache.get(j));if(n){d.show_tile(n,k,g,m);delete f[a]}else{$.when(d.data_cache.get_data(view.chrom,g,l,d.mode,e,d.data_url_extra_params)).then(function(o){if(view.reference_track&&m>CHAR_WIDTH_PX){$.when(view.reference_track.data_cache.get_data(view.chrom,g,l,d.mode,e,view.reference_track.data_url_extra_params)).then(function(p){h(a,o,e,c,k,m,p)})}else{h(a,o,e,c,k,m)}})}}},50);f[a]=true},show_tile:function(a,f,d,g){var b=this;var c=this.view.high-this.view.low,e=(d-this.view.low)*g;if(this.left_offset){e-=this.left_offset}a.css({position:"absolute",top:0,left:e,height:""});f.append(a);b.max_height=Math.max(b.max_height,a.height());b.content_div.css("height",b.max_height+"px");f.children().css("height",b.max_height+"px")},set_overview:function(){var a=this.view;if(this.initial_canvas&&this.is_overview){a.overview_close.show();a.overview_viewport.append(this.initial_canvas);a.overview_highlight.show().height(this.initial_canvas.height());a.overview_viewport.height(this.initial_canvas.height()+a.overview_box.height())}$(window).trigger("resize")},run_tool:function(){var b={dataset_id:this.original_dataset_id,chrom:this.view.chrom,low:this.view.low,high:this.view.high,tool_id:this.tool.name};$.extend(b,this.tool.get_param_values_dict());var d=this,c=b.tool_id+d.tool_region_and_parameters_str(b.chrom,b.low,b.high),e;if(d.track_type==="FeatureTrack"){e=new ToolDataFeatureTrack(c,view,d.hda_ldda,undefined,{},{},d)}this.add_track(e);e.content_div.text("Starting job.");view.has_changes=true;var a=function(){$.getJSON(run_tool_url,b,function(f){if(f==="no converter"){e.container_div.addClass("error");e.content_div.text(DATA_NOCONVERTER)}else{if(f.error){e.container_div.addClass("error");e.content_div.text(DATA_CANNOT_RUN_TOOL+f.message)}else{if(f==="pending"){e.container_div.addClass("pending");e.content_div.text("Converting input data so that it can be easily reused.");setTimeout(a,2000)}else{e.dataset_id=f.dataset_id;e.content_div.text("Running job.");e.init()}}}})};a()},tool_region_and_parameters_str:function(c,a,d){var b=this,e=(c!==undefined&&a!==undefined&&d!==undefined?c+":"+a+"-"+d:"all");return" - region=["+e+"], parameters=["+b.tool.get_param_values().join(", ")+"]"},add_track:function(a){a.track_id=this.track_id+"_"+this.child_tracks.length;a.container_div.attr("id","track_"+a.track_id);this.child_tracks_container.append(a.container_div);sortable(a.container_div,".child-track-icon");if(!$(this.child_tracks_container).is(":visible")){this.child_tracks_container.show()}this.child_tracks.push(a)},remove_track:function(a){a.container_div.fadeOut("slow",function(){$(this).remove()})}});var LabelTrack=function(a,b){this.track_type="LabelTrack";this.hidden=true;Track.call(this,null,a,b);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.view.container.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+commatize(a)+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var ReferenceTrack=function(a){this.track_type="ReferenceTrack";this.hidden=true;Track.call(this,null,a,a.top_labeltrack);TiledTrack.call(this);a.reference_track=this;this.left_offset=200;this.height_px=12;this.font=DEFAULT_FONT;this.container_div.addClass("reference-track");this.content_div.css("background","none");this.content_div.css("min-height","0px");this.content_div.css("border","none");this.data_url=reference_url;this.data_url_extra_params={dbkey:a.dbkey};this.data_cache=new DataManager(CACHED_DATA,this,false);this.tile_cache=new Cache(CACHED_TILES_LINE)};$.extend(ReferenceTrack.prototype,TiledTrack.prototype,{draw_tile:function(m,g,b,j,n){var f=this,d=DENSITY*g;if(n>CHAR_WIDTH_PX){if(m===null){f.content_div.css("height","0px");return}var e=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(e)}e=$(e);var l=e.get(0).getContext("2d");e.get(0).width=Math.ceil(d*n+f.left_offset);e.get(0).height=f.height_px;l.font=DEFAULT_FONT;l.textAlign="center";for(var h=0,k=m.length;h<k;h++){var a=Math.round(h*n);l.fillText(m[h],a+f.left_offset,10)}return e}this.content_div.css("height","0px")}});var LineTrack=function(e,c,f,a,d){var b=this;this.track_type="LineTrack";this.display_modes=["Histogram","Line","Filled","Intensity"];this.mode="Histogram";Track.call(this,e,c,c.viewport_container);TiledTrack.call(this);this.min_height_px=16;this.max_height_px=400;this.height_px=80;this.hda_ldda=f;this.dataset_id=a;this.original_dataset_id=a;this.data_cache=new DataManager(CACHED_DATA,this);this.tile_cache=new Cache(CACHED_TILES_LINE);this.track_config=new TrackConfig({track:this,params:[{key:"color",label:"Color",type:"color",default_value:"black"},{key:"min_value",label:"Min Value",type:"float",default_value:undefined},{key:"max_value",label:"Max Value",type:"float",default_value:undefined},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.height_px,hidden:true}],saved_values:d,onchange:function(){b.vertical_range=b.prefs.max_value-b.prefs.min_value;$("#linetrack_"+b.track_id+"_minval").text(b.prefs.min_value);$("#linetrack_"+b.track_id+"_maxval").text(b.prefs.max_value);b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;this.height_px=this.track_config.values.height;this.vertical_range=this.track_config.values.max_value-this.track_config.values.min_value;(function(g){var k=false;var j=false;var h=$("<div class='track-resize'>");$(g.container_div).hover(function(){k=true;h.show()},function(){k=false;if(!j){h.hide()}});h.hide().bind("dragstart",function(l,m){j=true;m.original_height=$(g.content_div).height()}).bind("drag",function(m,n){var l=Math.min(Math.max(n.original_height+n.deltaY,g.min_height_px),g.max_height_px);$(g.content_div).css("height",l);g.height_px=l;g.draw(true)}).bind("dragend",function(l,m){g.tile_cache.clear();j=false;if(!k){h.hide()}g.track_config.values.height=g.height_px}).appendTo(g.container_div)})(this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{predraw_init:function(){var a=this,b=a.view.tracks.indexOf(a);a.vertical_range=undefined;return $.getJSON(a.data_url,{stats:true,chrom:a.view.chrom,low:null,high:null,hda_ldda:a.hda_ldda,dataset_id:a.dataset_id},function(c){a.container_div.addClass("line-track");var e=c.data;if(isNaN(parseFloat(a.prefs.min_value))||isNaN(parseFloat(a.prefs.max_value))){a.prefs.min_value=e.min;a.prefs.max_value=e.max;$("#track_"+b+"_minval").val(a.prefs.min_value);$("#track_"+b+"_maxval").val(a.prefs.max_value)}a.vertical_range=a.prefs.max_value-a.prefs.min_value;a.total_frequency=e.total_frequency;a.container_div.find(".yaxislabel").remove();var f=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_minval").text(round_1000(a.prefs.min_value));var d=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_maxval").text(round_1000(a.prefs.max_value));d.css({position:"absolute",top:"24px",left:"10px"});d.prependTo(a.container_div);f.css({position:"absolute",bottom:"2px",left:"10px"});f.prependTo(a.container_div)})},draw_tile:function(j,q,t,c,e){if(this.vertical_range===undefined){return}var o=this,u=t*DENSITY*q,a=DENSITY*q,B=q+"_"+t,v={hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,resolution:this.view.resolution};var b=document.createElement("canvas"),z=j.data;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(b)}b=$(b);b.get(0).width=Math.ceil(a*e);b.get(0).height=o.height_px;var p=b.get(0).getContext("2d"),k=false,l=o.prefs.min_value,g=o.prefs.max_value,n=o.vertical_range,w=o.total_frequency,d=o.height_px,m=o.mode;var A=Math.round(d+l/n*d);p.beginPath();p.moveTo(0,A);p.lineTo(a*e,A);p.fillStyle="#aaa";p.stroke();p.beginPath();p.fillStyle=o.prefs.color;var x,h,f;if(z.length>1){f=Math.ceil((z[1][0]-z[0][0])*e)}else{f=10}for(var r=0,s=z.length;r<s;r++){x=Math.round((z[r][0]-u)*e);h=z[r][1];if(h===null){if(k&&m==="Filled"){p.lineTo(x,d)}k=false;continue}if(h<l){h=l}else{if(h>g){h=g}}if(m==="Histogram"){h=Math.round(h/n*d);p.fillRect(x,A,f,-h)}else{if(m==="Intensity"){h=255-Math.floor((h-l)/n*255);p.fillStyle="rgb("+h+","+h+","+h+")";p.fillRect(x,0,f,d)}else{h=Math.round(d-(h-l)/n*d);if(k){p.lineTo(x,h)}else{k=true;if(m==="Filled"){p.moveTo(x,d);p.lineTo(x,h)}else{p.moveTo(x,h)}}}}}if(m==="Filled"){if(k){p.lineTo(x,A);p.lineTo(0,A)}p.fill()}else{p.stroke()}return b}});var FeatureTrack=function(a,f,e,j,h,c,d,g){var b=this;this.track_type="FeatureTrack";this.display_modes=["Auto","Dense","Squish","Pack"];this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:h,onchange:function(){b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;Track.call(this,a,f,f.viewport_container);TiledTrack.call(this,c,d,g);this.height_px=0;this.container_div.addClass("feature-track");this.hda_ldda=e;this.dataset_id=j;this.original_dataset_id=j;this.zo_slots={};this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.default_font=DEFAULT_FONT;this.inc_slots={};this.start_end_dct={};this.tile_cache=new Cache(CACHED_TILES_FEATURE);this.data_cache=new DataManager(20,this);this.left_offset=200};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{incremental_slots:function(a,h,p){var q=this.inc_slots[a];if(!q||(q.mode!==p)){q={};q.w_scale=a;q.mode=p;this.inc_slots[a]=q;this.start_end_dct[a]={}}var l=q.w_scale,w=[],x=[],j=0,n=this.view.max_low;for(var u=0,v=h.length;u<v;u++){var g=h[u],k=g[0];if(q[k]!==undefined){j=Math.max(j,q[k]);x.push(q[k])}else{w.push(u)}}var d=this.start_end_dct[a];var m=function(D,E){for(var C=0;C<=MAX_FEATURE_DEPTH;C++){var A=false,F=d[C];if(F!==undefined){for(var z=0,B=F.length;z<B;z++){var y=F[z];if(E>y[0]&&D<y[1]){A=true;break}}}if(!A){return C}}return -1};for(var u=0,v=w.length;u<v;u++){var g=h[w[u]],k=g[0],s=g[1],b=g[2],o=g[3],c=Math.floor((s-n)*l),f=Math.ceil((b-n)*l),t=CONTEXT.measureText(o).width,e;if(o!==undefined&&p==="Pack"){t+=(LABEL_SPACING+PACK_SPACING);if(c-t>=0){c-=t;e="left"}else{f+=t;e="right"}}var r=m(c,f);if(r>=0){if(d[r]===undefined){d[r]=[]}d[r].push([c,f]);q[k]=r;j=Math.max(j,r)}else{}}return j+1},draw_summary_tree:function(b,o,n,l,r,j,f,e){var c=j+LABEL_SPACING+CHAR_HEIGHT_PX;delta_x_px=Math.ceil(n*r);var h=$("<div />").addClass("yaxislabel");h.text(l);h.css({position:"absolute",top:"22px",left:"10px"});h.prependTo(this.container_div);var q=b.get(0).getContext("2d");for(var d=0,g=o.length;d<g;d++){var m=Math.floor((o[d][0]-f)*r);var k=o[d][1];if(!k){continue}var p=k/l*j;q.fillStyle="black";q.fillRect(m+e,c-p,delta_x_px,p);var a=4;if(this.prefs.show_counts&&(q.measureText(k).width+a)<delta_x_px){q.fillStyle="#666";q.textAlign="center";q.fillText(k,m+e+(delta_x_px/2),10)}}},draw_element:function(p,f,g,x,j,r,I,M,N,a,A){var u=x[0],K=x[1],C=x[2]-1,s=x[3],D=Math.floor(Math.max(0,(K-r)*M)),q=Math.ceil(Math.min(a,Math.max(0,(C-r)*M))),B=ERROR_PADDING+(g==="Dense"?0:(0+j))*N,o,G,t=null,O=null,d=this.prefs.block_color,F=this.prefs.label_color;if(g==="Dense"){p.fillStyle=d;p.fillRect(D+A,B,q-D,DENSE_FEATURE_HEIGHT)}else{if(g==="no_detail"){p.fillStyle=d;p.fillRect(D+A,B+5,q-D,DENSE_FEATURE_HEIGHT)}else{var n=x[4],z=x[5],E=x[6],e=x[7];if(z&&E){t=Math.floor(Math.max(0,(z-r)*M));O=Math.ceil(Math.min(a,Math.max(0,(E-r)*M)))}var L,v;if(g==="Squish"){L=1;v=SQUISH_FEATURE_HEIGHT}else{L=5;v=PACK_FEATURE_HEIGHT}if(!e){if(x.strand){if(x.strand==="+"){p.fillStyle=RIGHT_STRAND_INV}else{if(x.strand==="-"){p.fillStyle=LEFT_STRAND_INV}}}else{p.fillStyle=d}p.fillRect(D+A,B,q-D,v)}else{var m,w;if(g==="Squish"){p.fillStyle=CONNECTOR_COLOR;m=B+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;w=1}else{if(n){var m=B;var w=v;if(n==="+"){p.fillStyle=RIGHT_STRAND}else{if(n==="-"){p.fillStyle=LEFT_STRAND}}}else{p.fillStyle=CONNECTOR_COLOR;m+=(SQUISH_FEATURE_HEIGHT/2)+1;w=1}}p.fillRect(D+A,m,q-D,w);for(var J=0,c=e.length;J<c;J++){var h=e[J],b=Math.floor(Math.max(0,(h[0]-r)*M)),y=Math.ceil(Math.min(a,Math.max((h[1]-1-r)*M)));if(b>y){continue}p.fillStyle=d;p.fillRect(b+A,B+(v-L)/2+1,y-b,L);if(t!==undefined&&!(b>O||y<t)){var H=Math.max(b,t),l=Math.min(y,O);p.fillRect(H+A,B+1,l-H,v)}}}if(g==="Pack"&&K>r){p.fillStyle=F;if(f===0&&D-p.measureText(s).width<0){p.textAlign="left";p.fillText(s,q+A+LABEL_SPACING,B+8)}else{p.textAlign="right";p.fillText(s,D+A-LABEL_SPACING,B+8)}p.fillStyle=d}}}},get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="no_detail"){a=NO_DETAIL_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT}}}return a},draw_tile:function(o,x,C,k,m,d){var t=this;var E=C*DENSITY*x,a=(C+1)*DENSITY*x,q=a-E,F={hda_ldda:t.hda_ldda,dataset_id:t.dataset_id,resolution:this.view.resolution,mode:this.mode};var v=Math.ceil(q*m),s=this.mode,H=25,g=this.left_offset,p,h;var c=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(c)}c=$(c);if(s==="Auto"){if(o.dataset_type==="summary_tree"){s=o.dataset_type}else{if(o.extra_info==="no_detail"){s="no_detail"}else{var G=o.data;if((G.length&&G.length<4)||(this.view.high-this.view.low>MIN_SQUISH_VIEW_WIDTH)){s="Squish"}else{s="Pack"}}}}var y=this.get_y_scale(s);if(s==="summary_tree"){h=this.summary_draw_height}if(s==="Dense"){h=H}else{if(s==="no_detail"||s==="Squish"||s==="Pack"){h=this.incremental_slots(m,o.data,s)*y+H;p=this.inc_slots[m]}}c.get(0).width=v+g;c.get(0).height=h;if(o.dataset_type==="summary_tree"){c.get(0).height+=LABEL_SPACING+CHAR_HEIGHT_PX}k.parent().css("height",Math.max(this.height_px,h)+"px");var w=c.get(0).getContext("2d");w.fillStyle=this.prefs.block_color;w.font=this.default_font;w.textAlign="right";this.container_div.find(".yaxislabel").remove();if(s==="summary_tree"){this.draw_summary_tree(c,o.data,o.delta,o.max,m,h,E,g);return c}if(o.message){c.css({"border-top":"1px solid red"});w.fillStyle="red";w.textAlign="left";var r=w.textBaseline;w.textBaseline="top";w.fillText(o.message,g,0);w.textBaseline=r;if(!o.data){return c}}this.example_feature=(o.data.length?o.data[0]:undefined);var G=o.data;for(var z=0,B=G.length;z<B;z++){var j=G[z],l=j[0],u=j[1],b=j[2],e=(p&&p[l]!==undefined?p[l]:null);var A=false;var n;for(var D=0;D<this.filters.length;D++){n=this.filters[D];n.update_attrs(j);if(!n.keep(j)){A=true;break}}if(A){continue}if(is_overlap([u,b],[E,a])&&(s=="Dense"||e!==null)){this.draw_element(w,C,s,j,e,E,a,m,y,v,g,d)}}return c}});var VcfTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_type="VcfTrack"};$.extend(VcfTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{draw_element:function(u,p,j,e,x,c,m,v,s){var j=data[i],l=j[0],t=j[1],d=j[2]-1,o=j[3],g=Math.floor(Math.max(0,(t-x)*m)),k=Math.ceil(Math.min(s,Math.max(0,(d-x)*m))),f=ERROR_PADDING+(p==="Dense"?0:(0+e))*v,a,y,b=null,n=null;if(no_label){u.fillStyle=block_color;u.fillRect(g+left_offset,f+5,k-g,1)}else{var w=j[4],r=j[5],h=j[6];a=9;y=1;u.fillRect(g+left_offset,f,k-g,a);if(p!=="Dense"&&o!==undefined&&t>x){u.fillStyle=label_color;if(tile_index===0&&g-u.measureText(o).width<0){u.textAlign="left";u.fillText(o,k+2+left_offset,f+8)}else{u.textAlign="right";u.fillText(o,g-2+left_offset,f+8)}u.fillStyle=block_color}var q=w+" / "+r;if(t>x&&u.measureText(q).width<(k-g)){u.fillStyle="white";u.textAlign="center";u.fillText(q,left_offset+g+(k-g)/2,f+8);u.fillStyle=block_color}}}});var ReadTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:c,onchange:function(){this.track.tile_cache.clear();this.track.draw()}});this.prefs=this.track_config.values;this.track_type="ReadTrack";this.make_name_popup_menu()};$.extend(ReadTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT;if(this.track_config.values.show_insertions){a*=2}}}return a},draw_read:function(m,d,J,n,D,G,e,o,y,a){m.textAlign="center";var l=this,u=[n,D],C=0,z=0,f=0;var r=[];if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){f=Math.round(J/2)}if(!e){e=[[0,o.length]]}for(var h=0,k=e.length;h<k;h++){var b=e[h],g="MIDNSHP=X"[b[0]],v=b[1];if(g==="H"||g==="S"){C-=v}var x=G+C,B=Math.floor(Math.max(0,(x-n)*J)),E=Math.floor(Math.max(0,(x+v-n)*J));switch(g){case"H":break;case"S":case"M":case"=":var p=compute_overlap([x,x+v],u);if(p!==NO_OVERLAP){var q=o.slice(z,z+v);if(f>0){m.fillStyle=this.prefs.block_color;m.fillRect(B+this.left_offset-f,y+1,E-B,9);m.fillStyle=CONNECTOR_COLOR;for(var I=0,j=q.length;I<j;I++){if(this.track_config.values.show_differences&&a){var s=a[x-n+I];if(!s||s.toLowerCase()===q[I].toLowerCase()){continue}}if(x+I>=n&&x+I<=D){var A=Math.floor(Math.max(0,(x+I-n)*J));m.fillText(q[I],A+this.left_offset,y+9)}}}else{m.fillStyle=this.prefs.block_color;m.fillRect(B+this.left_offset,y+(this.mode!=="Dense"?4:5),E-B,(d!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}z+=v;C+=v;break;case"N":m.fillStyle=CONNECTOR_COLOR;m.fillRect(B+this.left_offset-f,y+5,E-B,1);C+=v;break;case"D":m.fillStyle="red";m.fillRect(B+this.left_offset-f,y+4,E-B,3);C+=v;break;case"P":break;case"I":var p=compute_overlap([x,x+v],u),K=this.left_offset+B-f;if(p!==NO_OVERLAP){var q=o.slice(z,z+v);if(this.track_config.values.show_insertions){var w=this.left_offset+B-(E-B)/2;if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){m.fillStyle="yellow";m.fillRect(w-f,y-9,E-B,9);r[r.length]={type:"triangle",data:[K,y+4,5]};m.fillStyle=CONNECTOR_COLOR;switch(p){case (OVERLAP_START):q=q.slice(n-x);break;case (OVERLAP_END):q=q.slice(0,x-D);break;case (CONTAINED_BY):break;case (CONTAINS):q=q.slice(n-x,x-D);break}for(var I=0,j=q.length;I<j;I++){var A=Math.floor(Math.max(0,(x+I-n)*J));m.fillText(q[I],A+this.left_offset-(E-B)/2,y)}}else{m.fillStyle="yellow";m.fillRect(w,y+(this.mode!=="Dense"?2:5),E-B,(d!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){r[r.length]={type:"text",data:[q.length,K,y+9]}}else{}}}z+=v;break;case"X":z+=v;break}}m.fillStyle="yellow";var t,L,H;for(var F=0;F<r.length;F++){t=r[F];L=t.type;H=t.data;if(L==="text"){m.font="bold "+DEFAULT_FONT;m.fillText(H[0],H[1],H[2]);m.font=DEFAULT_FONT}else{if(L=="triangle"){m.drawDownwardEquilateralTriangle(H[0],H[1],H[2])}}}},draw_element:function(w,y,r,k,e,A,b,n,x,u,f,c){var m=k[0],v=k[1],d=k[2],o=k[3],h=Math.floor(Math.max(0,(v-A)*n)),j=Math.ceil(Math.min(u,Math.max(0,(d-A)*n))),g=(r==="Dense"?1:(1+e))*x,z=this.prefs.block_color,l=this.prefs.label_color,t=0;if((r==="Pack"||this.mode==="Auto")&&n>CHAR_WIDTH_PX){var t=Math.round(n/2)}w.fillStyle=z;if(k[5] instanceof Array){var s=Math.floor(Math.max(0,(k[4][0]-A)*n)),q=Math.ceil(Math.min(u,Math.max(0,(k[4][1]-A)*n))),p=Math.floor(Math.max(0,(k[5][0]-A)*n)),a=Math.ceil(Math.min(u,Math.max(0,(k[5][1]-A)*n)));if(k[4][1]>=A&&k[4][0]<=b&&k[4][2]){this.draw_read(w,r,n,A,b,k[4][0],k[4][2],k[4][3],g,c)}if(k[5][1]>=A&&k[5][0]<=b&&k[5][2]){this.draw_read(w,r,n,A,b,k[5][0],k[5][2],k[5][3],g,c)}if(p>q){w.fillStyle=CONNECTOR_COLOR;w.dashedLine(q+f-t,g+5,f+p-t,g+5)}}else{w.fillStyle=z;this.draw_read(w,r,n,A,b,v,k[4],k[5],g,c)}if(r==="Pack"&&v>A){w.fillStyle=this.prefs.label_color;if(y===0&&h-w.measureText(o).width<0){w.textAlign="left";w.fillText(o,j+f+LABEL_SPACING-t,g+8)}else{w.textAlign="right";w.fillText(o,h+f-LABEL_SPACING-t,g+8)}w.fillStyle=z}}});var ToolDataFeatureTrack=function(e,c,g,a,d,f,b){FeatureTrack.call(this,e,c,g,a,d,f,{},b);this.track_type="ToolDataFeatureTrack";this.data_url=raw_data_url;this.data_query_wait=1000;this.dataset_check_url=dataset_state_url};$.extend(ToolDataFeatureTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{predraw_init:function(){var b=this;var a=function(){if(b.data_cache.size()===0){setTimeout(a,300)}else{b.data_url=default_data_url;b.data_query_wait=DEFAULT_DATA_QUERY_WAIT;b.dataset_state_url=converted_datasets_state_url;$.getJSON(b.dataset_state_url,{dataset_id:b.dataset_id,hda_ldda:b.hda_ldda},function(c){})}};a()}});
\ No newline at end of file
+CanvasRenderingContext2D.prototype.dashedLine=function(c,j,b,h,f){if(f===undefined){f=4}var e=b-c;var d=h-j;var g=Math.floor(Math.sqrt(e*e+d*d)/f);var l=e/g;var k=d/g;var a;for(a=0;a<g;a++,c+=l,j+=k){if(a%2!==0){continue}this.fillRect(c,j,f,1)}};CanvasRenderingContext2D.prototype.drawDownwardEquilateralTriangle=function(b,a,e){var d=b-e/2,c=b+e/2,f=a-Math.sqrt(e*3/2);this.beginPath();this.moveTo(d,f);this.lineTo(c,f);this.lineTo(b,a);this.lineTo(d,f);this.strokeStyle=this.fillStyle;this.fill();this.stroke();this.closePath()};function sortable(a,b){a.bind("drag",{handle:b,relative:true},function(h,j){var g=$(this).parent();var f=g.children();var c;for(c=0;c<f.length;c++){if(j.offsetY<$(f.get(c)).position().top){break}}if(c===f.length){if(this!==f.get(c-1)){g.append(this)}}else{if(this!==f.get(c)){$(this).insertBefore(f.get(c))}}})}var NO_OVERLAP=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005;function compute_overlap(e,b){var g=e[0],f=e[1],d=b[0],c=b[1],a;if(g<d){if(f<d){a=NO_OVERLAP}else{if(f<=c){a=OVERLAP_START}else{a=CONTAINS}}}else{if(g>c){a=NO_OVERLAP}else{if(f<=c){a=CONTAINED_BY}else{a=OVERLAP_END}}}return a}function is_overlap(b,a){return(compute_overlap(b,a)!==NO_OVERLAP)}function get_slider_step(c,a){var b=a-c;return(b<=1?0.01:(b<=1000?1:5))}var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=1,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,ERROR_PADDING=10,LABEL_SPACING=2,PACK_SPACING=5,MIN_SQUISH_VIEW_WIDTH=12000,DEFAULT_FONT="9px Monaco, Lucida Console, monospace",DENSITY=200,FEATURE_LEVELS=10,MAX_FEATURE_DEPTH=100,DEFAULT_DATA_QUERY_WAIT=5000,MAX_CHROMS_SELECTABLE=100,CONNECTOR_COLOR="#ccc",DATA_ERROR="There was an error in indexing this dataset. ",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_CANNOT_RUN_TOOL="Tool cannot be rerun: ",DATA_LOADING="Loading data...",DATA_OK="Ready for display",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=5,CACHED_DATA=5,DUMMY_CANVAS=document.createElement("canvas"),RIGHT_STRAND,LEFT_STRAND;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(DUMMY_CANVAS)}var CONTEXT=DUMMY_CANVAS.getContext("2d");CONTEXT.font=DEFAULT_FONT;var CHAR_WIDTH_PX=CONTEXT.measureText("A").width,CHAR_HEIGHT_PX=9;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};function round_1000(a){return Math.round(a*1000)/1000}var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!==-1){this.move_key_to_end(b,a)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},move_key_to_end:function(b,a){this.key_ary.splice(a,1);this.key_ary.push(b)},clear:function(){this.obj_cache={};this.key_ary=[]},size:function(){return this.key_ary.length}});var DataManager=function(b,a,c){Cache.call(this,b);this.track=a;this.subset=(c!==undefined?c:true)};$.extend(DataManager.prototype,Cache.prototype,{load_data:function(h,j,d,g,a,f){var c={chrom:h,low:j,high:d,mode:g,resolution:a,dataset_id:this.track.dataset_id,hda_ldda:this.track.hda_ldda};$.extend(c,f);var k=[];for(var e=0;e<this.track.filters.length;e++){k[k.length]=this.track.filters[e].name}c.filter_cols=JSON.stringify(k);var b=this;return $.getJSON(this.track.data_url,c,function(l){b.set_data(j,d,g,l)})},get_data:function(j,k,c,h,b,f){var l=this.get(this.gen_key(k,c,h));if(l){return l}if(this.subset){var m,g,a,e,h,l;for(var d=0;d<this.key_ary.length;d++){m=this.key_ary[d];g=this.split_key(m);a=g[0];e=g[1];if(k>=a&&c<=e){l=this.obj_cache[m];if(l.dataset_type!=="summary_tree"&&l.extra_info!=="no_detail"){this.move_key_to_end(m,d);return l}}}}return this.load_data(j,k,c,h,b,f)},set_data:function(b,c,d,a){return this.set(this.gen_key(b,c,d),a)},gen_key:function(a,c,d){var b=a+"_"+c+"_"+d;return b},split_key:function(a){return a.split("_")}});var View=function(a,d,c,b,e){this.container=a;this.chrom=null;this.vis_id=c;this.dbkey=b;this.title=d;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.num_tracks=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init(e);this.canvas_manager=new CanvasManager(a.get(0).ownerDocument);this.reset()};$.extend(View.prototype,{init:function(d){var c=this.container,a=this;this.top_container=$("<div/>").addClass("top-container").appendTo(c);this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(c);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(c);this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("<div/>").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.intro_div=$("<div/>").addClass("intro").text("Select a chrom from the dropdown below").hide();this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("<div/>").addClass("nav-container").prependTo(this.top_container);this.nav=$("<div/>").addClass("nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("<a href='javascript:void(0);'>Close Overview</a>").addClass("overview-close").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var b=function(f){if(f.type==="focusout"||(f.keyCode||f.which)===13||(f.keyCode||f.which)===27){if((f.keyCode||f.which)!==27){a.go_to($(this).val())}$(this).hide();$(this).val("");a.location_span.show();a.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",b).appendTo(this.nav_controls);this.location_span=$("<span/>").addClass("location").appendTo(this.nav_controls);this.location_span.bind("click",function(){a.location_span.hide();a.chrom_select.hide();a.nav_input.val(a.chrom+":"+a.low+"-"+a.high);a.nav_input.css("display","inline-block");a.nav_input.select();a.nav_input.focus()});if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("<a id='zoom-out' />").click(function(){a.zoom_out();a.redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a id='zoom-in' />").click(function(){a.zoom_in();a.redraw()}).appendTo(this.nav_controls);this.load_chroms({low:0},d);this.chrom_select.bind("change",function(){a.change_chrom(a.chrom_select.val())});this.intro_div.show();this.content_div.bind("click",function(f){$(this).find("input").trigger("blur")});this.content_div.bind("dblclick",function(f){a.zoom_in(f.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(f,g){this.current_x=g.offsetX}).bind("drag",function(f,h){var j=h.offsetX-this.current_x;this.current_x=h.offsetX;var g=Math.round(j/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-g)});this.overview_close.bind("click",function(){for(var f=0,e=a.tracks.length;f<e;f++){a.tracks[f].is_overview=false}$(this).siblings().filter("canvas").remove();$(this).parent().css("height",a.overview_box.height());a.overview_highlight.hide();$(this).hide()});this.viewport_container.bind("draginit",function(f,g){if(f.clientX>a.viewport_container.width()-16){return false}}).bind("dragstart",function(f,g){g.original_low=a.low;g.current_height=f.clientY;g.current_x=g.offsetX}).bind("drag",function(h,k){var f=$(this);var l=k.offsetX-k.current_x;var g=f.scrollTop()-(h.clientY-k.current_height);f.scrollTop(g);k.current_height=h.clientY;k.current_x=k.offsetX;var j=Math.round(l/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}).bind("mousewheel",function(h,k,g,f){if(g){var j=Math.round(-g/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}});this.top_labeltrack.bind("dragstart",function(f,g){return $("<div />").css({height:a.content_div.height()+a.top_labeltrack.height()+a.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(k,l){$(l.proxy).css({left:Math.min(k.pageX,l.startX),width:Math.abs(k.pageX-l.startX)});var g=Math.min(k.pageX,l.startX)-a.container.offset().left,f=Math.max(k.pageX,l.startX)-a.container.offset().left,j=(a.high-a.low),h=a.viewport_container.width();a.update_location(Math.round(g/h*j)+a.low,Math.round(f/h*j)+a.low)}).bind("dragend",function(l,m){var g=Math.min(l.pageX,m.startX),f=Math.max(l.pageX,m.startX),j=(a.high-a.low),h=a.viewport_container.width(),k=a.low;a.low=Math.round(g/h*j)+k;a.high=Math.round(f/h*j)+k;$(m.proxy).remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack));$(window).bind("resize",function(){a.resize_window()});$(document).bind("redraw",function(){a.redraw()});this.reset();$(window).trigger("resize")},update_location:function(a,b){this.location_span.text(commatize(a)+" - "+commatize(b));this.nav_input.val(this.chrom+":"+commatize(a)+"-"+commatize(b))},load_chroms:function(b,c){b.num=MAX_CHROMS_SELECTABLE;$.extend(b,(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}));var a=this;$.ajax({url:chrom_url,data:b,dataType:"json",success:function(e){if(e.chrom_info.length===0){alert("Invalid chromosome: "+b.chrom);return}if(e.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=e.chrom_info;var h='<option value="">Select Chrom/Contig</option>';for(var g=0,d=a.chrom_data.length;g<d;g++){var f=a.chrom_data[g].chrom;h+='<option value="'+f+'">'+f+"</option>"}if(e.prev_chroms){h+='<option value="previous">Previous '+MAX_CHROMS_SELECTABLE+"</option>"}if(e.next_chroms){h+='<option value="next">Next '+MAX_CHROMS_SELECTABLE+"</option>"}a.chrom_select.html(h);if(c){c()}a.chrom_start_index=e.start_index},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}})},change_chrom:function(e,b,g){if(!e||e==="None"){return}var d=this;if(e==="previous"){d.load_chroms({low:this.chrom_start_index-MAX_CHROMS_SELECTABLE});return}if(e==="next"){d.load_chroms({low:this.chrom_start_index+MAX_CHROMS_SELECTABLE});return}var f=$.grep(d.chrom_data,function(j,k){return j.chrom===e})[0];if(f===undefined){d.load_chroms({chrom:e},function(){d.change_chrom(e,b,g)});return}else{if(e!==d.chrom){d.chrom=e;if(!d.chrom){d.intro_div.show()}else{d.intro_div.hide()}d.chrom_select.val(d.chrom);d.max_high=f.len-1;d.reset();d.redraw(true);for(var h=0,a=d.tracks.length;h<a;h++){var c=d.tracks[h];if(c.init){c.init()}}}if(b!==undefined&&g!==undefined){d.low=Math.max(b,0);d.high=Math.min(g,d.max_high)}d.reset_overview();d.redraw()}},go_to:function(f){var k=this,a,d,b=f.split(":"),h=b[0],j=b[1];if(j!==undefined){try{var g=j.split("-");a=parseInt(g[0].replace(/,/g,""),10);d=parseInt(g[1].replace(/,/g,""),10)}catch(c){return false}}k.change_chrom(h,a,d)},move_fraction:function(c){var a=this;var b=a.high-a.low;this.move_delta(c*b)},move_delta:function(c){var a=this;var b=a.high-a.low;if(a.low-c<a.max_low){a.low=a.max_low;a.high=a.max_low+b}else{if(a.high-c>a.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_track:function(a){a.view=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);sortable(a.container_div,".draghandle");this.track_id_counter+=1;this.num_tracks+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)];this.num_tracks-=1},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(h){var g=this.high-this.low,f=this.low,b=this.high;if(f<this.max_low){f=this.max_low}if(b>this.max_high){b=this.max_high}if(this.high!==0&&g<this.min_separation){b=f+this.min_separation}this.low=Math.floor(f);this.high=Math.ceil(b);this.resolution=Math.pow(10,Math.ceil(Math.log((this.high-this.low)/200)/Math.LN10));this.zoom_res=Math.pow(FEATURE_LEVELS,Math.max(0,Math.ceil(Math.log(this.resolution,FEATURE_LEVELS)/Math.log(FEATURE_LEVELS))));var a=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var e=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var j=13;this.overview_box.css({left:a,width:Math.max(j,e)}).show();if(e<j){this.overview_box.css("left",a-(j-e)/2)}if(this.overview_highlight){this.overview_highlight.css({left:a,width:e})}this.update_location(this.low,this.high);if(!h){for(var c=0,d=this.tracks.length;c<d;c++){if(this.tracks[c]&&this.tracks[c].enabled){this.tracks[c].draw()}}for(c=0,d=this.label_tracks.length;c<d;c++){this.label_tracks[c].draw()}}},zoom_in:function(b,c){if(this.max_high===0||this.high-this.low<this.min_separation){return}var d=this.high-this.low,e=d/2+this.low,a=(d/this.zoom_factor)/2;if(b){e=b/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(e-a);this.high=Math.round(e+a);this.redraw()},zoom_out:function(){if(this.max_high===0){return}var b=this.high-this.low,c=b/2+this.low,a=(b*this.zoom_factor)/2;this.low=Math.round(c-a);this.high=Math.round(c+a);this.redraw()},resize_window:function(){this.viewport_container.height(this.container.height()-this.top_container.height()-this.bottom_container.height());this.nav_container.width(this.container.width());this.redraw()},reset_overview:function(){this.overview_viewport.find("canvas").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide()}});var Tool=function(g){this.name=g.name;this.params=[];var a=g.params;for(var e=0;e<a.length;e++){var d=a[e],c=d.name,b=d.label,f=d.type;if(f==="int"||f==="float"){this.params[this.params.length]=new NumberParameter(c,b,d.min,d.max,d.value)}else{if(f=="select"){this.params[this.params.length]=new SelectParameter(c,b,d.options)}else{console.log("WARNING: unrecognized tool parameter type:",c,f)}}}};$.extend(Tool.prototype,{get_param_values_dict:function(){var b={};for(var a=0;a<this.params.length;a++){var c=this.params[a];b[c.name]=JSON.stringify(c.value)}return b},get_param_values:function(){var b=[];for(var a=0;a<this.params.length;a++){b[a]=this.params[a].value}return b}});var NumberParameter=function(c,b,e,a,d){this.name=c;this.label=b;this.min=e;this.max=a;this.value=d};var SelectParameter=function(c,b,a){this.name=c;this.label=b;this.options=a;this.value=(a.length!==0?a[0][1]:null)};var Filter=function(b,a,c){this.name=b;this.index=a;this.value=c};var NumberFilter=function(b,a){this.name=b;this.index=a;this.low=-Number.MAX_VALUE;this.high=Number.MAX_VALUE;this.min=Number.MAX_VALUE;this.max=-Number.MAX_VALUE;this.slider=null;this.slider_label=null};$.extend(NumberFilter.prototype,{applies_to:function(a){if(a.length>this.index){return true}return false},keep:function(a){if(!this.applies_to(a)){return true}return(a[this.index]>=this.low&&a[this.index]<=this.high)},update_attrs:function(b){var a=false;if(!this.applies_to(b)){return a}if(b[this.index]<this.min){this.min=Math.floor(b[this.index]);a=true}if(b[this.index]>this.max){this.max=Math.ceil(b[this.index]);a=true}return a},update_ui_elt:function(){var b=this.slider.slider("option","min"),a=this.slider.slider("option","max");if(this.min<b||this.max>a){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var get_filters_from_dict=function(a){var g=[];for(var d=0;d<a.length;d++){var f=a[d];var c=f.name,e=f.type,b=f.index;if(e==="int"||e==="float"){g[d]=new NumberFilter(c,b)}else{g[d]=new Filter(c,b,e)}}return g};var TrackConfig=function(a){this.track=a.track;this.params=a.params;this.values={};if(a.saved_values){this.restore_values(a.saved_values)}this.onchange=a.onchange};$.extend(TrackConfig.prototype,{restore_values:function(a){var b=this;$.each(this.params,function(c,d){if(a[d.key]!==undefined){b.values[d.key]=a[d.key]}else{b.values[d.key]=d.default_value}})},build_form:function(){var b=this;var a=$("<div />");$.each(this.params,function(f,d){if(!d.hidden){var c="param_"+f;var l=$("<div class='form-row' />").appendTo(a);l.append($("<label />").attr("for",c).text(d.label+":"));if(d.type==="bool"){l.append($('<input type="checkbox" />').attr("id",c).attr("name",c).attr("checked",b.values[d.key]))}else{if(d.type==="color"){var h=b.values[d.key];var g=$("<input />").attr("id",c).attr("name",c).val(h);var j=$("<div class='tipsy tipsy-north' style='position: absolute;' />").hide();var e=$("<div style='background-color: black; padding: 10px;'></div>").appendTo(j);var k=$("<div/>").appendTo(e).farbtastic({width:100,height:100,callback:g,color:h});$("<div />").append(g).append(j).appendTo(l).bind("click",function(m){j.css({left:$(this).position().left+($(g).width()/2)-60,top:$(this).position().top+$(this.height)}).show();$(document).bind("click.color-picker",function(){j.hide();$(document).unbind("click.color-picker")});m.stopPropagation()})}else{l.append($("<input />").attr("id",c).attr("name",c).val(b.values[d.key]))}}}});return a},update_from_form:function(a){var c=this;var b=false;$.each(this.params,function(d,f){if(!f.hidden){var g="param_"+d;var e=a.find("#"+g).val();if(f.type==="float"){e=parseFloat(e)}else{if(f.type==="int"){e=parseInt(e)}else{if(f.type==="bool"){e=a.find("#"+g).is(":checked")}}}if(e!==c.values[f.key]){c.values[f.key]=e;b=true}}});if(b){this.onchange()}}});var Tile=function(a,b,c){this.track=a;this.canvas=b;this.histo_max=c};var Track=function(b,a,e,c,d){this.name=b;this.view=a;this.parent_element=e;this.data_url=(c?c:default_data_url);this.data_url_extra_params={};this.data_query_wait=(d?d:DEFAULT_DATA_QUERY_WAIT);this.dataset_check_url=converted_datasets_state_url;this.container_div=$("<div />").addClass("track").css("position","relative");if(!this.hidden){this.header_div=$("<div class='track-header' />").appendTo(this.container_div);if(this.view.editor){this.drag_div=$("<div class='draghandle' />").appendTo(this.header_div)}this.name_div=$("<div class='menubutton popup' />").appendTo(this.header_div);this.name_div.text(this.name);this.name_div.attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase())}this.content_div=$("<div class='track-content'>").appendTo(this.container_div);this.parent_element.append(this.container_div)};$.extend(Track.prototype,{init:function(){var a=this;a.enabled=false;a.tile_cache.clear();a.data_cache.clear();a.initial_canvas=undefined;a.content_div.css("height","auto");a.container_div.removeClass("nodata error pending");if(!a.dataset_id){return}$.getJSON(converted_datasets_state_url,{hda_ldda:a.hda_ldda,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){if(!b||b==="error"||b.kind==="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR);if(b.message){var d=a.view.tracks.indexOf(a);var c=$(" <a href='javascript:void(0);'></a>").text("View error").bind("click",function(){show_modal("Trackster Error","<pre>"+b.message+"</pre>",{Close:hide_modal})});a.content_div.append(c)}}else{if(b==="no converter"){a.container_div.addClass("error");a.content_div.text(DATA_NOCONVERTER)}else{if(b==="no data"||(b.data!==undefined&&(b.data===null||b.data.length===0))){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(b==="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},a.data_query_wait)}else{if(b.status==="data"){if(b.valid_chroms){a.valid_chroms=b.valid_chroms;a.make_name_popup_menu()}a.content_div.text(DATA_OK);if(a.view.chrom){a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.enabled=true;$.when(a.predraw_init()).done(function(){a.draw()})}}}}}}})},predraw_init:function(){},update_name:function(a){this.old_name=this.name;this.name=a;this.name_div.text(this.name)},revert_name:function(){this.name=this.old_name;this.name_div.text(this.name)}});var TiledTrack=function(b,h,o){var c=this,p=c.view;this.filters=(b!==undefined?get_filters_from_dict(b):[]);this.filters_available=false;this.filters_visible=false;this.tool=(h!==undefined&&obj_length(h)>0?new Tool(h):undefined);this.parent_track=o;this.child_tracks=[];if(c.hidden){return}var k=function(r,s,t){r.click(function(){var u=s.text();max=parseFloat(t.slider("option","max")),input_size=(max<=1?4:max<=1000000?max.toString().length:6),multi_value=false;if(t.slider("option","values")){input_size=2*input_size+1;multi_value=true}s.text("");$("<input type='text'/>").attr("size",input_size).attr("maxlength",input_size).attr("value",u).appendTo(s).focus().select().click(function(v){v.stopPropagation()}).blur(function(){$(this).remove();s.text(u)}).keyup(function(z){if(z.keyCode===27){$(this).trigger("blur")}else{if(z.keyCode===13){var x=t.slider("option","min"),v=t.slider("option","max"),y=function(A){return(isNaN(A)||A>v||A<x)},w=$(this).val();if(!multi_value){w=parseFloat(w);if(y(w)){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}else{w=w.split("-");w=[parseFloat(w[0]),parseFloat(w[1])];if(y(w[0])||y(w[1])){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}t.slider((multi_value?"values":"value"),w)}}})})};if(this.parent_track){this.header_div.find(".draghandle").removeClass("draghandle").addClass("child-track-icon").addClass("icon-button");this.parent_element.addClass("child-track");this.tool=undefined}this.filters_div=$("<div/>").addClass("filters").hide();this.header_div.after(this.filters_div);this.filters_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});$.each(this.filters,function(x,s){var u=$("<div/>").addClass("slider-row").appendTo(c.filters_div);var r=$("<div/>").addClass("slider-label").appendTo(u);var z=$("<span/>").addClass("slider-name").text(s.name+" ").appendTo(r);var t=$("<span/>");var v=$("<span/>").addClass("slider-value").appendTo(r).append("[").append(t).append("]");var y=$("<div/>").addClass("slider").appendTo(u);s.control_element=$("<div/>").attr("id",s.name+"-filter-control").appendTo(y);var w=[0,0];s.control_element.slider({range:true,min:Number.MAX_VALUE,max:-Number.MIN_VALUE,values:[0,0],slide:function(A,B){w=B.values;t.text(B.values[0]+"-"+B.values[1]);setTimeout(function(){if(B.values[0]==w[0]&&B.values[1]==w[1]){var C=B.values;t.text(C[0]+"-"+C[1]);s.low=C[0];s.high=C[1];c.draw(true,true)}},50)},change:function(A,B){s.control_element.slider("option","slide").call(s.control_element,A,B)}});s.slider=s.control_element;s.slider_label=t;k(v,t,s.control_element);$("<div style='clear: both;'/>").appendTo(u)});if(this.tool){this.dynamic_tool_div=$("<div/>").addClass("dynamic-tool").hide();this.header_div.after(this.dynamic_tool_div);this.dynamic_tool_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});var n=$("<div class='tool-name'>").appendTo(this.dynamic_tool_div).text(this.tool.name);var m=this.tool.params;var c=this;$.each(this.tool.params,function(A,s){if(s instanceof NumberParameter){var x=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(x);var C=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var t=$("<span/>").text(s.value);var y=$("<span/>").addClass("slider-value").appendTo(u).append("[").append(t).append("]");var B=$("<div/>").addClass("slider").appendTo(x);var r=$("<div id='"+s.name+"-param-control'>").appendTo(B);r.slider({min:s.min,max:s.max,step:get_slider_step(s.min,s.max),value:s.value,slide:function(F,H){var G=H.value;s.value=G;if(0<G&&G<1){G=parseFloat(G).toFixed(2)}t.text(G)},change:function(F,G){r.slider("option","slide").call(r,F,G)}});k(y,t,r);$("<div style='clear: both;'/>").appendTo(x)}else{if(s instanceof SelectParameter){var x=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(x);var C=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var E=$("<div/>").addClass("slider").appendTo(x);var w=$("<select/>").appendTo(E);w.change(function(){s.value=$(this).val()});var D=w.attr("options");for(var z=0;z<s.options.length;z++){var v=s.options[z];D[D.length]=new Option(v[0],v[1])}$("<div style='clear: both;'/>").appendTo(x)}}});var q=$("<div>").addClass("slider-row").appendTo(this.dynamic_tool_div);var l=$("<input type='submit'>").attr("value","Run").appendTo(q);var c=this;l.click(function(){c.run_tool()})}c.child_tracks_container=$("<div/>").addClass("child-tracks-container").hide();c.container_div.append(c.child_tracks_container);if(c.display_modes!==undefined){if(c.mode_div===undefined){c.mode_div=$("<div class='right-float menubutton popup' />").appendTo(c.header_div);var e=(c.track_config&&c.track_config.values.mode?c.track_config.values.mode:c.display_modes[0]);c.mode=e;c.mode_div.text(e);var d=function(r){c.mode_div.text(r);c.mode=r;c.track_config.values.mode=r;c.tile_cache.clear();c.draw()};var a={};for(var f=0,j=c.display_modes.length;f<j;f++){var g=c.display_modes[f];a[g]=function(r){return function(){d(r)}}(g)}make_popupmenu(c.mode_div,a)}else{c.mode_div.hide()}}this.make_name_popup_menu()};$.extend(TiledTrack.prototype,Track.prototype,{make_name_popup_menu:function(){var b=this;var a={};a["Edit configuration"]=function(){var h=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},f=function(){b.track_config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},g=function(j){if((j.keyCode||j.which)===27){h()}else{if((j.keyCode||j.which)===13){f()}}};$(window).bind("keypress.check_enter_esc",g);show_modal("Configure Track",b.track_config.build_form(),{Cancel:h,OK:f})};if(b.filters_available>0){var e=(b.filters_div.is(":visible")?"Hide filters":"Show filters");a[e]=function(){b.filters_visible=(b.filters_div.is(":visible"));b.filters_div.toggle();b.make_name_popup_menu()}}if(b.tool){var e=(b.dynamic_tool_div.is(":visible")?"Hide tool":"Show tool");a[e]=function(){if(!b.dynamic_tool_div.is(":visible")){b.update_name(b.name+b.tool_region_and_parameters_str())}else{menu_option_text="Show dynamic tool";b.revert_name()}b.dynamic_tool_div.toggle();b.make_name_popup_menu()}}if(b.valid_chroms){a["List chrom/contigs with data"]=function(){show_modal("Chrom/contigs with data","<p>"+b.valid_chroms.join("<br/>")+"</p>",{Close:function(){hide_modal()}})}}var c=view;var d=function(){$("#no-tracks").show()};if(this.parent_track){c=this.parent_track;d=function(){}}a.Remove=function(){c.remove_track(b);if(c.num_tracks===0){d()}};make_popupmenu(b.name_div,a)},draw:function(a,d){var s=this.view.low,g=this.view.high,j=g-s,l=this.view.container.width(),f=l/j,m=this.view.resolution,e=$("<div style='position: relative;'></div>");if(!d){this.content_div.children().remove()}this.content_div.append(e);this.max_height=0;var o=Math.floor(s/m/DENSITY);var c={};while((o*DENSITY*m)<g){var r=l+"_"+f+"_"+o;var h=this.tile_cache.get(r);var p=o*DENSITY*this.view.resolution;var b=p+DENSITY*this.view.resolution;if(!a&&h){this.show_tile(h,e,p,f)}else{this.delayed_draw(a,r,p,b,o,m,e,f,c)}o+=1}var k=this;var q=setInterval(function(){if(obj_length(c)===0){clearInterval(q);if(d){var v=k.content_div.children();var u=false;for(var w=v.length-1,t=0;w>=t;w--){var z=$(v[w]);if(u){z.remove()}else{if(z.children().length!==0){u=true}}}}for(var y=0;y<k.filters.length;y++){k.filters[y].update_ui_elt()}var x=false;if(k.example_feature){for(var y=0;y<k.filters.length;y++){if(k.filters[y].applies_to(k.example_feature)){x=true;break}}}if(k.filters_available!==x){k.filters_available=x;if(!k.filters_available){k.filters_div.hide()}k.make_name_popup_menu()}}},50);for(var n=0;n<this.child_tracks.length;n++){this.child_tracks[n].draw(a,d)}},delayed_draw:function(b,j,g,l,c,e,k,m,f){var d=this;var h=function(u,n,p,o,s,t,q){returned_tile=d.draw_tile(n,p,o,s,t,q);var r=$("<div class='track-tile'>").prepend(returned_tile);tile_element=r;d.tile_cache.set(j,tile_element);d.show_tile(tile_element,s,g,t);delete f[u]};var a=setTimeout(function(){if(g<=d.view.high&&l>=d.view.low){var n=(b?undefined:d.tile_cache.get(j));if(n){d.show_tile(n,k,g,m);delete f[a]}else{$.when(d.data_cache.get_data(view.chrom,g,l,d.mode,e,d.data_url_extra_params)).then(function(o){if(view.reference_track&&m>CHAR_WIDTH_PX){$.when(view.reference_track.data_cache.get_data(view.chrom,g,l,d.mode,e,view.reference_track.data_url_extra_params)).then(function(p){h(a,o,e,c,k,m,p)})}else{h(a,o,e,c,k,m)}})}}},50);f[a]=true},show_tile:function(a,f,d,g){var b=this;var c=this.view.high-this.view.low,e=(d-this.view.low)*g;if(this.left_offset){e-=this.left_offset}a.css({position:"absolute",top:0,left:e,height:""});f.append(a);b.max_height=Math.max(b.max_height,a.height());b.content_div.css("height",b.max_height+"px");f.children().css("height",b.max_height+"px")},set_overview:function(){var a=this.view;if(this.initial_canvas&&this.is_overview){a.overview_close.show();a.overview_viewport.append(this.initial_canvas);a.overview_highlight.show().height(this.initial_canvas.height());a.overview_viewport.height(this.initial_canvas.height()+a.overview_box.height())}$(window).trigger("resize")},run_tool:function(){var b={dataset_id:this.original_dataset_id,chrom:this.view.chrom,low:this.view.low,high:this.view.high,tool_id:this.tool.name};$.extend(b,this.tool.get_param_values_dict());var d=this,c=b.tool_id+d.tool_region_and_parameters_str(b.chrom,b.low,b.high),e;if(d.track_type==="FeatureTrack"){e=new ToolDataFeatureTrack(c,view,d.hda_ldda,undefined,{},{},d)}this.add_track(e);e.content_div.text("Starting job.");view.has_changes=true;var a=function(){$.getJSON(run_tool_url,b,function(f){if(f==="no converter"){e.container_div.addClass("error");e.content_div.text(DATA_NOCONVERTER)}else{if(f.error){e.container_div.addClass("error");e.content_div.text(DATA_CANNOT_RUN_TOOL+f.message)}else{if(f==="pending"){e.container_div.addClass("pending");e.content_div.text("Converting input data so that it can be easily reused.");setTimeout(a,2000)}else{e.dataset_id=f.dataset_id;e.content_div.text("Running job.");e.init()}}}})};a()},tool_region_and_parameters_str:function(c,a,d){var b=this,e=(c!==undefined&&a!==undefined&&d!==undefined?c+":"+a+"-"+d:"all");return" - region=["+e+"], parameters=["+b.tool.get_param_values().join(", ")+"]"},add_track:function(a){a.track_id=this.track_id+"_"+this.child_tracks.length;a.container_div.attr("id","track_"+a.track_id);this.child_tracks_container.append(a.container_div);sortable(a.container_div,".child-track-icon");if(!$(this.child_tracks_container).is(":visible")){this.child_tracks_container.show()}this.child_tracks.push(a)},remove_track:function(a){a.container_div.fadeOut("slow",function(){$(this).remove()})}});var LabelTrack=function(a,b){this.track_type="LabelTrack";this.hidden=true;Track.call(this,null,a,b);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.view.container.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+commatize(a)+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var ReferenceTrack=function(a){this.track_type="ReferenceTrack";this.hidden=true;Track.call(this,null,a,a.top_labeltrack);TiledTrack.call(this);a.reference_track=this;this.left_offset=200;this.height_px=12;this.font=DEFAULT_FONT;this.container_div.addClass("reference-track");this.content_div.css("background","none");this.content_div.css("min-height","0px");this.content_div.css("border","none");this.data_url=reference_url;this.data_url_extra_params={dbkey:a.dbkey};this.data_cache=new DataManager(CACHED_DATA,this,false);this.tile_cache=new Cache(CACHED_TILES_LINE)};$.extend(ReferenceTrack.prototype,TiledTrack.prototype,{draw_tile:function(m,g,b,j,n){var f=this,d=DENSITY*g;if(n>CHAR_WIDTH_PX){if(m===null){f.content_div.css("height","0px");return}var e=this.view.canvas_manager.new_canvas();var l=e.getContext("2d");e.width=Math.ceil(d*n+f.left_offset);e.height=f.height_px;l.font=DEFAULT_FONT;l.textAlign="center";for(var h=0,k=m.length;h<k;h++){var a=Math.round(h*n);l.fillText(m[h],a+f.left_offset,10)}return e}this.content_div.css("height","0px")}});var LineTrack=function(e,c,f,a,d){var b=this;this.track_type="LineTrack";this.display_modes=["Histogram","Line","Filled","Intensity"];this.mode="Histogram";Track.call(this,e,c,c.viewport_container);TiledTrack.call(this);this.min_height_px=16;this.max_height_px=400;this.height_px=80;this.hda_ldda=f;this.dataset_id=a;this.original_dataset_id=a;this.data_cache=new DataManager(CACHED_DATA,this);this.tile_cache=new Cache(CACHED_TILES_LINE);this.track_config=new TrackConfig({track:this,params:[{key:"color",label:"Color",type:"color",default_value:"black"},{key:"min_value",label:"Min Value",type:"float",default_value:undefined},{key:"max_value",label:"Max Value",type:"float",default_value:undefined},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.height_px,hidden:true}],saved_values:d,onchange:function(){b.vertical_range=b.prefs.max_value-b.prefs.min_value;$("#linetrack_"+b.track_id+"_minval").text(b.prefs.min_value);$("#linetrack_"+b.track_id+"_maxval").text(b.prefs.max_value);b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;this.height_px=this.track_config.values.height;this.vertical_range=this.track_config.values.max_value-this.track_config.values.min_value;this.add_resize_handle()};$.extend(LineTrack.prototype,TiledTrack.prototype,{add_resize_handle:function(){var a=this;var d=false;var c=false;var b=$("<div class='track-resize'>");$(a.container_div).hover(function(){d=true;b.show()},function(){d=false;if(!c){b.hide()}});b.hide().bind("dragstart",function(f,g){c=true;g.original_height=$(a.content_div).height()}).bind("drag",function(g,h){var f=Math.min(Math.max(h.original_height+h.deltaY,a.min_height_px),a.max_height_px);$(a.content_div).css("height",f);a.height_px=f;a.draw(true)}).bind("dragend",function(f,g){a.tile_cache.clear();c=false;if(!d){b.hide()}a.track_config.values.height=a.height_px}).appendTo(a.container_div)},predraw_init:function(){var a=this,b=a.view.tracks.indexOf(a);a.vertical_range=undefined;return $.getJSON(a.data_url,{stats:true,chrom:a.view.chrom,low:null,high:null,hda_ldda:a.hda_ldda,dataset_id:a.dataset_id},function(c){a.container_div.addClass("line-track");var e=c.data;if(isNaN(parseFloat(a.prefs.min_value))||isNaN(parseFloat(a.prefs.max_value))){a.prefs.min_value=e.min;a.prefs.max_value=e.max;$("#track_"+b+"_minval").val(a.prefs.min_value);$("#track_"+b+"_maxval").val(a.prefs.max_value)}a.vertical_range=a.prefs.max_value-a.prefs.min_value;a.total_frequency=e.total_frequency;a.container_div.find(".yaxislabel").remove();var f=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_minval").text(round_1000(a.prefs.min_value));var d=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_maxval").text(round_1000(a.prefs.max_value));d.css({position:"absolute",top:"24px",left:"10px"});d.prependTo(a.container_div);f.css({position:"absolute",bottom:"2px",left:"10px"});f.prependTo(a.container_div)})},draw_tile:function(m,e,b,j,l){if(this.vertical_range===undefined){return}var f=b*DENSITY*e,d=DENSITY*e,a=Math.ceil(d*l),h=this.height_px;var c=this.view.canvas_manager.new_canvas();c.width=a,c.height=h;var k=c.getContext("2d");var g=new LinePainter(m.data,f,f+d,this.prefs.min_value,this.prefs.max_value,this.prefs.color,this.mode);g.draw(k,a,h);return c}});var FeatureTrack=function(a,f,e,j,h,c,d,g){var b=this;this.track_type="FeatureTrack";this.display_modes=["Auto","Dense","Squish","Pack"];this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:h,onchange:function(){b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;Track.call(this,a,f,f.viewport_container);TiledTrack.call(this,c,d,g);this.height_px=0;this.container_div.addClass("feature-track");this.hda_ldda=e;this.dataset_id=j;this.original_dataset_id=j;this.zo_slots={};this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.default_font=DEFAULT_FONT;this.inc_slots={};this.start_end_dct={};this.tile_cache=new Cache(CACHED_TILES_FEATURE);this.data_cache=new DataManager(20,this);this.left_offset=200;this.painter=LinkedFeaturePainter};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{incremental_slots:function(d,b,c){var a=this.inc_slots[d];if(!a||(a.mode!==c)){a=new FeatureSlotter(d,c==="Pack",function(e){return CONTEXT.measureText(e)});a.mode=c;this.inc_slots[d]=a}return a.slot_features(b)},get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="no_detail"){a=NO_DETAIL_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT}}}return a},draw_tile:function(o,w,A,k,m,d){var t=this,C=A*DENSITY*w,b=(A+1)*DENSITY*w,q=b-C,u=Math.ceil(q*m),s=this.mode,G=25,e=this.left_offset,p,g;if(s==="Auto"){if(o.dataset_type==="summary_tree"){s=o.dataset_type}else{if(o.extra_info==="no_detail"){s="no_detail"}else{var F=o.data;if((F.length&&F.length<4)||(this.view.high-this.view.low>MIN_SQUISH_VIEW_WIDTH)){s="Squish"}else{s="Pack"}}}}if(s==="summary_tree"){g=this.summary_draw_height;k.parent().css("height",Math.max(this.height_px,g)+"px");this.container_div.find(".yaxislabel").remove();var a=$("<div />").addClass("yaxislabel");a.text(o.max);a.css({position:"absolute",top:"22px",left:"10px"});a.prependTo(this.container_div);var c=this.view.canvas_manager.new_canvas();c.width=u+e;c.height=g+LABEL_SPACING+CHAR_HEIGHT_PX;var D=new SummaryTreePainter(o.data,o.delta,o.max,C,b,this.prefs.show_counts);var v=c.getContext("2d");v.translate(e,0);D.draw(v,u,g);return c}var p,j=1;if(s==="no_detail"||s==="Squish"||s==="Pack"){j=this.incremental_slots(m,o.data,s);p=this.inc_slots[m].slots}var l=[];if(o.data){for(var x=0,z=o.data.length;x<z;x++){var h=o.data[x];var y=false;var n;for(var B=0,E=this.filters.length;B<E;B++){n=this.filters[B];n.update_attrs(h);if(!n.keep(h)){y=true;break}}if(!y){l.push(h)}}}var D=new (this.painter)(l,C,b,this.prefs,s,d);var g=D.get_required_height(j)+ERROR_PADDING;var c=this.view.canvas_manager.new_canvas();c.width=u+e;c.height=g;k.parent().css("height",Math.max(this.height_px,g)+"px");var v=c.getContext("2d");v.fillStyle=this.prefs.block_color;v.font=this.default_font;v.textAlign="right";this.container_div.find(".yaxislabel").remove();if(o.message){$(c).css({"border-top":"1px solid red"});v.fillStyle="red";v.textAlign="left";var r=v.textBaseline;v.textBaseline="top";v.fillText(o.message,e,0);v.textBaseline=r;if(!o.data){return c}}this.example_feature=(o.data.length?o.data[0]:undefined);v.translate(e,ERROR_PADDING);D.draw(v,u,g,p);return c}});var VcfTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_type="VcfTrack";this.painter=VariantPainter};$.extend(VcfTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype);var ReadTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:c,onchange:function(){this.track.tile_cache.clear();this.track.draw()}});this.prefs=this.track_config.values;this.track_type="ReadTrack";this.painter=ReadPainter;this.make_name_popup_menu()};$.extend(ReadTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype);var ToolDataFeatureTrack=function(e,c,g,a,d,f,b){FeatureTrack.call(this,e,c,g,a,d,f,{},b);this.track_type="ToolDataFeatureTrack";this.data_url=raw_data_url;this.data_query_wait=1000;this.dataset_check_url=dataset_state_url};$.extend(ToolDataFeatureTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{predraw_init:function(){var b=this;var a=function(){if(b.data_cache.size()===0){setTimeout(a,300)}else{b.data_url=default_data_url;b.data_query_wait=DEFAULT_DATA_QUERY_WAIT;b.dataset_state_url=converted_datasets_state_url;$.getJSON(b.dataset_state_url,{dataset_id:b.dataset_id,hda_ldda:b.hda_ldda},function(c){})}};a()}});var extend=function(){var c=arguments[0];for(var b=1;b<arguments.length;b++){var a=arguments[b];for(key in a){c[key]=a[key]}}};var CanvasManager=function(a){this.document=a};CanvasManager.prototype.new_canvas=function(){var a=this.document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(a)}return a};var FeatureSlotter=function(c,b,a){this.slots={};this.start_end_dct={};this.w_scale=c;this.include_label=b;this.measureText=a};FeatureSlotter.prototype.slot_features=function(g){var k=this.w_scale,n=this.slots,c=this.start_end_dct,t=[],u=[],h=0;for(var r=0,s=g.length;r<s;r++){var e=g[r],j=e[0];if(n[j]!==undefined){h=Math.max(h,n[j]);u.push(n[j])}else{t.push(r)}}var l=function(A,B){for(var z=0;z<=MAX_FEATURE_DEPTH;z++){var x=false,C=c[z];if(C!==undefined){for(var w=0,y=C.length;w<y;w++){var v=C[w];if(B>v[0]&&A<v[1]){x=true;break}}}if(!x){return z}}return -1};for(var r=0,s=t.length;r<s;r++){var e=g[t[r]],j=e[0],p=e[1],a=e[2],m=e[3],b=Math.floor(p*k),f=Math.ceil(a*k),q=this.measureText(m).width,d;if(m!==undefined&&this.include_label){q+=(LABEL_SPACING+PACK_SPACING);if(b-q>=0){b-=q;d="left"}else{f+=q;d="right"}}var o=l(b,f);if(o>=0){if(c[o]===undefined){c[o]=[]}c[o].push([b,f]);n[j]=o;h=Math.max(h,o)}else{}}return h+1};var SummaryTreePainter=function(d,f,b,e,a,c){this.data=d;this.delta=f;this.max=b;this.view_start=e;this.view_end=a;this.show_counts=c};SummaryTreePainter.prototype.draw=function(o,a,n){var f=this.view_start,q=this.view_end-this.view_start,p=a/q;var l=this.data,k=this.delta,h=this.max,c=n+LABEL_SPACING+CHAR_HEIGHT_PX;delta_x_px=Math.ceil(k*p);o.save();for(var d=0,e=l.length;d<e;d++){var j=Math.floor((l[d][0]-f)*p);var g=l[d][1];if(!g){continue}var m=g/h*n;o.fillStyle="black";o.fillRect(j,c-m,delta_x_px,m);var b=4;if(this.show_counts&&(o.measureText(g).width+b)<delta_x_px){o.fillStyle="#666";o.textAlign="center";o.fillText(g,j+(delta_x_px/2),10)}}o.restore()};var LinePainter=function(d,g,a,b,f,c,e){this.data=d;this.view_start=g;this.view_end=a;this.min_value=b;this.max_value=f;this.color=c;this.mode=e};LinePainter.prototype.draw=function(o,n,l){var g=false,h=this.min_value,e=this.max_value,k=e-h,a=l,b=this.view_start,m=this.view_end-this.view_start,c=n/m,j=this.mode,s=this.data;o.save();var t=Math.round(l+h/k*l);if(j!=="Intensity"){o.beginPath();o.moveTo(0,t);o.lineTo(n,t);o.fillStyle="#aaa";o.stroke()}o.beginPath();o.fillStyle=this.color;var r,f,d;if(s.length>1){d=Math.ceil((s[1][0]-s[0][0])*c)}else{d=10}for(var p=0,q=s.length;p<q;p++){r=Math.round((s[p][0]-b)*c);f=s[p][1];if(f===null){if(g&&j==="Filled"){o.lineTo(r,a)}g=false;continue}if(f<h){f=h}else{if(f>e){f=e}}if(j==="Histogram"){f=Math.round(f/k*a);o.fillRect(r,t,d,-f)}else{if(j==="Intensity"){f=255-Math.floor((f-h)/k*255);o.fillStyle="rgb("+f+","+f+","+f+")";o.fillRect(r,0,d,a)}else{f=Math.round(a-(f-h)/k*a);if(g){o.lineTo(r,f)}else{g=true;if(j==="Filled"){o.moveTo(r,a);o.lineTo(r,f)}else{o.moveTo(r,f)}}}}}if(j==="Filled"){if(g){o.lineTo(r,t);o.lineTo(0,t)}o.fill()}else{o.stroke()}o.restore()};var FeaturePainter=function(c,e,a,b,d){this.data=c;this.view_start=e;this.view_end=a;this.prefs=b;this.mode=d};extend(FeaturePainter.prototype,{get_required_height:function(b){var a=y_scale=this.get_row_height(),c=this.mode;if(c==="no_detail"||c==="Squish"||c==="Pack"){a=b*y_scale}return a+Math.round(y_scale/2)},draw:function(n,d,m,j){var g=this.data,k=this.view_start,o=this.view_end;n.save();n.fillStyle=this.prefs.block_color;n.textAlign="right";var r=this.view_end-this.view_start,q=d/r,c=this.get_row_height();for(var f=0,h=g.length;f<h;f++){var p=g[f],e=p[0],a=p[1],b=p[2],l=(j&&j[e]!==undefined?j[e]:null);if(is_overlap([a,b],[k,o])&&(this.mode=="Dense"||l!==null)){this.draw_element(n,this.mode,p,l,k,o,q,c,d)}}n.restore()}});var LinkedFeaturePainter=function(c,e,a,b,d){FeaturePainter.call(this,c,e,a,b,d)};extend(LinkedFeaturePainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var b=this.mode,a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="no_detail"){a=NO_DETAIL_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT}}}return a},draw_element:function(p,g,x,j,r,H,L,M,a){var u=x[0],J=x[1],B=x[2]-1,s=x[3],C=Math.floor(Math.max(0,(J-r)*L)),q=Math.ceil(Math.min(a,Math.max(0,(B-r)*L))),A=(g==="Dense"?0:(0+j))*M,o,F,t=null,N=null,d=this.prefs.block_color,E=this.prefs.label_color;if(g==="Dense"){p.fillStyle=d;p.fillRect(C,A,q-C,DENSE_FEATURE_HEIGHT)}else{if(g==="no_detail"){p.fillStyle=d;p.fillRect(C,A+5,q-C,DENSE_FEATURE_HEIGHT)}else{var n=x[4],z=x[5],D=x[6],e=x[7];if(z&&D){t=Math.floor(Math.max(0,(z-r)*L));N=Math.ceil(Math.min(a,Math.max(0,(D-r)*L)))}var K,v;if(g==="Squish"){K=1;v=SQUISH_FEATURE_HEIGHT}else{K=5;v=PACK_FEATURE_HEIGHT}if(!e){if(x.strand){if(x.strand==="+"){p.fillStyle=RIGHT_STRAND_INV}else{if(x.strand==="-"){p.fillStyle=LEFT_STRAND_INV}}}else{p.fillStyle=d}p.fillRect(C,A,q-C,v)}else{var m,w;if(g==="Squish"){p.fillStyle=CONNECTOR_COLOR;m=A+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;w=1}else{if(n){var m=A;var w=v;if(n==="+"){p.fillStyle=RIGHT_STRAND}else{if(n==="-"){p.fillStyle=LEFT_STRAND}}}else{p.fillStyle=CONNECTOR_COLOR;m+=(SQUISH_FEATURE_HEIGHT/2)+1;w=1}}p.fillRect(C,m,q-C,w);for(var I=0,c=e.length;I<c;I++){var h=e[I],b=Math.floor(Math.max(0,(h[0]-r)*L)),y=Math.ceil(Math.min(a,Math.max((h[1]-1-r)*L)));if(b>y){continue}p.fillStyle=d;p.fillRect(b,A+(v-K)/2+1,y-b,K);if(t!==undefined&&!(b>N||y<t)){var G=Math.max(b,t),l=Math.min(y,N);p.fillRect(G,A+1,l-G,v)}}}if(g==="Pack"&&J>r){p.fillStyle=E;var f=1;if(f===0&&C-p.measureText(s).width<0){p.textAlign="left";p.fillText(s,q+LABEL_SPACING,A+8)}else{p.textAlign="right";p.fillText(s,C-LABEL_SPACING,A+8)}p.fillStyle=d}}}}});var VariantPainter=function(c,e,a,b,d){FeaturePainter.call(this,c,e,a,b,d)};extend(VariantPainter.prototype,FeaturePainter.prototype,{draw_element:function(u,p,j,e,x,c,m,v,s){var j=data[i],l=j[0],t=j[1],d=j[2]-1,o=j[3],g=Math.floor(Math.max(0,(t-x)*m)),k=Math.ceil(Math.min(s,Math.max(0,(d-x)*m))),f=(p==="Dense"?0:(0+e))*v,a,y,b=null,n=null;if(no_label){u.fillStyle=block_color;u.fillRect(g+left_offset,f+5,k-g,1)}else{var w=j[4],r=j[5],h=j[6];a=9;y=1;u.fillRect(g+left_offset,f,k-g,a);if(p!=="Dense"&&o!==undefined&&t>x){u.fillStyle=label_color;if(tile_index===0&&g-u.measureText(o).width<0){u.textAlign="left";u.fillText(o,k+2+left_offset,f+8)}else{u.textAlign="right";u.fillText(o,g-2+left_offset,f+8)}u.fillStyle=block_color}var q=w+" / "+r;if(t>x&&u.measureText(q).width<(k-g)){u.fillStyle="white";u.textAlign="center";u.fillText(q,left_offset+g+(k-g)/2,f+8);u.fillStyle=block_color}}}});var ReadPainter=function(d,f,a,c,e,b){FeaturePainter.call(this,d,f,a,c,e);this.ref_seq=b};extend(ReadPainter.prototype,FeaturePainter.prototype,{get_row_height:function(){var a,b=this.mode;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT;if(this.prefs.show_insertions){a*=2}}}return a},draw_read:function(z,u,p,E,d,y,m,j,h){z.textAlign="center";var x=this,b=[E,d],s=0,A=0,w=0;ref_seq=this.ref_seq;var J=[];if((u==="Pack"||this.mode==="Auto")&&j!==undefined&&p>CHAR_WIDTH_PX){w=Math.round(p/2)}if(!m){m=[[0,j.length]]}for(var q=0,C=m.length;q<C;q++){var n=m[q],e="MIDNSHP=X"[n[0]],r=n[1];if(e==="H"||e==="S"){s-=r}var k=y+s,I=Math.floor(Math.max(0,(k-E)*p)),l=Math.floor(Math.max(0,(k+r-E)*p));switch(e){case"H":break;case"S":case"M":case"=":var t=compute_overlap([k,k+r],b);if(t!==NO_OVERLAP){var v=j.slice(A,A+r);if(w>0){z.fillStyle=this.prefs.block_color;z.fillRect(I-w,h+1,l-I,9);z.fillStyle=CONNECTOR_COLOR;for(var G=0,a=v.length;G<a;G++){if(this.prefs.show_differences&&ref_seq){var o=ref_seq[k-E+G];if(!o||o.toLowerCase()===v[G].toLowerCase()){continue}}if(k+G>=E&&k+G<=d){var H=Math.floor(Math.max(0,(k+G-E)*p));z.fillText(v[G],H,h+9)}}}else{z.fillStyle=this.prefs.block_color;z.fillRect(I,h+(this.mode!=="Dense"?4:5),l-I,(u!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}A+=r;s+=r;break;case"N":z.fillStyle=CONNECTOR_COLOR;z.fillRect(I-w,h+5,l-I,1);s+=r;break;case"D":z.fillStyle="red";z.fillRect(I-w,h+4,l-I,3);s+=r;break;case"P":break;case"I":var t=compute_overlap([k,k+r],b),D=I-w;if(t!==NO_OVERLAP){var v=j.slice(A,A+r);if(this.prefs.show_insertions){var g=I-(l-I)/2;if((u==="Pack"||this.mode==="Auto")&&j!==undefined&&p>CHAR_WIDTH_PX){z.fillStyle="yellow";z.fillRect(g-w,h-9,l-I,9);J[J.length]={type:"triangle",data:[D,h+4,5]};z.fillStyle=CONNECTOR_COLOR;switch(t){case (OVERLAP_START):v=v.slice(E-k);break;case (OVERLAP_END):v=v.slice(0,k-d);break;case (CONTAINED_BY):break;case (CONTAINS):v=v.slice(E-k,k-d);break}for(var G=0,a=v.length;G<a;G++){var H=Math.floor(Math.max(0,(k+G-E)*p));z.fillText(v[G],H-(l-I)/2,h)}}else{z.fillStyle="yellow";z.fillRect(g,h+(this.mode!=="Dense"?2:5),l-I,(u!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((u==="Pack"||this.mode==="Auto")&&j!==undefined&&p>CHAR_WIDTH_PX){J[J.length]={type:"text",data:[v.length,D,h+9]}}else{}}}A+=r;break;case"X":A+=r;break}}z.fillStyle="yellow";var F,f,K;for(var B=0;B<J.length;B++){F=J[B];f=F.type;K=F.data;if(f==="text"){z.font="bold "+DEFAULT_FONT;z.fillText(K[0],K[1],K[2]);z.font=DEFAULT_FONT}else{if(f=="triangle"){z.drawDownwardEquilateralTriangle(K[0],K[1],K[2])}}}},draw_element:function(u,p,g,d,x,b,l,v,s){var k=g[0],t=g[1],c=g[2],m=g[3],f=Math.floor(Math.max(0,(t-x)*l)),h=Math.ceil(Math.min(s,Math.max(0,(c-x)*l))),e=(p==="Dense"?1:(1+d))*v,y=this.prefs.block_color,j=this.prefs.label_color,r=0;if((p==="Pack"||this.mode==="Auto")&&l>CHAR_WIDTH_PX){var r=Math.round(l/2)}u.fillStyle=y;if(g[5] instanceof Array){var q=Math.floor(Math.max(0,(g[4][0]-x)*l)),o=Math.ceil(Math.min(s,Math.max(0,(g[4][1]-x)*l))),n=Math.floor(Math.max(0,(g[5][0]-x)*l)),a=Math.ceil(Math.min(s,Math.max(0,(g[5][1]-x)*l)));if(g[4][1]>=x&&g[4][0]<=b&&g[4][2]){this.draw_read(u,p,l,x,b,g[4][0],g[4][2],g[4][3],e)}if(g[5][1]>=x&&g[5][0]<=b&&g[5][2]){this.draw_read(u,p,l,x,b,g[5][0],g[5][2],g[5][3],e)}if(n>o){u.fillStyle=CONNECTOR_COLOR;u.dashedLine(o-r,e+5,n-r,e+5)}}else{u.fillStyle=y;this.draw_read(u,p,l,x,b,t,g[4],g[5],e)}if(p==="Pack"&&t>x){u.fillStyle=this.prefs.label_color;var w=1;if(w===0&&f-u.measureText(m).width<0){u.textAlign="left";u.fillText(m,h+LABEL_SPACING-r,e+8)}else{u.textAlign="right";u.fillText(m,f-LABEL_SPACING-r,e+8)}u.fillStyle=y}}});
\ No newline at end of file
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.
1
0
31 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/dd867c332729/
changeset: r5294:dd867c332729
user: kanwei
date: 2011-04-01 00:58:02
summary: Remove array_tree datatype; add tabix
affected #: 1 file (5 bytes)
--- a/datatypes_conf.xml.sample Thu Mar 31 17:22:10 2011 -0400
+++ b/datatypes_conf.xml.sample Thu Mar 31 18:58:02 2011 -0400
@@ -128,9 +128,9 @@
<converter file="wiggle_to_simple_converter.xml" target_datatype="interval"/><!-- <display file="gbrowse/gbrowse_wig.xml" /> --></datatype>
- <datatype extension="array_tree" type="galaxy.datatypes.data:Data" /><datatype extension="summary_tree" type="galaxy.datatypes.data:Data" /><datatype extension="interval_index" type="galaxy.datatypes.data:Data" />
+ <datatype extension="tabix" type="galaxy.datatypes.data:Data" /><!-- Start EMBOSS tools --><datatype extension="acedb" type="galaxy.datatypes.data:Text"/><datatype extension="asn1" type="galaxy.datatypes.data:Text"/>
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.
1
0
commit/galaxy-central: jgoecks: Trackster visual analytics: enable datasets that cannot be indexed--and hence cannot be subseted--to be used as tool inputs.
by Bitbucket 31 Mar '11
by Bitbucket 31 Mar '11
31 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/dc8701726b13/
changeset: r5293:dc8701726b13
user: jgoecks
date: 2011-03-31 23:22:10
summary: Trackster visual analytics: enable datasets that cannot be indexed--and hence cannot be subseted--to be used as tool inputs.
affected #: 1 file (621 bytes)
--- a/lib/galaxy/web/controllers/tracks.py Thu Mar 31 15:07:02 2011 -0400
+++ b/lib/galaxy/web/controllers/tracks.py Thu Mar 31 17:22:10 2011 -0400
@@ -670,6 +670,8 @@
if not tool:
return messages.NO_TOOL
tool_params = dict( [ ( p.name, p.value ) for p in original_job.parameters ] )
+ # TODO: need to handle updates to conditional parameters; conditional
+ # params are stored in dicts (and dicts within dicts).
tool_params.update( dict( [ ( key, value ) for key, value in kwargs.items() if key in tool.inputs ] ) )
tool_params = tool.params_from_strings( tool_params, self.app )
@@ -685,13 +687,16 @@
messages_list = []
for jida in original_job.input_datasets:
input_dataset = jida.dataset
- track_type, data_sources = input_dataset.datatype.get_track_type()
- # Convert to datasource that provides 'data' because we need to
- # extract the original data.
- data_source = data_sources[ 'data' ]
- msg = self._convert_dataset( trans, input_dataset, data_source )
- if msg is not None:
- messages_list.append( msg )
+ # TODO: put together more robust way to determine if a dataset can be indexed.
+ if hasattr( input_dataset, 'get_track_type' ):
+ # Can index dataset.
+ track_type, data_sources = input_dataset.datatype.get_track_type()
+ # Convert to datasource that provides 'data' because we need to
+ # extract the original data.
+ data_source = data_sources[ 'data' ]
+ msg = self._convert_dataset( trans, input_dataset, data_source )
+ if msg is not None:
+ messages_list.append( msg )
# Return any messages generated during conversions.
return_message = _get_highest_priority_msg( messages_list )
@@ -707,38 +712,42 @@
messages_list = []
for jida in original_job.input_datasets:
input_dataset = jida.dataset
- track_type, data_sources = input_dataset.datatype.get_track_type()
- data_source = data_sources[ 'data' ]
- converted_dataset = input_dataset.get_converted_dataset( trans, data_source )
+ if hasattr( input_dataset, 'get_track_type' ):
+ #
+ # Dataset can be indexed and hence a subset can be extracted.
+ #
+ track_type, data_sources = input_dataset.datatype.get_track_type()
+ data_source = data_sources[ 'data' ]
+ converted_dataset = input_dataset.get_converted_dataset( trans, data_source )
- #
- # Create new HDA for input dataset's subset.
- #
- subset_dataset = trans.app.model.HistoryDatasetAssociation( extension=input_dataset.ext, \
- dbkey=input_dataset.dbkey, \
- create_dataset=True, \
- sa_session=trans.sa_session,
- name="Subset [%s:%i-%i] of data %i" % \
- ( chrom, low, high, input_dataset.hid ),
- visible=False )
- job_history.add_dataset( subset_dataset )
- trans.sa_session.add( subset_dataset )
- trans.app.security_agent.set_all_dataset_permissions( subset_dataset.dataset, hda_permissions )
+ #
+ # Create new HDA for input dataset's subset.
+ #
+ new_dataset = trans.app.model.HistoryDatasetAssociation( extension=input_dataset.ext, \
+ dbkey=input_dataset.dbkey, \
+ create_dataset=True, \
+ sa_session=trans.sa_session,
+ name="Subset [%s:%i-%i] of data %i" % \
+ ( chrom, low, high, input_dataset.hid ),
+ visible=False )
+ job_history.add_dataset( new_dataset )
+ trans.sa_session.add( new_dataset )
+ trans.app.security_agent.set_all_dataset_permissions( new_dataset.dataset, hda_permissions )
- # Write data subset to new HDA.
- data_provider_class = get_data_provider( original_dataset=input_dataset )
- data_provider = data_provider_class( original_dataset=input_dataset,
- converted_dataset=converted_dataset )
- data_provider.write_data_to_file( chrom, low, high, subset_dataset.file_name )
+ # Write subset of data to new dataset
+ data_provider_class = get_data_provider( original_dataset=input_dataset )
+ data_provider = data_provider_class( original_dataset=input_dataset,
+ converted_dataset=converted_dataset )
+ data_provider.write_data_to_file( chrom, low, high, new_dataset.file_name )
- # TODO: size not working.
- subset_dataset.set_size()
- subset_dataset.info = "Data subset for trackster"
- subset_dataset.set_dataset_state( trans.app.model.Dataset.states.OK )
- trans.sa_session.flush()
+ # TODO: size not working.
+ new_dataset.set_size()
+ new_dataset.info = "Data subset for trackster"
+ new_dataset.set_dataset_state( trans.app.model.Dataset.states.OK )
+ trans.sa_session.flush()
- # Add dataset to tool's parameters.
- tool_params[ jida.name ] = subset_dataset
+ # Add dataset to tool's parameters.
+ tool_params[ jida.name ] = new_dataset
#
# Start tool and handle outputs.
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.
1
0
commit/galaxy-central: jgoecks: Trackster visual analytics: add support for static select parameters.
by Bitbucket 31 Mar '11
by Bitbucket 31 Mar '11
31 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/846542872d17/
changeset: r5292:846542872d17
user: jgoecks
date: 2011-03-31 21:07:02
summary: Trackster visual analytics: add support for static select parameters.
affected #: 3 files (2.6 KB)
--- a/lib/galaxy/visualization/tracks/visual_analytics.py Thu Mar 31 14:09:47 2011 -0400
+++ b/lib/galaxy/visualization/tracks/visual_analytics.py Thu Mar 31 15:07:02 2011 -0400
@@ -1,5 +1,5 @@
-from galaxy.tools.parameters.basic import IntegerToolParameter, FloatToolParameter
-
+from galaxy.tools.parameters.basic import IntegerToolParameter, FloatToolParameter, SelectToolParameter
+from galaxy.tools.parameters.dynamic_options import DynamicOptions
def get_dataset_job( hda ):
# Get dataset's job.
@@ -29,13 +29,16 @@
tool_param_values = tool.params_from_strings( tool_param_values, trans.app, ignore_errors=True )
for name, input in tool.inputs.items():
if type( input ) == IntegerToolParameter:
- tool_params.append( { 'name' : name, 'label': input.label, 'type': 'int', \
- 'value': tool_param_values.get( name, input.value ), \
- 'min' : input.min, 'max' : input.max } )
+ tool_params.append( { 'name' : name, 'label' : input.label, \
+ 'value' : tool_param_values.get( name, input.value ), \
+ 'type' : 'int', 'min' : input.min, 'max' : input.max } )
elif type( input ) == FloatToolParameter:
- tool_params.append( { 'name' : name, 'label': input.label, 'type': 'float', \
- 'value': tool_param_values.get( name, input.value ), \
- 'min' : input.min, 'max' : input.max } )
+ tool_params.append( { 'name' : name, 'label' : input.label, \
+ 'value' : tool_param_values.get( name, input.value ), \
+ 'type' : 'float', 'min' : input.min, 'max' : input.max } )
+ elif type( input ) == SelectToolParameter and type( input.options ) != DynamicOptions:
+ tool_params.append( { 'name' : name, 'label' : input.label, 'type' : 'select', \
+ 'options' : [ option[:2] for option in input.get_options( trans, None ) ] } )
# If tool has parameters that can be interactively modified, return tool.
# Return empty set otherwise.
--- a/static/scripts/packed/trackster.js Thu Mar 31 14:09:47 2011 -0400
+++ b/static/scripts/packed/trackster.js Thu Mar 31 15:07:02 2011 -0400
@@ -1,1 +1,1 @@
-CanvasRenderingContext2D.prototype.dashedLine=function(c,j,b,h,f){if(f===undefined){f=4}var e=b-c;var d=h-j;var g=Math.floor(Math.sqrt(e*e+d*d)/f);var l=e/g;var k=d/g;var a;for(a=0;a<g;a++,c+=l,j+=k){if(a%2!==0){continue}this.fillRect(c,j,f,1)}};CanvasRenderingContext2D.prototype.drawDownwardEquilateralTriangle=function(b,a,e){var d=b-e/2,c=b+e/2,f=a-Math.sqrt(e*3/2);this.beginPath();this.moveTo(d,f);this.lineTo(c,f);this.lineTo(b,a);this.lineTo(d,f);this.strokeStyle=this.fillStyle;this.fill();this.stroke();this.closePath()};function sortable(a,b){a.bind("drag",{handle:b,relative:true},function(h,j){var g=$(this).parent();var f=g.children();var c;for(c=0;c<f.length;c++){if(j.offsetY<$(f.get(c)).position().top){break}}if(c===f.length){if(this!==f.get(c-1)){g.append(this)}}else{if(this!==f.get(c)){$(this).insertBefore(f.get(c))}}})}var NO_OVERLAP=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005;function compute_overlap(e,b){var g=e[0],f=e[1],d=b[0],c=b[1],a;if(g<d){if(f<d){a=NO_OVERLAP}else{if(f<=c){a=OVERLAP_START}else{a=CONTAINS}}}else{if(g>c){a=NO_OVERLAP}else{if(f<=c){a=CONTAINED_BY}else{a=OVERLAP_END}}}return a}function is_overlap(b,a){return(compute_overlap(b,a)!==NO_OVERLAP)}function get_slider_step(c,a){var b=a-c;return(b<=1?0.01:(b<=1000?1:5))}var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=1,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,ERROR_PADDING=10,LABEL_SPACING=2,PACK_SPACING=5,MIN_SQUISH_VIEW_WIDTH=12000,DEFAULT_FONT="9px Monaco, Lucida Console, monospace",DENSITY=200,FEATURE_LEVELS=10,MAX_FEATURE_DEPTH=100,DEFAULT_DATA_QUERY_WAIT=5000,MAX_CHROMS_SELECTABLE=100,CONNECTOR_COLOR="#ccc",DATA_ERROR="There was an error in indexing this dataset. ",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_CANNOT_RUN_TOOL="Tool cannot be rerun: ",DATA_LOADING="Loading data...",DATA_OK="Ready for display",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=5,CACHED_DATA=5,DUMMY_CANVAS=document.createElement("canvas"),RIGHT_STRAND,LEFT_STRAND;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(DUMMY_CANVAS)}var CONTEXT=DUMMY_CANVAS.getContext("2d");CONTEXT.font=DEFAULT_FONT;var CHAR_WIDTH_PX=CONTEXT.measureText("A").width,CHAR_HEIGHT_PX=9;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};function round_1000(a){return Math.round(a*1000)/1000}var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!==-1){this.move_key_to_end(b,a)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},move_key_to_end:function(b,a){this.key_ary.splice(a,1);this.key_ary.push(b)},clear:function(){this.obj_cache={};this.key_ary=[]},size:function(){return this.key_ary.length}});var DataManager=function(b,a,c){Cache.call(this,b);this.track=a;this.subset=(c!==undefined?c:true)};$.extend(DataManager.prototype,Cache.prototype,{load_data:function(h,j,d,g,a,f){var c={chrom:h,low:j,high:d,mode:g,resolution:a,dataset_id:this.track.dataset_id,hda_ldda:this.track.hda_ldda};$.extend(c,f);var k=[];for(var e=0;e<this.track.filters.length;e++){k[k.length]=this.track.filters[e].name}c.filter_cols=JSON.stringify(k);var b=this;return $.getJSON(this.track.data_url,c,function(l){b.set_data(j,d,g,l)})},get_data:function(j,k,c,h,b,f){var l=this.get(this.gen_key(k,c,h));if(l){return l}if(this.subset){var m,g,a,e,h,l;for(var d=0;d<this.key_ary.length;d++){m=this.key_ary[d];g=this.split_key(m);a=g[0];e=g[1];if(k>=a&&c<=e){l=this.obj_cache[m];if(l.dataset_type!=="summary_tree"&&l.extra_info!=="no_detail"){this.move_key_to_end(m,d);return l}}}}return this.load_data(j,k,c,h,b,f)},set_data:function(b,c,d,a){return this.set(this.gen_key(b,c,d),a)},gen_key:function(a,c,d){var b=a+"_"+c+"_"+d;return b},split_key:function(a){return a.split("_")}});var View=function(a,d,c,b,e){this.container=a;this.chrom=null;this.vis_id=c;this.dbkey=b;this.title=d;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.num_tracks=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init(e);this.reset()};$.extend(View.prototype,{init:function(d){var c=this.container,a=this;this.top_container=$("<div/>").addClass("top-container").appendTo(c);this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(c);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(c);this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("<div/>").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.intro_div=$("<div/>").addClass("intro").text("Select a chrom from the dropdown below").hide();this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("<div/>").addClass("nav-container").prependTo(this.top_container);this.nav=$("<div/>").addClass("nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("<a href='javascript:void(0);'>Close Overview</a>").addClass("overview-close").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var b=function(f){if(f.type==="focusout"||(f.keyCode||f.which)===13||(f.keyCode||f.which)===27){if((f.keyCode||f.which)!==27){a.go_to($(this).val())}$(this).hide();$(this).val("");a.location_span.show();a.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",b).appendTo(this.nav_controls);this.location_span=$("<span/>").addClass("location").appendTo(this.nav_controls);this.location_span.bind("click",function(){a.location_span.hide();a.chrom_select.hide();a.nav_input.val(a.chrom+":"+a.low+"-"+a.high);a.nav_input.css("display","inline-block");a.nav_input.select();a.nav_input.focus()});if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("<a id='zoom-out' />").click(function(){a.zoom_out();a.redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a id='zoom-in' />").click(function(){a.zoom_in();a.redraw()}).appendTo(this.nav_controls);this.load_chroms({low:0},d);this.chrom_select.bind("change",function(){a.change_chrom(a.chrom_select.val())});this.intro_div.show();this.content_div.bind("click",function(f){$(this).find("input").trigger("blur")});this.content_div.bind("dblclick",function(f){a.zoom_in(f.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(f,g){this.current_x=g.offsetX}).bind("drag",function(f,h){var j=h.offsetX-this.current_x;this.current_x=h.offsetX;var g=Math.round(j/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-g)});this.overview_close.bind("click",function(){for(var f=0,e=a.tracks.length;f<e;f++){a.tracks[f].is_overview=false}$(this).siblings().filter("canvas").remove();$(this).parent().css("height",a.overview_box.height());a.overview_highlight.hide();$(this).hide()});this.viewport_container.bind("draginit",function(f,g){if(f.clientX>a.viewport_container.width()-16){return false}}).bind("dragstart",function(f,g){g.original_low=a.low;g.current_height=f.clientY;g.current_x=g.offsetX}).bind("drag",function(h,k){var f=$(this);var l=k.offsetX-k.current_x;var g=f.scrollTop()-(h.clientY-k.current_height);f.scrollTop(g);k.current_height=h.clientY;k.current_x=k.offsetX;var j=Math.round(l/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}).bind("mousewheel",function(h,k,g,f){if(g){var j=Math.round(-g/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}});this.top_labeltrack.bind("dragstart",function(f,g){return $("<div />").css({height:a.content_div.height()+a.top_labeltrack.height()+a.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(k,l){$(l.proxy).css({left:Math.min(k.pageX,l.startX),width:Math.abs(k.pageX-l.startX)});var g=Math.min(k.pageX,l.startX)-a.container.offset().left,f=Math.max(k.pageX,l.startX)-a.container.offset().left,j=(a.high-a.low),h=a.viewport_container.width();a.update_location(Math.round(g/h*j)+a.low,Math.round(f/h*j)+a.low)}).bind("dragend",function(l,m){var g=Math.min(l.pageX,m.startX),f=Math.max(l.pageX,m.startX),j=(a.high-a.low),h=a.viewport_container.width(),k=a.low;a.low=Math.round(g/h*j)+k;a.high=Math.round(f/h*j)+k;$(m.proxy).remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack));$(window).bind("resize",function(){a.resize_window()});$(document).bind("redraw",function(){a.redraw()});this.reset();$(window).trigger("resize")},update_location:function(a,b){this.location_span.text(commatize(a)+" - "+commatize(b));this.nav_input.val(this.chrom+":"+commatize(a)+"-"+commatize(b))},load_chroms:function(b,c){b.num=MAX_CHROMS_SELECTABLE;$.extend(b,(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}));var a=this;$.ajax({url:chrom_url,data:b,dataType:"json",success:function(e){if(e.chrom_info.length===0){alert("Invalid chromosome: "+b.chrom);return}if(e.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=e.chrom_info;var h='<option value="">Select Chrom/Contig</option>';for(var g=0,d=a.chrom_data.length;g<d;g++){var f=a.chrom_data[g].chrom;h+='<option value="'+f+'">'+f+"</option>"}if(e.prev_chroms){h+='<option value="previous">Previous '+MAX_CHROMS_SELECTABLE+"</option>"}if(e.next_chroms){h+='<option value="next">Next '+MAX_CHROMS_SELECTABLE+"</option>"}a.chrom_select.html(h);if(c){c()}a.chrom_start_index=e.start_index},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}})},change_chrom:function(e,b,g){if(!e||e==="None"){return}var d=this;if(e==="previous"){d.load_chroms({low:this.chrom_start_index-MAX_CHROMS_SELECTABLE});return}if(e==="next"){d.load_chroms({low:this.chrom_start_index+MAX_CHROMS_SELECTABLE});return}var f=$.grep(d.chrom_data,function(j,k){return j.chrom===e})[0];if(f===undefined){d.load_chroms({chrom:e},function(){d.change_chrom(e,b,g)});return}else{if(e!==d.chrom){d.chrom=e;if(!d.chrom){d.intro_div.show()}else{d.intro_div.hide()}d.chrom_select.val(d.chrom);d.max_high=f.len-1;d.reset();d.redraw(true);for(var h=0,a=d.tracks.length;h<a;h++){var c=d.tracks[h];if(c.init){c.init()}}}if(b!==undefined&&g!==undefined){d.low=Math.max(b,0);d.high=Math.min(g,d.max_high)}d.reset_overview();d.redraw()}},go_to:function(f){var k=this,a,d,b=f.split(":"),h=b[0],j=b[1];if(j!==undefined){try{var g=j.split("-");a=parseInt(g[0].replace(/,/g,""),10);d=parseInt(g[1].replace(/,/g,""),10)}catch(c){return false}}k.change_chrom(h,a,d)},move_fraction:function(c){var a=this;var b=a.high-a.low;this.move_delta(c*b)},move_delta:function(c){var a=this;var b=a.high-a.low;if(a.low-c<a.max_low){a.low=a.max_low;a.high=a.max_low+b}else{if(a.high-c>a.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_track:function(a){a.view=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);sortable(a.container_div,".draghandle");this.track_id_counter+=1;this.num_tracks+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)];this.num_tracks-=1},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(h){var g=this.high-this.low,f=this.low,b=this.high;if(f<this.max_low){f=this.max_low}if(b>this.max_high){b=this.max_high}if(this.high!==0&&g<this.min_separation){b=f+this.min_separation}this.low=Math.floor(f);this.high=Math.ceil(b);this.resolution=Math.pow(10,Math.ceil(Math.log((this.high-this.low)/200)/Math.LN10));this.zoom_res=Math.pow(FEATURE_LEVELS,Math.max(0,Math.ceil(Math.log(this.resolution,FEATURE_LEVELS)/Math.log(FEATURE_LEVELS))));var a=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var e=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var j=13;this.overview_box.css({left:a,width:Math.max(j,e)}).show();if(e<j){this.overview_box.css("left",a-(j-e)/2)}if(this.overview_highlight){this.overview_highlight.css({left:a,width:e})}this.update_location(this.low,this.high);if(!h){for(var c=0,d=this.tracks.length;c<d;c++){if(this.tracks[c]&&this.tracks[c].enabled){this.tracks[c].draw()}}for(c=0,d=this.label_tracks.length;c<d;c++){this.label_tracks[c].draw()}}},zoom_in:function(b,c){if(this.max_high===0||this.high-this.low<this.min_separation){return}var d=this.high-this.low,e=d/2+this.low,a=(d/this.zoom_factor)/2;if(b){e=b/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(e-a);this.high=Math.round(e+a);this.redraw()},zoom_out:function(){if(this.max_high===0){return}var b=this.high-this.low,c=b/2+this.low,a=(b*this.zoom_factor)/2;this.low=Math.round(c-a);this.high=Math.round(c+a);this.redraw()},resize_window:function(){this.viewport_container.height(this.container.height()-this.top_container.height()-this.bottom_container.height());this.nav_container.width(this.container.width());this.redraw()},reset_overview:function(){this.overview_viewport.find("canvas").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide()}});var Tool=function(a,b){this.name=a;this.params=b};$.extend(Tool.prototype,{get_param_values_dict:function(){var b={};for(var a=0;a<this.params.length;a++){var c=this.params[a];b[c.name]=c.value}return b},get_param_values:function(){var b=[];for(var a=0;a<this.params.length;a++){b[a]=this.params[a].value}return b}});var NumberToolParameter=function(c,b,e,a,d){this.name=c;this.label=b;this.min=e;this.max=a;this.value=d};var get_tool_from_dict=function(f){if(obj_length(f)===0){return undefined}var b=f.name;var l=f.params;var c=Array();for(var e=0;e<l.length;e++){var g=l[e];var a=g.name,k=g.label,h=g.type,d=g.min,j=g.max,m=g.value;c[c.length]=new NumberToolParameter(a,k,d,j,m)}return new Tool(b,c)};var Filter=function(b,a,c){this.name=b;this.index=a;this.value=c};var NumberFilter=function(b,a){this.name=b;this.index=a;this.low=-Number.MAX_VALUE;this.high=Number.MAX_VALUE;this.min=Number.MAX_VALUE;this.max=-Number.MAX_VALUE;this.slider=null;this.slider_label=null};$.extend(NumberFilter.prototype,{applies_to:function(a){if(a.length>this.index){return true}return false},keep:function(a){if(!this.applies_to(a)){return true}return(a[this.index]>=this.low&&a[this.index]<=this.high)},update_attrs:function(b){var a=false;if(!this.applies_to(b)){return a}if(b[this.index]<this.min){this.min=Math.floor(b[this.index]);a=true}if(b[this.index]>this.max){this.max=Math.ceil(b[this.index]);a=true}return a},update_ui_elt:function(){var b=this.slider.slider("option","min"),a=this.slider.slider("option","max");if(this.min<b||this.max>a){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var get_filters_from_dict=function(a){var g=[];for(var d=0;d<a.length;d++){var f=a[d];var c=f.name,e=f.type,b=f.index;if(e==="int"||e==="float"){g[d]=new NumberFilter(c,b)}else{g[d]=new Filter(c,b,e)}}return g};var TrackConfig=function(a){this.track=a.track;this.params=a.params;this.values={};if(a.saved_values){this.restore_values(a.saved_values)}this.onchange=a.onchange};$.extend(TrackConfig.prototype,{restore_values:function(a){var b=this;$.each(this.params,function(c,d){if(a[d.key]!==undefined){b.values[d.key]=a[d.key]}else{b.values[d.key]=d.default_value}})},build_form:function(){var b=this;var a=$("<div />");$.each(this.params,function(f,d){if(!d.hidden){var c="param_"+f;var l=$("<div class='form-row' />").appendTo(a);l.append($("<label />").attr("for",c).text(d.label+":"));if(d.type==="bool"){l.append($('<input type="checkbox" />').attr("id",c).attr("name",c).attr("checked",b.values[d.key]))}else{if(d.type==="color"){var h=b.values[d.key];var g=$("<input />").attr("id",c).attr("name",c).val(h);var j=$("<div class='tipsy tipsy-north' style='position: absolute;' />").hide();var e=$("<div style='background-color: black; padding: 10px;'></div>").appendTo(j);var k=$("<div/>").appendTo(e).farbtastic({width:100,height:100,callback:g,color:h});$("<div />").append(g).append(j).appendTo(l).bind("click",function(m){j.css({left:$(this).position().left+($(g).width()/2)-60,top:$(this).position().top+$(this.height)}).show();$(document).bind("click.color-picker",function(){j.hide();$(document).unbind("click.color-picker")});m.stopPropagation()})}else{l.append($("<input />").attr("id",c).attr("name",c).val(b.values[d.key]))}}}});return a},update_from_form:function(a){var c=this;var b=false;$.each(this.params,function(d,f){if(!f.hidden){var g="param_"+d;var e=a.find("#"+g).val();if(f.type==="float"){e=parseFloat(e)}else{if(f.type==="int"){e=parseInt(e)}else{if(f.type==="bool"){e=a.find("#"+g).is(":checked")}}}if(e!==c.values[f.key]){c.values[f.key]=e;b=true}}});if(b){this.onchange()}}});var Track=function(b,a,e,c,d){this.name=b;this.view=a;this.parent_element=e;this.data_url=(c?c:default_data_url);this.data_url_extra_params={};this.data_query_wait=(d?d:DEFAULT_DATA_QUERY_WAIT);this.dataset_check_url=converted_datasets_state_url;this.container_div=$("<div />").addClass("track").css("position","relative");if(!this.hidden){this.header_div=$("<div class='track-header' />").appendTo(this.container_div);if(this.view.editor){this.drag_div=$("<div class='draghandle' />").appendTo(this.header_div)}this.name_div=$("<div class='menubutton popup' />").appendTo(this.header_div);this.name_div.text(this.name);this.name_div.attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase())}this.content_div=$("<div class='track-content'>").appendTo(this.container_div);this.parent_element.append(this.container_div)};$.extend(Track.prototype,{init:function(){var a=this;a.enabled=false;a.tile_cache.clear();a.data_cache.clear();a.initial_canvas=undefined;a.content_div.css("height","auto");a.container_div.removeClass("nodata error pending");if(!a.dataset_id){return}$.getJSON(converted_datasets_state_url,{hda_ldda:a.hda_ldda,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){if(!b||b==="error"||b.kind==="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR);if(b.message){var d=a.view.tracks.indexOf(a);var c=$(" <a href='javascript:void(0);'></a>").text("View error").bind("click",function(){show_modal("Trackster Error","<pre>"+b.message+"</pre>",{Close:hide_modal})});a.content_div.append(c)}}else{if(b==="no converter"){a.container_div.addClass("error");a.content_div.text(DATA_NOCONVERTER)}else{if(b==="no data"||(b.data!==undefined&&(b.data===null||b.data.length===0))){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(b==="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},a.data_query_wait)}else{if(b.status==="data"){if(b.valid_chroms){a.valid_chroms=b.valid_chroms;a.make_name_popup_menu()}a.content_div.text(DATA_OK);if(a.view.chrom){a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.enabled=true;$.when(a.predraw_init()).done(function(){a.draw()})}}}}}}})},predraw_init:function(){},update_name:function(a){this.old_name=this.name;this.name=a;this.name_div.text(this.name)},revert_name:function(){this.name=this.old_name;this.name_div.text(this.name)}});var TiledTrack=function(b,j,o){var c=this,p=c.view;this.filters=(b!==undefined?get_filters_from_dict(b):[]);this.filters_available=false;this.filters_visible=false;this.tool=(j!==undefined?get_tool_from_dict(j):undefined);this.parent_track=o;this.child_tracks=[];if(c.hidden){return}var k=function(r,s,t){r.click(function(){var u=s.text();max=parseFloat(t.slider("option","max")),input_size=(max<=1?4:max<=1000000?max.toString().length:6),multi_value=false;if(t.slider("option","values")){input_size=2*input_size+1;multi_value=true}s.text("");$("<input type='text'/>").attr("size",input_size).attr("maxlength",input_size).attr("value",u).appendTo(s).focus().select().click(function(v){v.stopPropagation()}).blur(function(){$(this).remove();s.text(u)}).keyup(function(z){if(z.keyCode===27){$(this).trigger("blur")}else{if(z.keyCode===13){var x=t.slider("option","min"),v=t.slider("option","max"),y=function(A){return(isNaN(A)||A>v||A<x)},w=$(this).val();if(!multi_value){w=parseFloat(w);if(y(w)){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}else{w=w.split("-");w=[parseFloat(w[0]),parseFloat(w[1])];if(y(w[0])||y(w[1])){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}t.slider((multi_value?"values":"value"),w)}}})})};if(this.parent_track){this.header_div.find(".draghandle").removeClass("draghandle").addClass("child-track-icon").addClass("icon-button");this.parent_element.addClass("child-track");this.tool=undefined}this.filters_div=$("<div/>").addClass("filters").hide();this.header_div.after(this.filters_div);this.filters_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});$.each(this.filters,function(x,s){var u=$("<div/>").addClass("slider-row").appendTo(c.filters_div);var r=$("<div/>").addClass("slider-label").appendTo(u);var z=$("<span/>").addClass("slider-name").text(s.name+" ").appendTo(r);var t=$("<span/>");var v=$("<span/>").addClass("slider-value").appendTo(r).append("[").append(t).append("]");var y=$("<div/>").addClass("slider").appendTo(u);s.control_element=$("<div/>").attr("id",s.name+"-filter-control").appendTo(y);var w=[0,0];s.control_element.slider({range:true,min:Number.MAX_VALUE,max:-Number.MIN_VALUE,values:[0,0],slide:function(A,B){w=B.values;t.text(B.values[0]+"-"+B.values[1]);setTimeout(function(){if(B.values[0]==w[0]&&B.values[1]==w[1]){var C=B.values;t.text(C[0]+"-"+C[1]);s.low=C[0];s.high=C[1];c.draw(true,true)}},50)},change:function(A,B){s.control_element.slider("option","slide").call(s.control_element,A,B)}});s.slider=s.control_element;s.slider_label=t;k(v,t,s.control_element);$("<div style='clear: both;'/>").appendTo(u)});if(this.tool){this.dynamic_tool_div=$("<div/>").addClass("dynamic-tool").hide();this.header_div.after(this.dynamic_tool_div);this.dynamic_tool_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});var n=$("<div class='tool-name'>").appendTo(this.dynamic_tool_div).text(this.tool.name);var m=this.tool.params;var c=this;$.each(this.tool.params,function(x,s){var v=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(v);var z=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var t=$("<span/>").text(s.value);var w=$("<span/>").addClass("slider-value").appendTo(u).append("[").append(t).append("]");var y=$("<div/>").addClass("slider").appendTo(v);var r=$("<div id='"+s.name+"-param-control'>").appendTo(y);r.slider({min:s.min,max:s.max,step:get_slider_step(s.min,s.max),value:s.value,slide:function(A,C){var B=C.value;s.value=B;if(0<B&&B<1){B=parseFloat(B).toFixed(2)}t.text(B)},change:function(A,B){r.slider("option","slide").call(r,A,B)}});k(w,t,r);$("<div style='clear: both;'/>").appendTo(v)});var q=$("<div>").addClass("slider-row").appendTo(this.dynamic_tool_div);var l=$("<input type='submit'>").attr("value","Run").appendTo(q);var c=this;l.click(function(){c.run_tool()})}c.child_tracks_container=$("<div/>").addClass("child-tracks-container").hide();c.container_div.append(c.child_tracks_container);if(c.display_modes!==undefined){if(c.mode_div===undefined){c.mode_div=$("<div class='right-float menubutton popup' />").appendTo(c.header_div);var e=(c.track_config&&c.track_config.values.mode?c.track_config.values.mode:c.display_modes[0]);c.mode=e;c.mode_div.text(e);var d=function(r){c.mode_div.text(r);c.mode=r;c.track_config.values.mode=r;c.tile_cache.clear();c.draw()};var a={};for(var f=0,h=c.display_modes.length;f<h;f++){var g=c.display_modes[f];a[g]=function(r){return function(){d(r)}}(g)}make_popupmenu(c.mode_div,a)}else{c.mode_div.hide()}}this.make_name_popup_menu()};$.extend(TiledTrack.prototype,Track.prototype,{make_name_popup_menu:function(){var b=this;var a={};a["Edit configuration"]=function(){var h=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},f=function(){b.track_config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},g=function(j){if((j.keyCode||j.which)===27){h()}else{if((j.keyCode||j.which)===13){f()}}};$(window).bind("keypress.check_enter_esc",g);show_modal("Configure Track",b.track_config.build_form(),{Cancel:h,OK:f})};if(b.filters_available>0){var e=(b.filters_div.is(":visible")?"Hide filters":"Show filters");a[e]=function(){b.filters_visible=(b.filters_div.is(":visible"));b.filters_div.toggle();b.make_name_popup_menu()}}if(b.tool){var e=(b.dynamic_tool_div.is(":visible")?"Hide tool":"Show tool");a[e]=function(){if(!b.dynamic_tool_div.is(":visible")){b.update_name(b.name+b.tool_region_and_parameters_str())}else{menu_option_text="Show dynamic tool";b.revert_name()}b.dynamic_tool_div.toggle();b.make_name_popup_menu()}}if(b.valid_chroms){a["List chrom/contigs with data"]=function(){show_modal("Chrom/contigs with data","<p>"+b.valid_chroms.join("<br/>")+"</p>",{Close:function(){hide_modal()}})}}var c=view;var d=function(){$("#no-tracks").show()};if(this.parent_track){c=this.parent_track;d=function(){}}a.Remove=function(){c.remove_track(b);if(c.num_tracks===0){d()}};make_popupmenu(b.name_div,a)},draw:function(a,d){var s=this.view.low,g=this.view.high,j=g-s,l=this.view.container.width(),m=this.view.resolution;var e=$("<div style='position: relative;'></div>"),f=l/j;if(!d){this.content_div.children().remove()}this.content_div.append(e);this.max_height=0;var o=Math.floor(s/m/DENSITY);var c={};while((o*DENSITY*m)<g){var r=l+"_"+f+"_"+o;var h=this.tile_cache.get(r);var p=o*DENSITY*this.view.resolution;var b=p+DENSITY*this.view.resolution;if(!a&&h){this.show_tile(h,e,p,f)}else{this.delayed_draw(a,r,p,b,o,m,e,f,c)}o+=1}var k=this;var q=setInterval(function(){if(obj_length(c)===0){clearInterval(q);if(d){var v=k.content_div.children();var u=false;for(var w=v.length-1,t=0;w>=t;w--){var z=$(v[w]);if(u){z.remove()}else{if(z.children().length!==0){u=true}}}}for(var y=0;y<k.filters.length;y++){k.filters[y].update_ui_elt()}var x=false;if(k.example_feature){for(var y=0;y<k.filters.length;y++){if(k.filters[y].applies_to(k.example_feature)){x=true;break}}}if(k.filters_available!==x){k.filters_available=x;if(!k.filters_available){k.filters_div.hide()}k.make_name_popup_menu()}}},50);for(var n=0;n<this.child_tracks.length;n++){this.child_tracks[n].draw(a,d)}},delayed_draw:function(b,j,g,l,c,e,k,m,f){var d=this;var h=function(u,n,p,o,s,t,q){returned_tile=d.draw_tile(n,p,o,s,t,q);var r=$("<div class='track-tile'>").prepend(returned_tile);tile_element=r;d.tile_cache.set(j,tile_element);d.show_tile(tile_element,s,g,t);delete f[u]};var a=setTimeout(function(){if(g<=d.view.high&&l>=d.view.low){var n=(b?undefined:d.tile_cache.get(j));if(n){d.show_tile(n,k,g,m);delete f[a]}else{$.when(d.data_cache.get_data(view.chrom,g,l,d.mode,e,d.data_url_extra_params)).then(function(){var o=d.data_cache.get_data(view.chrom,g,l,d.mode,e,d.data_url_extra_params);if(view.reference_track&&m>CHAR_WIDTH_PX){$.when(view.reference_track.data_cache.get_data(view.chrom,g,l,d.mode,e,view.reference_track.data_url_extra_params)).then(function(){var p=view.reference_track.data_cache.get_data(view.chrom,g,l,d.mode,e,view.reference_track.data_url_extra_params);h(a,o,e,c,k,m,p)})}else{h(a,o,e,c,k,m)}})}}},50);f[a]=true},show_tile:function(a,f,d,g){var b=this;var c=this.view.high-this.view.low,e=(d-this.view.low)*g;if(this.left_offset){e-=this.left_offset}a.css({position:"absolute",top:0,left:e,height:""});f.append(a);b.max_height=Math.max(b.max_height,a.height());b.content_div.css("height",b.max_height+"px");f.children().css("height",b.max_height+"px")},set_overview:function(){var a=this.view;if(this.initial_canvas&&this.is_overview){a.overview_close.show();a.overview_viewport.append(this.initial_canvas);a.overview_highlight.show().height(this.initial_canvas.height());a.overview_viewport.height(this.initial_canvas.height()+a.overview_box.height())}$(window).trigger("resize")},run_tool:function(){var b={dataset_id:this.original_dataset_id,chrom:this.view.chrom,low:this.view.low,high:this.view.high,tool_id:this.tool.name};$.extend(b,this.tool.get_param_values_dict());var d=this,c=b.tool_id+d.tool_region_and_parameters_str(b.chrom,b.low,b.high),e;if(d.track_type==="FeatureTrack"){e=new ToolDataFeatureTrack(c,view,d.hda_ldda,undefined,{},{},d)}this.add_track(e);e.content_div.text("Starting job.");view.has_changes=true;var a=function(){$.getJSON(run_tool_url,b,function(f){if(f==="no converter"){e.container_div.addClass("error");e.content_div.text(DATA_NOCONVERTER)}else{if(f.error){e.container_div.addClass("error");e.content_div.text(DATA_CANNOT_RUN_TOOL+f.message)}else{if(f==="pending"){e.container_div.addClass("pending");e.content_div.text("Converting input data so that it can be easily reused.");setTimeout(a,2000)}else{e.dataset_id=f.dataset_id;e.content_div.text("Running job.");e.init()}}}})};a()},tool_region_and_parameters_str:function(c,a,d){var b=this,e=(c!==undefined&&a!==undefined&&d!==undefined?c+":"+a+"-"+d:"all");return" - region=["+e+"], parameters=["+b.tool.get_param_values().join(", ")+"]"},add_track:function(a){a.track_id=this.track_id+"_"+this.child_tracks.length;a.container_div.attr("id","track_"+a.track_id);this.child_tracks_container.append(a.container_div);sortable(a.container_div,".child-track-icon");if(!$(this.child_tracks_container).is(":visible")){this.child_tracks_container.show()}this.child_tracks.push(a)},remove_track:function(a){a.container_div.fadeOut("slow",function(){$(this).remove()})}});var LabelTrack=function(a,b){this.track_type="LabelTrack";this.hidden=true;Track.call(this,null,a,b);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.view.container.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+commatize(a)+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var ReferenceTrack=function(a){this.track_type="ReferenceTrack";this.hidden=true;Track.call(this,null,a,a.top_labeltrack);TiledTrack.call(this);a.reference_track=this;this.left_offset=200;this.height_px=12;this.font=DEFAULT_FONT;this.container_div.addClass("reference-track");this.content_div.css("background","none");this.content_div.css("min-height","0px");this.content_div.css("border","none");this.data_url=reference_url;this.data_url_extra_params={dbkey:a.dbkey};this.data_cache=new DataManager(CACHED_DATA,this,false);this.tile_cache=new Cache(CACHED_TILES_LINE)};$.extend(ReferenceTrack.prototype,TiledTrack.prototype,{draw_tile:function(m,g,b,j,n){var f=this,d=DENSITY*g;if(n>CHAR_WIDTH_PX){if(m===null){f.content_div.css("height","0px");return}var e=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(e)}e=$(e);var l=e.get(0).getContext("2d");e.get(0).width=Math.ceil(d*n+f.left_offset);e.get(0).height=f.height_px;l.font=DEFAULT_FONT;l.textAlign="center";for(var h=0,k=m.length;h<k;h++){var a=Math.round(h*n);l.fillText(m[h],a+f.left_offset,10)}return e}this.content_div.css("height","0px")}});var LineTrack=function(e,c,f,a,d){var b=this;this.track_type="LineTrack";this.display_modes=["Histogram","Line","Filled","Intensity"];this.mode="Histogram";Track.call(this,e,c,c.viewport_container);TiledTrack.call(this);this.min_height_px=16;this.max_height_px=400;this.height_px=80;this.hda_ldda=f;this.dataset_id=a;this.original_dataset_id=a;this.data_cache=new DataManager(CACHED_DATA,this);this.tile_cache=new Cache(CACHED_TILES_LINE);this.track_config=new TrackConfig({track:this,params:[{key:"color",label:"Color",type:"color",default_value:"black"},{key:"min_value",label:"Min Value",type:"float",default_value:undefined},{key:"max_value",label:"Max Value",type:"float",default_value:undefined},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.height_px,hidden:true}],saved_values:d,onchange:function(){b.vertical_range=b.prefs.max_value-b.prefs.min_value;$("#linetrack_"+b.track_id+"_minval").text(b.prefs.min_value);$("#linetrack_"+b.track_id+"_maxval").text(b.prefs.max_value);b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;this.height_px=this.track_config.values.height;this.vertical_range=this.track_config.values.max_value-this.track_config.values.min_value;(function(g){var k=false;var j=false;var h=$("<div class='track-resize'>");$(g.container_div).hover(function(){k=true;h.show()},function(){k=false;if(!j){h.hide()}});h.hide().bind("dragstart",function(l,m){j=true;m.original_height=$(g.content_div).height()}).bind("drag",function(m,n){var l=Math.min(Math.max(n.original_height+n.deltaY,g.min_height_px),g.max_height_px);$(g.content_div).css("height",l);g.height_px=l;g.draw(true)}).bind("dragend",function(l,m){g.tile_cache.clear();j=false;if(!k){h.hide()}g.track_config.values.height=g.height_px}).appendTo(g.container_div)})(this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{predraw_init:function(){var a=this,b=a.view.tracks.indexOf(a);a.vertical_range=undefined;return $.getJSON(a.data_url,{stats:true,chrom:a.view.chrom,low:null,high:null,hda_ldda:a.hda_ldda,dataset_id:a.dataset_id},function(c){a.container_div.addClass("line-track");var e=c.data;if(isNaN(parseFloat(a.prefs.min_value))||isNaN(parseFloat(a.prefs.max_value))){a.prefs.min_value=e.min;a.prefs.max_value=e.max;$("#track_"+b+"_minval").val(a.prefs.min_value);$("#track_"+b+"_maxval").val(a.prefs.max_value)}a.vertical_range=a.prefs.max_value-a.prefs.min_value;a.total_frequency=e.total_frequency;a.container_div.find(".yaxislabel").remove();var f=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_minval").text(round_1000(a.prefs.min_value));var d=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_maxval").text(round_1000(a.prefs.max_value));d.css({position:"absolute",top:"24px",left:"10px"});d.prependTo(a.container_div);f.css({position:"absolute",bottom:"2px",left:"10px"});f.prependTo(a.container_div)})},draw_tile:function(j,q,t,c,e){if(this.vertical_range===undefined){return}var o=this,u=t*DENSITY*q,a=DENSITY*q,B=q+"_"+t,v={hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,resolution:this.view.resolution};var b=document.createElement("canvas"),z=j.data;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(b)}b=$(b);b.get(0).width=Math.ceil(a*e);b.get(0).height=o.height_px;var p=b.get(0).getContext("2d"),k=false,l=o.prefs.min_value,g=o.prefs.max_value,n=o.vertical_range,w=o.total_frequency,d=o.height_px,m=o.mode;var A=Math.round(d+l/n*d);p.beginPath();p.moveTo(0,A);p.lineTo(a*e,A);p.fillStyle="#aaa";p.stroke();p.beginPath();p.fillStyle=o.prefs.color;var x,h,f;if(z.length>1){f=Math.ceil((z[1][0]-z[0][0])*e)}else{f=10}for(var r=0,s=z.length;r<s;r++){x=Math.round((z[r][0]-u)*e);h=z[r][1];if(h===null){if(k&&m==="Filled"){p.lineTo(x,d)}k=false;continue}if(h<l){h=l}else{if(h>g){h=g}}if(m==="Histogram"){h=Math.round(h/n*d);p.fillRect(x,A,f,-h)}else{if(m==="Intensity"){h=255-Math.floor((h-l)/n*255);p.fillStyle="rgb("+h+","+h+","+h+")";p.fillRect(x,0,f,d)}else{h=Math.round(d-(h-l)/n*d);if(k){p.lineTo(x,h)}else{k=true;if(m==="Filled"){p.moveTo(x,d);p.lineTo(x,h)}else{p.moveTo(x,h)}}}}}if(m==="Filled"){if(k){p.lineTo(x,A);p.lineTo(0,A)}p.fill()}else{p.stroke()}return b}});var FeatureTrack=function(a,f,e,j,h,c,d,g){var b=this;this.track_type="FeatureTrack";this.display_modes=["Auto","Dense","Squish","Pack"];this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:h,onchange:function(){b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;Track.call(this,a,f,f.viewport_container);TiledTrack.call(this,c,d,g);this.height_px=0;this.container_div.addClass("feature-track");this.hda_ldda=e;this.dataset_id=j;this.original_dataset_id=j;this.zo_slots={};this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.default_font=DEFAULT_FONT;this.inc_slots={};this.start_end_dct={};this.tile_cache=new Cache(CACHED_TILES_FEATURE);this.data_cache=new DataManager(20,this);this.left_offset=200};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{incremental_slots:function(a,h,p){var q=this.inc_slots[a];if(!q||(q.mode!==p)){q={};q.w_scale=a;q.mode=p;this.inc_slots[a]=q;this.start_end_dct[a]={}}var l=q.w_scale,w=[],x=[],j=0,n=this.view.max_low;for(var u=0,v=h.length;u<v;u++){var g=h[u],k=g[0];if(q[k]!==undefined){j=Math.max(j,q[k]);x.push(q[k])}else{w.push(u)}}var d=this.start_end_dct[a];var m=function(D,E){for(var C=0;C<=MAX_FEATURE_DEPTH;C++){var A=false,F=d[C];if(F!==undefined){for(var z=0,B=F.length;z<B;z++){var y=F[z];if(E>y[0]&&D<y[1]){A=true;break}}}if(!A){return C}}return -1};for(var u=0,v=w.length;u<v;u++){var g=h[w[u]],k=g[0],s=g[1],b=g[2],o=g[3],c=Math.floor((s-n)*l),f=Math.ceil((b-n)*l),t=CONTEXT.measureText(o).width,e;if(o!==undefined&&p==="Pack"){t+=(LABEL_SPACING+PACK_SPACING);if(c-t>=0){c-=t;e="left"}else{f+=t;e="right"}}var r=m(c,f);if(r>=0){if(d[r]===undefined){d[r]=[]}d[r].push([c,f]);q[k]=r;j=Math.max(j,r)}else{}}return j+1},draw_summary_tree:function(b,o,n,l,r,j,f,e){var c=j+LABEL_SPACING+CHAR_HEIGHT_PX;delta_x_px=Math.ceil(n*r);var h=$("<div />").addClass("yaxislabel");h.text(l);h.css({position:"absolute",top:"22px",left:"10px"});h.prependTo(this.container_div);var q=b.get(0).getContext("2d");for(var d=0,g=o.length;d<g;d++){var m=Math.floor((o[d][0]-f)*r);var k=o[d][1];if(!k){continue}var p=k/l*j;q.fillStyle="black";q.fillRect(m+e,c-p,delta_x_px,p);var a=4;if(this.prefs.show_counts&&(q.measureText(k).width+a)<delta_x_px){q.fillStyle="#666";q.textAlign="center";q.fillText(k,m+e+(delta_x_px/2),10)}}},draw_element:function(p,f,g,x,j,r,I,M,N,a,A){var u=x[0],K=x[1],C=x[2]-1,s=x[3],D=Math.floor(Math.max(0,(K-r)*M)),q=Math.ceil(Math.min(a,Math.max(0,(C-r)*M))),B=ERROR_PADDING+(g==="Dense"?0:(0+j))*N,o,G,t=null,O=null,d=this.prefs.block_color,F=this.prefs.label_color;if(g==="Dense"){p.fillStyle=d;p.fillRect(D+A,B,q-D,DENSE_FEATURE_HEIGHT)}else{if(g==="no_detail"){p.fillStyle=d;p.fillRect(D+A,B+5,q-D,DENSE_FEATURE_HEIGHT)}else{var n=x[4],z=x[5],E=x[6],e=x[7];if(z&&E){t=Math.floor(Math.max(0,(z-r)*M));O=Math.ceil(Math.min(a,Math.max(0,(E-r)*M)))}var L,v;if(g==="Squish"){L=1;v=SQUISH_FEATURE_HEIGHT}else{L=5;v=PACK_FEATURE_HEIGHT}if(!e){if(x.strand){if(x.strand==="+"){p.fillStyle=RIGHT_STRAND_INV}else{if(x.strand==="-"){p.fillStyle=LEFT_STRAND_INV}}}else{p.fillStyle=d}p.fillRect(D+A,B,q-D,v)}else{var m,w;if(g==="Squish"){p.fillStyle=CONNECTOR_COLOR;m=B+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;w=1}else{if(n){var m=B;var w=v;if(n==="+"){p.fillStyle=RIGHT_STRAND}else{if(n==="-"){p.fillStyle=LEFT_STRAND}}}else{p.fillStyle=CONNECTOR_COLOR;m+=(SQUISH_FEATURE_HEIGHT/2)+1;w=1}}p.fillRect(D+A,m,q-D,w);for(var J=0,c=e.length;J<c;J++){var h=e[J],b=Math.floor(Math.max(0,(h[0]-r)*M)),y=Math.ceil(Math.min(a,Math.max((h[1]-1-r)*M)));if(b>y){continue}p.fillStyle=d;p.fillRect(b+A,B+(v-L)/2+1,y-b,L);if(t!==undefined&&!(b>O||y<t)){var H=Math.max(b,t),l=Math.min(y,O);p.fillRect(H+A,B+1,l-H,v)}}}if(g==="Pack"&&K>r){p.fillStyle=F;if(f===0&&D-p.measureText(s).width<0){p.textAlign="left";p.fillText(s,q+A+LABEL_SPACING,B+8)}else{p.textAlign="right";p.fillText(s,D+A-LABEL_SPACING,B+8)}p.fillStyle=d}}}},get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="no_detail"){a=NO_DETAIL_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT}}}return a},draw_tile:function(o,x,C,k,m,d){var t=this;var E=C*DENSITY*x,a=(C+1)*DENSITY*x,q=a-E,F={hda_ldda:t.hda_ldda,dataset_id:t.dataset_id,resolution:this.view.resolution,mode:this.mode};var v=Math.ceil(q*m),s=this.mode,H=25,g=this.left_offset,p,h;var c=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(c)}c=$(c);if(s==="Auto"){if(o.dataset_type==="summary_tree"){s=o.dataset_type}else{if(o.extra_info==="no_detail"){s="no_detail"}else{var G=o.data;if((G.length&&G.length<4)||(this.view.high-this.view.low>MIN_SQUISH_VIEW_WIDTH)){s="Squish"}else{s="Pack"}}}}var y=this.get_y_scale(s);if(s==="summary_tree"){h=this.summary_draw_height}if(s==="Dense"){h=H}else{if(s==="no_detail"||s==="Squish"||s==="Pack"){h=this.incremental_slots(m,o.data,s)*y+H;p=this.inc_slots[m]}}c.get(0).width=v+g;c.get(0).height=h;if(o.dataset_type==="summary_tree"){c.get(0).height+=LABEL_SPACING+CHAR_HEIGHT_PX}k.parent().css("height",Math.max(this.height_px,h)+"px");var w=c.get(0).getContext("2d");w.fillStyle=this.prefs.block_color;w.font=this.default_font;w.textAlign="right";this.container_div.find(".yaxislabel").remove();if(s==="summary_tree"){this.draw_summary_tree(c,o.data,o.delta,o.max,m,h,E,g);return c}if(o.message){c.css({"border-top":"1px solid red"});w.fillStyle="red";w.textAlign="left";var r=w.textBaseline;w.textBaseline="top";w.fillText(o.message,g,0);w.textBaseline=r;if(!o.data){return c}}this.example_feature=(o.data.length?o.data[0]:undefined);var G=o.data;for(var z=0,B=G.length;z<B;z++){var j=G[z],l=j[0],u=j[1],b=j[2],e=(p&&p[l]!==undefined?p[l]:null);var A=false;var n;for(var D=0;D<this.filters.length;D++){n=this.filters[D];n.update_attrs(j);if(!n.keep(j)){A=true;break}}if(A){continue}if(is_overlap([u,b],[E,a])&&(s=="Dense"||e!==null)){this.draw_element(w,C,s,j,e,E,a,m,y,v,g,d)}}return c}});var VcfTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_type="VcfTrack"};$.extend(VcfTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{draw_element:function(u,p,j,e,x,c,m,v,s){var j=data[i],l=j[0],t=j[1],d=j[2]-1,o=j[3],g=Math.floor(Math.max(0,(t-x)*m)),k=Math.ceil(Math.min(s,Math.max(0,(d-x)*m))),f=ERROR_PADDING+(p==="Dense"?0:(0+e))*v,a,y,b=null,n=null;if(no_label){u.fillStyle=block_color;u.fillRect(g+left_offset,f+5,k-g,1)}else{var w=j[4],r=j[5],h=j[6];a=9;y=1;u.fillRect(g+left_offset,f,k-g,a);if(p!=="Dense"&&o!==undefined&&t>x){u.fillStyle=label_color;if(tile_index===0&&g-u.measureText(o).width<0){u.textAlign="left";u.fillText(o,k+2+left_offset,f+8)}else{u.textAlign="right";u.fillText(o,g-2+left_offset,f+8)}u.fillStyle=block_color}var q=w+" / "+r;if(t>x&&u.measureText(q).width<(k-g)){u.fillStyle="white";u.textAlign="center";u.fillText(q,left_offset+g+(k-g)/2,f+8);u.fillStyle=block_color}}}});var ReadTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:c,onchange:function(){this.track.tile_cache.clear();this.track.draw()}});this.prefs=this.track_config.values;this.track_type="ReadTrack";this.make_name_popup_menu()};$.extend(ReadTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT;if(this.track_config.values.show_insertions){a*=2}}}return a},draw_read:function(m,d,J,n,D,G,e,o,y,a){m.textAlign="center";var l=this,u=[n,D],C=0,z=0,f=0;var r=[];if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){f=Math.round(J/2)}if(!e){e=[[0,o.length]]}for(var h=0,k=e.length;h<k;h++){var b=e[h],g="MIDNSHP=X"[b[0]],v=b[1];if(g==="H"||g==="S"){C-=v}var x=G+C,B=Math.floor(Math.max(0,(x-n)*J)),E=Math.floor(Math.max(0,(x+v-n)*J));switch(g){case"H":break;case"S":case"M":case"=":var p=compute_overlap([x,x+v],u);if(p!==NO_OVERLAP){var q=o.slice(z,z+v);if(f>0){m.fillStyle=this.prefs.block_color;m.fillRect(B+this.left_offset-f,y+1,E-B,9);m.fillStyle=CONNECTOR_COLOR;for(var I=0,j=q.length;I<j;I++){if(this.track_config.values.show_differences&&a){var s=a[x-n+I];if(!s||s.toLowerCase()===q[I].toLowerCase()){continue}}if(x+I>=n&&x+I<=D){var A=Math.floor(Math.max(0,(x+I-n)*J));m.fillText(q[I],A+this.left_offset,y+9)}}}else{m.fillStyle=this.prefs.block_color;m.fillRect(B+this.left_offset,y+(this.mode!=="Dense"?4:5),E-B,(d!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}z+=v;C+=v;break;case"N":m.fillStyle=CONNECTOR_COLOR;m.fillRect(B+this.left_offset-f,y+5,E-B,1);C+=v;break;case"D":m.fillStyle="red";m.fillRect(B+this.left_offset-f,y+4,E-B,3);C+=v;break;case"P":break;case"I":var p=compute_overlap([x,x+v],u),K=this.left_offset+B-f;if(p!==NO_OVERLAP){var q=o.slice(z,z+v);if(this.track_config.values.show_insertions){var w=this.left_offset+B-(E-B)/2;if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){m.fillStyle="yellow";m.fillRect(w-f,y-9,E-B,9);r[r.length]={type:"triangle",data:[K,y+4,5]};m.fillStyle=CONNECTOR_COLOR;switch(p){case (OVERLAP_START):q=q.slice(n-x);break;case (OVERLAP_END):q=q.slice(0,x-D);break;case (CONTAINED_BY):break;case (CONTAINS):q=q.slice(n-x,x-D);break}for(var I=0,j=q.length;I<j;I++){var A=Math.floor(Math.max(0,(x+I-n)*J));m.fillText(q[I],A+this.left_offset-(E-B)/2,y)}}else{m.fillStyle="yellow";m.fillRect(w,y+(this.mode!=="Dense"?2:5),E-B,(d!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){r[r.length]={type:"text",data:[q.length,K,y+9]}}else{}}}z+=v;break;case"X":z+=v;break}}m.fillStyle="yellow";var t,L,H;for(var F=0;F<r.length;F++){t=r[F];L=t.type;H=t.data;if(L==="text"){m.font="bold "+DEFAULT_FONT;m.fillText(H[0],H[1],H[2]);m.font=DEFAULT_FONT}else{if(L=="triangle"){m.drawDownwardEquilateralTriangle(H[0],H[1],H[2])}}}},draw_element:function(w,y,r,k,e,A,b,n,x,u,f,c){var m=k[0],v=k[1],d=k[2],o=k[3],h=Math.floor(Math.max(0,(v-A)*n)),j=Math.ceil(Math.min(u,Math.max(0,(d-A)*n))),g=(r==="Dense"?1:(1+e))*x,z=this.prefs.block_color,l=this.prefs.label_color;t=0;w.fillStyle=z;if(k[5] instanceof Array){var s=Math.floor(Math.max(0,(k[4][0]-A)*n)),q=Math.ceil(Math.min(u,Math.max(0,(k[4][1]-A)*n))),p=Math.floor(Math.max(0,(k[5][0]-A)*n)),a=Math.ceil(Math.min(u,Math.max(0,(k[5][1]-A)*n)));if(k[4][1]>=A&&k[4][0]<=b&&k[4][2]){this.draw_read(w,r,n,A,b,k[4][0],k[4][2],k[4][3],g,c)}if(k[5][1]>=A&&k[5][0]<=b&&k[5][2]){this.draw_read(w,r,n,A,b,k[5][0],k[5][2],k[5][3],g,c)}if(p>q){w.fillStyle=CONNECTOR_COLOR;w.dashedLine(q+f,g+5,f+p,g+5)}}else{w.fillStyle=z;this.draw_read(w,r,n,A,b,v,k[4],k[5],g,c)}if(r==="Pack"&&v>A){w.fillStyle=this.prefs.label_color;if((r==="Pack"||this.mode==="Auto")&&n>CHAR_WIDTH_PX){var t=Math.round(n/2)}if(y===0&&h-w.measureText(o).width<0){w.textAlign="left";w.fillText(o,j+f+LABEL_SPACING-t,g+8)}else{w.textAlign="right";w.fillText(o,h+f-LABEL_SPACING-t,g+8)}w.fillStyle=z}}});var ToolDataFeatureTrack=function(e,c,g,a,d,f,b){FeatureTrack.call(this,e,c,g,a,d,f,{},b);this.track_type="ToolDataFeatureTrack";this.data_url=raw_data_url;this.data_query_wait=1000;this.dataset_check_url=dataset_state_url};$.extend(ToolDataFeatureTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{predraw_init:function(){var b=this;var a=function(){if(b.data_cache.size()===0){setTimeout(a,300)}else{b.data_url=default_data_url;b.data_query_wait=DEFAULT_DATA_QUERY_WAIT;b.dataset_state_url=converted_datasets_state_url;$.getJSON(b.dataset_state_url,{dataset_id:b.dataset_id,hda_ldda:b.hda_ldda},function(c){})}};a()}});
\ No newline at end of file
+CanvasRenderingContext2D.prototype.dashedLine=function(c,j,b,h,f){if(f===undefined){f=4}var e=b-c;var d=h-j;var g=Math.floor(Math.sqrt(e*e+d*d)/f);var l=e/g;var k=d/g;var a;for(a=0;a<g;a++,c+=l,j+=k){if(a%2!==0){continue}this.fillRect(c,j,f,1)}};CanvasRenderingContext2D.prototype.drawDownwardEquilateralTriangle=function(b,a,e){var d=b-e/2,c=b+e/2,f=a-Math.sqrt(e*3/2);this.beginPath();this.moveTo(d,f);this.lineTo(c,f);this.lineTo(b,a);this.lineTo(d,f);this.strokeStyle=this.fillStyle;this.fill();this.stroke();this.closePath()};function sortable(a,b){a.bind("drag",{handle:b,relative:true},function(h,j){var g=$(this).parent();var f=g.children();var c;for(c=0;c<f.length;c++){if(j.offsetY<$(f.get(c)).position().top){break}}if(c===f.length){if(this!==f.get(c-1)){g.append(this)}}else{if(this!==f.get(c)){$(this).insertBefore(f.get(c))}}})}var NO_OVERLAP=1001,CONTAINS=1002,OVERLAP_START=1003,OVERLAP_END=1004,CONTAINED_BY=1005;function compute_overlap(e,b){var g=e[0],f=e[1],d=b[0],c=b[1],a;if(g<d){if(f<d){a=NO_OVERLAP}else{if(f<=c){a=OVERLAP_START}else{a=CONTAINS}}}else{if(g>c){a=NO_OVERLAP}else{if(f<=c){a=CONTAINED_BY}else{a=OVERLAP_END}}}return a}function is_overlap(b,a){return(compute_overlap(b,a)!==NO_OVERLAP)}function get_slider_step(c,a){var b=a-c;return(b<=1?0.01:(b<=1000?1:5))}var DENSE_TRACK_HEIGHT=10,NO_DETAIL_TRACK_HEIGHT=3,SQUISH_TRACK_HEIGHT=5,PACK_TRACK_HEIGHT=10,NO_DETAIL_FEATURE_HEIGHT=1,DENSE_FEATURE_HEIGHT=1,SQUISH_FEATURE_HEIGHT=3,PACK_FEATURE_HEIGHT=9,ERROR_PADDING=10,LABEL_SPACING=2,PACK_SPACING=5,MIN_SQUISH_VIEW_WIDTH=12000,DEFAULT_FONT="9px Monaco, Lucida Console, monospace",DENSITY=200,FEATURE_LEVELS=10,MAX_FEATURE_DEPTH=100,DEFAULT_DATA_QUERY_WAIT=5000,MAX_CHROMS_SELECTABLE=100,CONNECTOR_COLOR="#ccc",DATA_ERROR="There was an error in indexing this dataset. ",DATA_NOCONVERTER="A converter for this dataset is not installed. Please check your datatypes_conf.xml file.",DATA_NONE="No data for this chrom/contig.",DATA_PENDING="Currently indexing... please wait",DATA_CANNOT_RUN_TOOL="Tool cannot be rerun: ",DATA_LOADING="Loading data...",DATA_OK="Ready for display",CACHED_TILES_FEATURE=10,CACHED_TILES_LINE=5,CACHED_DATA=5,DUMMY_CANVAS=document.createElement("canvas"),RIGHT_STRAND,LEFT_STRAND;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(DUMMY_CANVAS)}var CONTEXT=DUMMY_CANVAS.getContext("2d");CONTEXT.font=DEFAULT_FONT;var CHAR_WIDTH_PX=CONTEXT.measureText("A").width,CHAR_HEIGHT_PX=9;var right_img=new Image();right_img.src=image_path+"/visualization/strand_right.png";right_img.onload=function(){RIGHT_STRAND=CONTEXT.createPattern(right_img,"repeat")};var left_img=new Image();left_img.src=image_path+"/visualization/strand_left.png";left_img.onload=function(){LEFT_STRAND=CONTEXT.createPattern(left_img,"repeat")};var right_img_inv=new Image();right_img_inv.src=image_path+"/visualization/strand_right_inv.png";right_img_inv.onload=function(){RIGHT_STRAND_INV=CONTEXT.createPattern(right_img_inv,"repeat")};var left_img_inv=new Image();left_img_inv.src=image_path+"/visualization/strand_left_inv.png";left_img_inv.onload=function(){LEFT_STRAND_INV=CONTEXT.createPattern(left_img_inv,"repeat")};function round_1000(a){return Math.round(a*1000)/1000}var Cache=function(a){this.num_elements=a;this.clear()};$.extend(Cache.prototype,{get:function(b){var a=this.key_ary.indexOf(b);if(a!==-1){this.move_key_to_end(b,a)}return this.obj_cache[b]},set:function(b,c){if(!this.obj_cache[b]){if(this.key_ary.length>=this.num_elements){var a=this.key_ary.shift();delete this.obj_cache[a]}this.key_ary.push(b)}this.obj_cache[b]=c;return c},move_key_to_end:function(b,a){this.key_ary.splice(a,1);this.key_ary.push(b)},clear:function(){this.obj_cache={};this.key_ary=[]},size:function(){return this.key_ary.length}});var DataManager=function(b,a,c){Cache.call(this,b);this.track=a;this.subset=(c!==undefined?c:true)};$.extend(DataManager.prototype,Cache.prototype,{load_data:function(h,j,d,g,a,f){var c={chrom:h,low:j,high:d,mode:g,resolution:a,dataset_id:this.track.dataset_id,hda_ldda:this.track.hda_ldda};$.extend(c,f);var k=[];for(var e=0;e<this.track.filters.length;e++){k[k.length]=this.track.filters[e].name}c.filter_cols=JSON.stringify(k);var b=this;return $.getJSON(this.track.data_url,c,function(l){b.set_data(j,d,g,l)})},get_data:function(j,k,c,h,b,f){var l=this.get(this.gen_key(k,c,h));if(l){return l}if(this.subset){var m,g,a,e,h,l;for(var d=0;d<this.key_ary.length;d++){m=this.key_ary[d];g=this.split_key(m);a=g[0];e=g[1];if(k>=a&&c<=e){l=this.obj_cache[m];if(l.dataset_type!=="summary_tree"&&l.extra_info!=="no_detail"){this.move_key_to_end(m,d);return l}}}}return this.load_data(j,k,c,h,b,f)},set_data:function(b,c,d,a){return this.set(this.gen_key(b,c,d),a)},gen_key:function(a,c,d){var b=a+"_"+c+"_"+d;return b},split_key:function(a){return a.split("_")}});var View=function(a,d,c,b,e){this.container=a;this.chrom=null;this.vis_id=c;this.dbkey=b;this.title=d;this.tracks=[];this.label_tracks=[];this.max_low=0;this.max_high=0;this.num_tracks=0;this.track_id_counter=0;this.zoom_factor=3;this.min_separation=30;this.has_changes=false;this.init(e);this.reset()};$.extend(View.prototype,{init:function(d){var c=this.container,a=this;this.top_container=$("<div/>").addClass("top-container").appendTo(c);this.content_div=$("<div/>").addClass("content").css("position","relative").appendTo(c);this.bottom_container=$("<div/>").addClass("bottom-container").appendTo(c);this.top_labeltrack=$("<div/>").addClass("top-labeltrack").appendTo(this.top_container);this.viewport_container=$("<div/>").addClass("viewport-container").addClass("viewport-container").appendTo(this.content_div);this.intro_div=$("<div/>").addClass("intro").text("Select a chrom from the dropdown below").hide();this.nav_labeltrack=$("<div/>").addClass("nav-labeltrack").appendTo(this.bottom_container);this.nav_container=$("<div/>").addClass("nav-container").prependTo(this.top_container);this.nav=$("<div/>").addClass("nav").appendTo(this.nav_container);this.overview=$("<div/>").addClass("overview").appendTo(this.bottom_container);this.overview_viewport=$("<div/>").addClass("overview-viewport").appendTo(this.overview);this.overview_close=$("<a href='javascript:void(0);'>Close Overview</a>").addClass("overview-close").hide().appendTo(this.overview_viewport);this.overview_highlight=$("<div/>").addClass("overview-highlight").hide().appendTo(this.overview_viewport);this.overview_box_background=$("<div/>").addClass("overview-boxback").appendTo(this.overview_viewport);this.overview_box=$("<div/>").addClass("overview-box").appendTo(this.overview_viewport);this.default_overview_height=this.overview_box.height();this.nav_controls=$("<div/>").addClass("nav-controls").appendTo(this.nav);this.chrom_select=$("<select/>").attr({name:"chrom"}).css("width","15em").addClass("no-autocomplete").append("<option value=''>Loading</option>").appendTo(this.nav_controls);var b=function(f){if(f.type==="focusout"||(f.keyCode||f.which)===13||(f.keyCode||f.which)===27){if((f.keyCode||f.which)!==27){a.go_to($(this).val())}$(this).hide();$(this).val("");a.location_span.show();a.chrom_select.show()}};this.nav_input=$("<input/>").addClass("nav-input").hide().bind("keyup focusout",b).appendTo(this.nav_controls);this.location_span=$("<span/>").addClass("location").appendTo(this.nav_controls);this.location_span.bind("click",function(){a.location_span.hide();a.chrom_select.hide();a.nav_input.val(a.chrom+":"+a.low+"-"+a.high);a.nav_input.css("display","inline-block");a.nav_input.select();a.nav_input.focus()});if(this.vis_id!==undefined){this.hidden_input=$("<input/>").attr("type","hidden").val(this.vis_id).appendTo(this.nav_controls)}this.zo_link=$("<a id='zoom-out' />").click(function(){a.zoom_out();a.redraw()}).appendTo(this.nav_controls);this.zi_link=$("<a id='zoom-in' />").click(function(){a.zoom_in();a.redraw()}).appendTo(this.nav_controls);this.load_chroms({low:0},d);this.chrom_select.bind("change",function(){a.change_chrom(a.chrom_select.val())});this.intro_div.show();this.content_div.bind("click",function(f){$(this).find("input").trigger("blur")});this.content_div.bind("dblclick",function(f){a.zoom_in(f.pageX,this.viewport_container)});this.overview_box.bind("dragstart",function(f,g){this.current_x=g.offsetX}).bind("drag",function(f,h){var j=h.offsetX-this.current_x;this.current_x=h.offsetX;var g=Math.round(j/a.viewport_container.width()*(a.max_high-a.max_low));a.move_delta(-g)});this.overview_close.bind("click",function(){for(var f=0,e=a.tracks.length;f<e;f++){a.tracks[f].is_overview=false}$(this).siblings().filter("canvas").remove();$(this).parent().css("height",a.overview_box.height());a.overview_highlight.hide();$(this).hide()});this.viewport_container.bind("draginit",function(f,g){if(f.clientX>a.viewport_container.width()-16){return false}}).bind("dragstart",function(f,g){g.original_low=a.low;g.current_height=f.clientY;g.current_x=g.offsetX}).bind("drag",function(h,k){var f=$(this);var l=k.offsetX-k.current_x;var g=f.scrollTop()-(h.clientY-k.current_height);f.scrollTop(g);k.current_height=h.clientY;k.current_x=k.offsetX;var j=Math.round(l/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}).bind("mousewheel",function(h,k,g,f){if(g){var j=Math.round(-g/a.viewport_container.width()*(a.high-a.low));a.move_delta(j)}});this.top_labeltrack.bind("dragstart",function(f,g){return $("<div />").css({height:a.content_div.height()+a.top_labeltrack.height()+a.nav_labeltrack.height()+1,top:"0px",position:"absolute","background-color":"#ccf",opacity:0.5,"z-index":1000}).appendTo($(this))}).bind("drag",function(k,l){$(l.proxy).css({left:Math.min(k.pageX,l.startX),width:Math.abs(k.pageX-l.startX)});var g=Math.min(k.pageX,l.startX)-a.container.offset().left,f=Math.max(k.pageX,l.startX)-a.container.offset().left,j=(a.high-a.low),h=a.viewport_container.width();a.update_location(Math.round(g/h*j)+a.low,Math.round(f/h*j)+a.low)}).bind("dragend",function(l,m){var g=Math.min(l.pageX,m.startX),f=Math.max(l.pageX,m.startX),j=(a.high-a.low),h=a.viewport_container.width(),k=a.low;a.low=Math.round(g/h*j)+k;a.high=Math.round(f/h*j)+k;$(m.proxy).remove();a.redraw()});this.add_label_track(new LabelTrack(this,this.top_labeltrack));this.add_label_track(new LabelTrack(this,this.nav_labeltrack));$(window).bind("resize",function(){a.resize_window()});$(document).bind("redraw",function(){a.redraw()});this.reset();$(window).trigger("resize")},update_location:function(a,b){this.location_span.text(commatize(a)+" - "+commatize(b));this.nav_input.val(this.chrom+":"+commatize(a)+"-"+commatize(b))},load_chroms:function(b,c){b.num=MAX_CHROMS_SELECTABLE;$.extend(b,(this.vis_id!==undefined?{vis_id:this.vis_id}:{dbkey:this.dbkey}));var a=this;$.ajax({url:chrom_url,data:b,dataType:"json",success:function(e){if(e.chrom_info.length===0){alert("Invalid chromosome: "+b.chrom);return}if(e.reference){a.add_label_track(new ReferenceTrack(a))}a.chrom_data=e.chrom_info;var h='<option value="">Select Chrom/Contig</option>';for(var g=0,d=a.chrom_data.length;g<d;g++){var f=a.chrom_data[g].chrom;h+='<option value="'+f+'">'+f+"</option>"}if(e.prev_chroms){h+='<option value="previous">Previous '+MAX_CHROMS_SELECTABLE+"</option>"}if(e.next_chroms){h+='<option value="next">Next '+MAX_CHROMS_SELECTABLE+"</option>"}a.chrom_select.html(h);if(c){c()}a.chrom_start_index=e.start_index},error:function(){alert("Could not load chroms for this dbkey:",a.dbkey)}})},change_chrom:function(e,b,g){if(!e||e==="None"){return}var d=this;if(e==="previous"){d.load_chroms({low:this.chrom_start_index-MAX_CHROMS_SELECTABLE});return}if(e==="next"){d.load_chroms({low:this.chrom_start_index+MAX_CHROMS_SELECTABLE});return}var f=$.grep(d.chrom_data,function(j,k){return j.chrom===e})[0];if(f===undefined){d.load_chroms({chrom:e},function(){d.change_chrom(e,b,g)});return}else{if(e!==d.chrom){d.chrom=e;if(!d.chrom){d.intro_div.show()}else{d.intro_div.hide()}d.chrom_select.val(d.chrom);d.max_high=f.len-1;d.reset();d.redraw(true);for(var h=0,a=d.tracks.length;h<a;h++){var c=d.tracks[h];if(c.init){c.init()}}}if(b!==undefined&&g!==undefined){d.low=Math.max(b,0);d.high=Math.min(g,d.max_high)}d.reset_overview();d.redraw()}},go_to:function(f){var k=this,a,d,b=f.split(":"),h=b[0],j=b[1];if(j!==undefined){try{var g=j.split("-");a=parseInt(g[0].replace(/,/g,""),10);d=parseInt(g[1].replace(/,/g,""),10)}catch(c){return false}}k.change_chrom(h,a,d)},move_fraction:function(c){var a=this;var b=a.high-a.low;this.move_delta(c*b)},move_delta:function(c){var a=this;var b=a.high-a.low;if(a.low-c<a.max_low){a.low=a.max_low;a.high=a.max_low+b}else{if(a.high-c>a.max_high){a.high=a.max_high;a.low=a.max_high-b}else{a.high-=c;a.low-=c}}a.redraw()},add_track:function(a){a.view=this;a.track_id=this.track_id_counter;this.tracks.push(a);if(a.init){a.init()}a.container_div.attr("id","track_"+a.track_id);sortable(a.container_div,".draghandle");this.track_id_counter+=1;this.num_tracks+=1},add_label_track:function(a){a.view=this;this.label_tracks.push(a)},remove_track:function(a){this.has_changes=true;a.container_div.fadeOut("slow",function(){$(this).remove()});delete this.tracks[this.tracks.indexOf(a)];this.num_tracks-=1},reset:function(){this.low=this.max_low;this.high=this.max_high;this.viewport_container.find(".yaxislabel").remove()},redraw:function(h){var g=this.high-this.low,f=this.low,b=this.high;if(f<this.max_low){f=this.max_low}if(b>this.max_high){b=this.max_high}if(this.high!==0&&g<this.min_separation){b=f+this.min_separation}this.low=Math.floor(f);this.high=Math.ceil(b);this.resolution=Math.pow(10,Math.ceil(Math.log((this.high-this.low)/200)/Math.LN10));this.zoom_res=Math.pow(FEATURE_LEVELS,Math.max(0,Math.ceil(Math.log(this.resolution,FEATURE_LEVELS)/Math.log(FEATURE_LEVELS))));var a=(this.low/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var e=((this.high-this.low)/(this.max_high-this.max_low)*this.overview_viewport.width())||0;var j=13;this.overview_box.css({left:a,width:Math.max(j,e)}).show();if(e<j){this.overview_box.css("left",a-(j-e)/2)}if(this.overview_highlight){this.overview_highlight.css({left:a,width:e})}this.update_location(this.low,this.high);if(!h){for(var c=0,d=this.tracks.length;c<d;c++){if(this.tracks[c]&&this.tracks[c].enabled){this.tracks[c].draw()}}for(c=0,d=this.label_tracks.length;c<d;c++){this.label_tracks[c].draw()}}},zoom_in:function(b,c){if(this.max_high===0||this.high-this.low<this.min_separation){return}var d=this.high-this.low,e=d/2+this.low,a=(d/this.zoom_factor)/2;if(b){e=b/this.viewport_container.width()*(this.high-this.low)+this.low}this.low=Math.round(e-a);this.high=Math.round(e+a);this.redraw()},zoom_out:function(){if(this.max_high===0){return}var b=this.high-this.low,c=b/2+this.low,a=(b*this.zoom_factor)/2;this.low=Math.round(c-a);this.high=Math.round(c+a);this.redraw()},resize_window:function(){this.viewport_container.height(this.container.height()-this.top_container.height()-this.bottom_container.height());this.nav_container.width(this.container.width());this.redraw()},reset_overview:function(){this.overview_viewport.find("canvas").remove();this.overview_viewport.height(this.default_overview_height);this.overview_box.height(this.default_overview_height);this.overview_close.hide();this.overview_highlight.hide()}});var Tool=function(g){this.name=g.name;this.params=[];var a=g.params;for(var e=0;e<a.length;e++){var d=a[e],c=d.name,b=d.label,f=d.type;if(f==="int"||f==="float"){this.params[this.params.length]=new NumberParameter(c,b,d.min,d.max,d.value)}else{if(f=="select"){this.params[this.params.length]=new SelectParameter(c,b,d.options)}else{console.log("WARNING: unrecognized tool parameter type:",c,f)}}}};$.extend(Tool.prototype,{get_param_values_dict:function(){var b={};for(var a=0;a<this.params.length;a++){var c=this.params[a];b[c.name]=JSON.stringify(c.value)}return b},get_param_values:function(){var b=[];for(var a=0;a<this.params.length;a++){b[a]=this.params[a].value}return b}});var NumberParameter=function(c,b,e,a,d){this.name=c;this.label=b;this.min=e;this.max=a;this.value=d};var SelectParameter=function(c,b,a){this.name=c;this.label=b;this.options=a;this.value=(a.length!==0?a[0][1]:null)};var Filter=function(b,a,c){this.name=b;this.index=a;this.value=c};var NumberFilter=function(b,a){this.name=b;this.index=a;this.low=-Number.MAX_VALUE;this.high=Number.MAX_VALUE;this.min=Number.MAX_VALUE;this.max=-Number.MAX_VALUE;this.slider=null;this.slider_label=null};$.extend(NumberFilter.prototype,{applies_to:function(a){if(a.length>this.index){return true}return false},keep:function(a){if(!this.applies_to(a)){return true}return(a[this.index]>=this.low&&a[this.index]<=this.high)},update_attrs:function(b){var a=false;if(!this.applies_to(b)){return a}if(b[this.index]<this.min){this.min=Math.floor(b[this.index]);a=true}if(b[this.index]>this.max){this.max=Math.ceil(b[this.index]);a=true}return a},update_ui_elt:function(){var b=this.slider.slider("option","min"),a=this.slider.slider("option","max");if(this.min<b||this.max>a){this.slider.slider("option","min",this.min);this.slider.slider("option","max",this.max);this.slider.slider("option","step",get_slider_step(this.min,this.max));this.slider.slider("option","values",[this.min,this.max])}}});var get_filters_from_dict=function(a){var g=[];for(var d=0;d<a.length;d++){var f=a[d];var c=f.name,e=f.type,b=f.index;if(e==="int"||e==="float"){g[d]=new NumberFilter(c,b)}else{g[d]=new Filter(c,b,e)}}return g};var TrackConfig=function(a){this.track=a.track;this.params=a.params;this.values={};if(a.saved_values){this.restore_values(a.saved_values)}this.onchange=a.onchange};$.extend(TrackConfig.prototype,{restore_values:function(a){var b=this;$.each(this.params,function(c,d){if(a[d.key]!==undefined){b.values[d.key]=a[d.key]}else{b.values[d.key]=d.default_value}})},build_form:function(){var b=this;var a=$("<div />");$.each(this.params,function(f,d){if(!d.hidden){var c="param_"+f;var l=$("<div class='form-row' />").appendTo(a);l.append($("<label />").attr("for",c).text(d.label+":"));if(d.type==="bool"){l.append($('<input type="checkbox" />').attr("id",c).attr("name",c).attr("checked",b.values[d.key]))}else{if(d.type==="color"){var h=b.values[d.key];var g=$("<input />").attr("id",c).attr("name",c).val(h);var j=$("<div class='tipsy tipsy-north' style='position: absolute;' />").hide();var e=$("<div style='background-color: black; padding: 10px;'></div>").appendTo(j);var k=$("<div/>").appendTo(e).farbtastic({width:100,height:100,callback:g,color:h});$("<div />").append(g).append(j).appendTo(l).bind("click",function(m){j.css({left:$(this).position().left+($(g).width()/2)-60,top:$(this).position().top+$(this.height)}).show();$(document).bind("click.color-picker",function(){j.hide();$(document).unbind("click.color-picker")});m.stopPropagation()})}else{l.append($("<input />").attr("id",c).attr("name",c).val(b.values[d.key]))}}}});return a},update_from_form:function(a){var c=this;var b=false;$.each(this.params,function(d,f){if(!f.hidden){var g="param_"+d;var e=a.find("#"+g).val();if(f.type==="float"){e=parseFloat(e)}else{if(f.type==="int"){e=parseInt(e)}else{if(f.type==="bool"){e=a.find("#"+g).is(":checked")}}}if(e!==c.values[f.key]){c.values[f.key]=e;b=true}}});if(b){this.onchange()}}});var Tile=function(a,b,c){this.track=a;this.canvas=b;this.histo_max=c};var Track=function(b,a,e,c,d){this.name=b;this.view=a;this.parent_element=e;this.data_url=(c?c:default_data_url);this.data_url_extra_params={};this.data_query_wait=(d?d:DEFAULT_DATA_QUERY_WAIT);this.dataset_check_url=converted_datasets_state_url;this.container_div=$("<div />").addClass("track").css("position","relative");if(!this.hidden){this.header_div=$("<div class='track-header' />").appendTo(this.container_div);if(this.view.editor){this.drag_div=$("<div class='draghandle' />").appendTo(this.header_div)}this.name_div=$("<div class='menubutton popup' />").appendTo(this.header_div);this.name_div.text(this.name);this.name_div.attr("id",this.name.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9\-]/g,"").toLowerCase())}this.content_div=$("<div class='track-content'>").appendTo(this.container_div);this.parent_element.append(this.container_div)};$.extend(Track.prototype,{init:function(){var a=this;a.enabled=false;a.tile_cache.clear();a.data_cache.clear();a.initial_canvas=undefined;a.content_div.css("height","auto");a.container_div.removeClass("nodata error pending");if(!a.dataset_id){return}$.getJSON(converted_datasets_state_url,{hda_ldda:a.hda_ldda,dataset_id:a.dataset_id,chrom:a.view.chrom},function(b){if(!b||b==="error"||b.kind==="error"){a.container_div.addClass("error");a.content_div.text(DATA_ERROR);if(b.message){var d=a.view.tracks.indexOf(a);var c=$(" <a href='javascript:void(0);'></a>").text("View error").bind("click",function(){show_modal("Trackster Error","<pre>"+b.message+"</pre>",{Close:hide_modal})});a.content_div.append(c)}}else{if(b==="no converter"){a.container_div.addClass("error");a.content_div.text(DATA_NOCONVERTER)}else{if(b==="no data"||(b.data!==undefined&&(b.data===null||b.data.length===0))){a.container_div.addClass("nodata");a.content_div.text(DATA_NONE)}else{if(b==="pending"){a.container_div.addClass("pending");a.content_div.text(DATA_PENDING);setTimeout(function(){a.init()},a.data_query_wait)}else{if(b.status==="data"){if(b.valid_chroms){a.valid_chroms=b.valid_chroms;a.make_name_popup_menu()}a.content_div.text(DATA_OK);if(a.view.chrom){a.content_div.text("");a.content_div.css("height",a.height_px+"px");a.enabled=true;$.when(a.predraw_init()).done(function(){a.draw()})}}}}}}})},predraw_init:function(){},update_name:function(a){this.old_name=this.name;this.name=a;this.name_div.text(this.name)},revert_name:function(){this.name=this.old_name;this.name_div.text(this.name)}});var TiledTrack=function(b,h,o){var c=this,p=c.view;this.filters=(b!==undefined?get_filters_from_dict(b):[]);this.filters_available=false;this.filters_visible=false;this.tool=(h!==undefined&&obj_length(h)>0?new Tool(h):undefined);this.parent_track=o;this.child_tracks=[];if(c.hidden){return}var k=function(r,s,t){r.click(function(){var u=s.text();max=parseFloat(t.slider("option","max")),input_size=(max<=1?4:max<=1000000?max.toString().length:6),multi_value=false;if(t.slider("option","values")){input_size=2*input_size+1;multi_value=true}s.text("");$("<input type='text'/>").attr("size",input_size).attr("maxlength",input_size).attr("value",u).appendTo(s).focus().select().click(function(v){v.stopPropagation()}).blur(function(){$(this).remove();s.text(u)}).keyup(function(z){if(z.keyCode===27){$(this).trigger("blur")}else{if(z.keyCode===13){var x=t.slider("option","min"),v=t.slider("option","max"),y=function(A){return(isNaN(A)||A>v||A<x)},w=$(this).val();if(!multi_value){w=parseFloat(w);if(y(w)){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}else{w=w.split("-");w=[parseFloat(w[0]),parseFloat(w[1])];if(y(w[0])||y(w[1])){alert("Parameter value must be in the range ["+x+"-"+v+"]");return $(this)}}t.slider((multi_value?"values":"value"),w)}}})})};if(this.parent_track){this.header_div.find(".draghandle").removeClass("draghandle").addClass("child-track-icon").addClass("icon-button");this.parent_element.addClass("child-track");this.tool=undefined}this.filters_div=$("<div/>").addClass("filters").hide();this.header_div.after(this.filters_div);this.filters_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});$.each(this.filters,function(x,s){var u=$("<div/>").addClass("slider-row").appendTo(c.filters_div);var r=$("<div/>").addClass("slider-label").appendTo(u);var z=$("<span/>").addClass("slider-name").text(s.name+" ").appendTo(r);var t=$("<span/>");var v=$("<span/>").addClass("slider-value").appendTo(r).append("[").append(t).append("]");var y=$("<div/>").addClass("slider").appendTo(u);s.control_element=$("<div/>").attr("id",s.name+"-filter-control").appendTo(y);var w=[0,0];s.control_element.slider({range:true,min:Number.MAX_VALUE,max:-Number.MIN_VALUE,values:[0,0],slide:function(A,B){w=B.values;t.text(B.values[0]+"-"+B.values[1]);setTimeout(function(){if(B.values[0]==w[0]&&B.values[1]==w[1]){var C=B.values;t.text(C[0]+"-"+C[1]);s.low=C[0];s.high=C[1];c.draw(true,true)}},50)},change:function(A,B){s.control_element.slider("option","slide").call(s.control_element,A,B)}});s.slider=s.control_element;s.slider_label=t;k(v,t,s.control_element);$("<div style='clear: both;'/>").appendTo(u)});if(this.tool){this.dynamic_tool_div=$("<div/>").addClass("dynamic-tool").hide();this.header_div.after(this.dynamic_tool_div);this.dynamic_tool_div.bind("drag",function(r){r.stopPropagation()}).bind("click",function(r){r.stopPropagation()}).bind("dblclick",function(r){r.stopPropagation()});var n=$("<div class='tool-name'>").appendTo(this.dynamic_tool_div).text(this.tool.name);var m=this.tool.params;var c=this;$.each(this.tool.params,function(A,s){if(s instanceof NumberParameter){var x=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(x);var C=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var t=$("<span/>").text(s.value);var y=$("<span/>").addClass("slider-value").appendTo(u).append("[").append(t).append("]");var B=$("<div/>").addClass("slider").appendTo(x);var r=$("<div id='"+s.name+"-param-control'>").appendTo(B);r.slider({min:s.min,max:s.max,step:get_slider_step(s.min,s.max),value:s.value,slide:function(F,H){var G=H.value;s.value=G;if(0<G&&G<1){G=parseFloat(G).toFixed(2)}t.text(G)},change:function(F,G){r.slider("option","slide").call(r,F,G)}});k(y,t,r);$("<div style='clear: both;'/>").appendTo(x)}else{if(s instanceof SelectParameter){var x=$("<div>").addClass("slider-row").appendTo(c.dynamic_tool_div);var u=$("<div>").addClass("slider-label").appendTo(x);var C=$("<span/>").addClass("slider-name").text(s.label+" ").appendTo(u);var E=$("<div/>").addClass("slider").appendTo(x);var w=$("<select/>").appendTo(E);w.change(function(){s.value=$(this).val()});var D=w.attr("options");for(var z=0;z<s.options.length;z++){var v=s.options[z];D[D.length]=new Option(v[0],v[1])}$("<div style='clear: both;'/>").appendTo(x)}}});var q=$("<div>").addClass("slider-row").appendTo(this.dynamic_tool_div);var l=$("<input type='submit'>").attr("value","Run").appendTo(q);var c=this;l.click(function(){c.run_tool()})}c.child_tracks_container=$("<div/>").addClass("child-tracks-container").hide();c.container_div.append(c.child_tracks_container);if(c.display_modes!==undefined){if(c.mode_div===undefined){c.mode_div=$("<div class='right-float menubutton popup' />").appendTo(c.header_div);var e=(c.track_config&&c.track_config.values.mode?c.track_config.values.mode:c.display_modes[0]);c.mode=e;c.mode_div.text(e);var d=function(r){c.mode_div.text(r);c.mode=r;c.track_config.values.mode=r;c.tile_cache.clear();c.draw()};var a={};for(var f=0,j=c.display_modes.length;f<j;f++){var g=c.display_modes[f];a[g]=function(r){return function(){d(r)}}(g)}make_popupmenu(c.mode_div,a)}else{c.mode_div.hide()}}this.make_name_popup_menu()};$.extend(TiledTrack.prototype,Track.prototype,{make_name_popup_menu:function(){var b=this;var a={};a["Edit configuration"]=function(){var h=function(){hide_modal();$(window).unbind("keypress.check_enter_esc")},f=function(){b.track_config.update_from_form($(".dialog-box"));hide_modal();$(window).unbind("keypress.check_enter_esc")},g=function(j){if((j.keyCode||j.which)===27){h()}else{if((j.keyCode||j.which)===13){f()}}};$(window).bind("keypress.check_enter_esc",g);show_modal("Configure Track",b.track_config.build_form(),{Cancel:h,OK:f})};if(b.filters_available>0){var e=(b.filters_div.is(":visible")?"Hide filters":"Show filters");a[e]=function(){b.filters_visible=(b.filters_div.is(":visible"));b.filters_div.toggle();b.make_name_popup_menu()}}if(b.tool){var e=(b.dynamic_tool_div.is(":visible")?"Hide tool":"Show tool");a[e]=function(){if(!b.dynamic_tool_div.is(":visible")){b.update_name(b.name+b.tool_region_and_parameters_str())}else{menu_option_text="Show dynamic tool";b.revert_name()}b.dynamic_tool_div.toggle();b.make_name_popup_menu()}}if(b.valid_chroms){a["List chrom/contigs with data"]=function(){show_modal("Chrom/contigs with data","<p>"+b.valid_chroms.join("<br/>")+"</p>",{Close:function(){hide_modal()}})}}var c=view;var d=function(){$("#no-tracks").show()};if(this.parent_track){c=this.parent_track;d=function(){}}a.Remove=function(){c.remove_track(b);if(c.num_tracks===0){d()}};make_popupmenu(b.name_div,a)},draw:function(a,d){var s=this.view.low,g=this.view.high,j=g-s,l=this.view.container.width(),f=l/j,m=this.view.resolution,e=$("<div style='position: relative;'></div>");if(!d){this.content_div.children().remove()}this.content_div.append(e);this.max_height=0;var o=Math.floor(s/m/DENSITY);var c={};while((o*DENSITY*m)<g){var r=l+"_"+f+"_"+o;var h=this.tile_cache.get(r);var p=o*DENSITY*this.view.resolution;var b=p+DENSITY*this.view.resolution;if(!a&&h){this.show_tile(h,e,p,f)}else{this.delayed_draw(a,r,p,b,o,m,e,f,c)}o+=1}var k=this;var q=setInterval(function(){if(obj_length(c)===0){clearInterval(q);if(d){var v=k.content_div.children();var u=false;for(var w=v.length-1,t=0;w>=t;w--){var z=$(v[w]);if(u){z.remove()}else{if(z.children().length!==0){u=true}}}}for(var y=0;y<k.filters.length;y++){k.filters[y].update_ui_elt()}var x=false;if(k.example_feature){for(var y=0;y<k.filters.length;y++){if(k.filters[y].applies_to(k.example_feature)){x=true;break}}}if(k.filters_available!==x){k.filters_available=x;if(!k.filters_available){k.filters_div.hide()}k.make_name_popup_menu()}}},50);for(var n=0;n<this.child_tracks.length;n++){this.child_tracks[n].draw(a,d)}},delayed_draw:function(b,j,g,l,c,e,k,m,f){var d=this;var h=function(u,n,p,o,s,t,q){returned_tile=d.draw_tile(n,p,o,s,t,q);var r=$("<div class='track-tile'>").prepend(returned_tile);tile_element=r;d.tile_cache.set(j,tile_element);d.show_tile(tile_element,s,g,t);delete f[u]};var a=setTimeout(function(){if(g<=d.view.high&&l>=d.view.low){var n=(b?undefined:d.tile_cache.get(j));if(n){d.show_tile(n,k,g,m);delete f[a]}else{$.when(d.data_cache.get_data(view.chrom,g,l,d.mode,e,d.data_url_extra_params)).then(function(o){if(view.reference_track&&m>CHAR_WIDTH_PX){$.when(view.reference_track.data_cache.get_data(view.chrom,g,l,d.mode,e,view.reference_track.data_url_extra_params)).then(function(p){h(a,o,e,c,k,m,p)})}else{h(a,o,e,c,k,m)}})}}},50);f[a]=true},show_tile:function(a,f,d,g){var b=this;var c=this.view.high-this.view.low,e=(d-this.view.low)*g;if(this.left_offset){e-=this.left_offset}a.css({position:"absolute",top:0,left:e,height:""});f.append(a);b.max_height=Math.max(b.max_height,a.height());b.content_div.css("height",b.max_height+"px");f.children().css("height",b.max_height+"px")},set_overview:function(){var a=this.view;if(this.initial_canvas&&this.is_overview){a.overview_close.show();a.overview_viewport.append(this.initial_canvas);a.overview_highlight.show().height(this.initial_canvas.height());a.overview_viewport.height(this.initial_canvas.height()+a.overview_box.height())}$(window).trigger("resize")},run_tool:function(){var b={dataset_id:this.original_dataset_id,chrom:this.view.chrom,low:this.view.low,high:this.view.high,tool_id:this.tool.name};$.extend(b,this.tool.get_param_values_dict());var d=this,c=b.tool_id+d.tool_region_and_parameters_str(b.chrom,b.low,b.high),e;if(d.track_type==="FeatureTrack"){e=new ToolDataFeatureTrack(c,view,d.hda_ldda,undefined,{},{},d)}this.add_track(e);e.content_div.text("Starting job.");view.has_changes=true;var a=function(){$.getJSON(run_tool_url,b,function(f){if(f==="no converter"){e.container_div.addClass("error");e.content_div.text(DATA_NOCONVERTER)}else{if(f.error){e.container_div.addClass("error");e.content_div.text(DATA_CANNOT_RUN_TOOL+f.message)}else{if(f==="pending"){e.container_div.addClass("pending");e.content_div.text("Converting input data so that it can be easily reused.");setTimeout(a,2000)}else{e.dataset_id=f.dataset_id;e.content_div.text("Running job.");e.init()}}}})};a()},tool_region_and_parameters_str:function(c,a,d){var b=this,e=(c!==undefined&&a!==undefined&&d!==undefined?c+":"+a+"-"+d:"all");return" - region=["+e+"], parameters=["+b.tool.get_param_values().join(", ")+"]"},add_track:function(a){a.track_id=this.track_id+"_"+this.child_tracks.length;a.container_div.attr("id","track_"+a.track_id);this.child_tracks_container.append(a.container_div);sortable(a.container_div,".child-track-icon");if(!$(this.child_tracks_container).is(":visible")){this.child_tracks_container.show()}this.child_tracks.push(a)},remove_track:function(a){a.container_div.fadeOut("slow",function(){$(this).remove()})}});var LabelTrack=function(a,b){this.track_type="LabelTrack";this.hidden=true;Track.call(this,null,a,b);this.container_div.addClass("label-track")};$.extend(LabelTrack.prototype,Track.prototype,{draw:function(){var c=this.view,d=c.high-c.low,g=Math.floor(Math.pow(10,Math.floor(Math.log(d)/Math.log(10)))),a=Math.floor(c.low/g)*g,e=this.view.container.width(),b=$("<div style='position: relative; height: 1.3em;'></div>");while(a<c.high){var f=(a-c.low)/d*e;b.append($("<div class='label'>"+commatize(a)+"</div>").css({position:"absolute",left:f-1}));a+=g}this.content_div.children(":first").remove();this.content_div.append(b)}});var ReferenceTrack=function(a){this.track_type="ReferenceTrack";this.hidden=true;Track.call(this,null,a,a.top_labeltrack);TiledTrack.call(this);a.reference_track=this;this.left_offset=200;this.height_px=12;this.font=DEFAULT_FONT;this.container_div.addClass("reference-track");this.content_div.css("background","none");this.content_div.css("min-height","0px");this.content_div.css("border","none");this.data_url=reference_url;this.data_url_extra_params={dbkey:a.dbkey};this.data_cache=new DataManager(CACHED_DATA,this,false);this.tile_cache=new Cache(CACHED_TILES_LINE)};$.extend(ReferenceTrack.prototype,TiledTrack.prototype,{draw_tile:function(m,g,b,j,n){var f=this,d=DENSITY*g;if(n>CHAR_WIDTH_PX){if(m===null){f.content_div.css("height","0px");return}var e=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(e)}e=$(e);var l=e.get(0).getContext("2d");e.get(0).width=Math.ceil(d*n+f.left_offset);e.get(0).height=f.height_px;l.font=DEFAULT_FONT;l.textAlign="center";for(var h=0,k=m.length;h<k;h++){var a=Math.round(h*n);l.fillText(m[h],a+f.left_offset,10)}return e}this.content_div.css("height","0px")}});var LineTrack=function(e,c,f,a,d){var b=this;this.track_type="LineTrack";this.display_modes=["Histogram","Line","Filled","Intensity"];this.mode="Histogram";Track.call(this,e,c,c.viewport_container);TiledTrack.call(this);this.min_height_px=16;this.max_height_px=400;this.height_px=80;this.hda_ldda=f;this.dataset_id=a;this.original_dataset_id=a;this.data_cache=new DataManager(CACHED_DATA,this);this.tile_cache=new Cache(CACHED_TILES_LINE);this.track_config=new TrackConfig({track:this,params:[{key:"color",label:"Color",type:"color",default_value:"black"},{key:"min_value",label:"Min Value",type:"float",default_value:undefined},{key:"max_value",label:"Max Value",type:"float",default_value:undefined},{key:"mode",type:"string",default_value:this.mode,hidden:true},{key:"height",type:"int",default_value:this.height_px,hidden:true}],saved_values:d,onchange:function(){b.vertical_range=b.prefs.max_value-b.prefs.min_value;$("#linetrack_"+b.track_id+"_minval").text(b.prefs.min_value);$("#linetrack_"+b.track_id+"_maxval").text(b.prefs.max_value);b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;this.height_px=this.track_config.values.height;this.vertical_range=this.track_config.values.max_value-this.track_config.values.min_value;(function(g){var k=false;var j=false;var h=$("<div class='track-resize'>");$(g.container_div).hover(function(){k=true;h.show()},function(){k=false;if(!j){h.hide()}});h.hide().bind("dragstart",function(l,m){j=true;m.original_height=$(g.content_div).height()}).bind("drag",function(m,n){var l=Math.min(Math.max(n.original_height+n.deltaY,g.min_height_px),g.max_height_px);$(g.content_div).css("height",l);g.height_px=l;g.draw(true)}).bind("dragend",function(l,m){g.tile_cache.clear();j=false;if(!k){h.hide()}g.track_config.values.height=g.height_px}).appendTo(g.container_div)})(this)};$.extend(LineTrack.prototype,TiledTrack.prototype,{predraw_init:function(){var a=this,b=a.view.tracks.indexOf(a);a.vertical_range=undefined;return $.getJSON(a.data_url,{stats:true,chrom:a.view.chrom,low:null,high:null,hda_ldda:a.hda_ldda,dataset_id:a.dataset_id},function(c){a.container_div.addClass("line-track");var e=c.data;if(isNaN(parseFloat(a.prefs.min_value))||isNaN(parseFloat(a.prefs.max_value))){a.prefs.min_value=e.min;a.prefs.max_value=e.max;$("#track_"+b+"_minval").val(a.prefs.min_value);$("#track_"+b+"_maxval").val(a.prefs.max_value)}a.vertical_range=a.prefs.max_value-a.prefs.min_value;a.total_frequency=e.total_frequency;a.container_div.find(".yaxislabel").remove();var f=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_minval").text(round_1000(a.prefs.min_value));var d=$("<div />").addClass("yaxislabel").attr("id","linetrack_"+b+"_maxval").text(round_1000(a.prefs.max_value));d.css({position:"absolute",top:"24px",left:"10px"});d.prependTo(a.container_div);f.css({position:"absolute",bottom:"2px",left:"10px"});f.prependTo(a.container_div)})},draw_tile:function(j,q,t,c,e){if(this.vertical_range===undefined){return}var o=this,u=t*DENSITY*q,a=DENSITY*q,B=q+"_"+t,v={hda_ldda:this.hda_ldda,dataset_id:this.dataset_id,resolution:this.view.resolution};var b=document.createElement("canvas"),z=j.data;if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(b)}b=$(b);b.get(0).width=Math.ceil(a*e);b.get(0).height=o.height_px;var p=b.get(0).getContext("2d"),k=false,l=o.prefs.min_value,g=o.prefs.max_value,n=o.vertical_range,w=o.total_frequency,d=o.height_px,m=o.mode;var A=Math.round(d+l/n*d);p.beginPath();p.moveTo(0,A);p.lineTo(a*e,A);p.fillStyle="#aaa";p.stroke();p.beginPath();p.fillStyle=o.prefs.color;var x,h,f;if(z.length>1){f=Math.ceil((z[1][0]-z[0][0])*e)}else{f=10}for(var r=0,s=z.length;r<s;r++){x=Math.round((z[r][0]-u)*e);h=z[r][1];if(h===null){if(k&&m==="Filled"){p.lineTo(x,d)}k=false;continue}if(h<l){h=l}else{if(h>g){h=g}}if(m==="Histogram"){h=Math.round(h/n*d);p.fillRect(x,A,f,-h)}else{if(m==="Intensity"){h=255-Math.floor((h-l)/n*255);p.fillStyle="rgb("+h+","+h+","+h+")";p.fillRect(x,0,f,d)}else{h=Math.round(d-(h-l)/n*d);if(k){p.lineTo(x,h)}else{k=true;if(m==="Filled"){p.moveTo(x,d);p.lineTo(x,h)}else{p.moveTo(x,h)}}}}}if(m==="Filled"){if(k){p.lineTo(x,A);p.lineTo(0,A)}p.fill()}else{p.stroke()}return b}});var FeatureTrack=function(a,f,e,j,h,c,d,g){var b=this;this.track_type="FeatureTrack";this.display_modes=["Auto","Dense","Squish","Pack"];this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:h,onchange:function(){b.tile_cache.clear();b.draw()}});this.prefs=this.track_config.values;Track.call(this,a,f,f.viewport_container);TiledTrack.call(this,c,d,g);this.height_px=0;this.container_div.addClass("feature-track");this.hda_ldda=e;this.dataset_id=j;this.original_dataset_id=j;this.zo_slots={};this.show_labels_scale=0.001;this.showing_details=false;this.summary_draw_height=30;this.default_font=DEFAULT_FONT;this.inc_slots={};this.start_end_dct={};this.tile_cache=new Cache(CACHED_TILES_FEATURE);this.data_cache=new DataManager(20,this);this.left_offset=200};$.extend(FeatureTrack.prototype,TiledTrack.prototype,{incremental_slots:function(a,h,p){var q=this.inc_slots[a];if(!q||(q.mode!==p)){q={};q.w_scale=a;q.mode=p;this.inc_slots[a]=q;this.start_end_dct[a]={}}var l=q.w_scale,w=[],x=[],j=0,n=this.view.max_low;for(var u=0,v=h.length;u<v;u++){var g=h[u],k=g[0];if(q[k]!==undefined){j=Math.max(j,q[k]);x.push(q[k])}else{w.push(u)}}var d=this.start_end_dct[a];var m=function(D,E){for(var C=0;C<=MAX_FEATURE_DEPTH;C++){var A=false,F=d[C];if(F!==undefined){for(var z=0,B=F.length;z<B;z++){var y=F[z];if(E>y[0]&&D<y[1]){A=true;break}}}if(!A){return C}}return -1};for(var u=0,v=w.length;u<v;u++){var g=h[w[u]],k=g[0],s=g[1],b=g[2],o=g[3],c=Math.floor((s-n)*l),f=Math.ceil((b-n)*l),t=CONTEXT.measureText(o).width,e;if(o!==undefined&&p==="Pack"){t+=(LABEL_SPACING+PACK_SPACING);if(c-t>=0){c-=t;e="left"}else{f+=t;e="right"}}var r=m(c,f);if(r>=0){if(d[r]===undefined){d[r]=[]}d[r].push([c,f]);q[k]=r;j=Math.max(j,r)}else{}}return j+1},draw_summary_tree:function(b,o,n,l,r,j,f,e){var c=j+LABEL_SPACING+CHAR_HEIGHT_PX;delta_x_px=Math.ceil(n*r);var h=$("<div />").addClass("yaxislabel");h.text(l);h.css({position:"absolute",top:"22px",left:"10px"});h.prependTo(this.container_div);var q=b.get(0).getContext("2d");for(var d=0,g=o.length;d<g;d++){var m=Math.floor((o[d][0]-f)*r);var k=o[d][1];if(!k){continue}var p=k/l*j;q.fillStyle="black";q.fillRect(m+e,c-p,delta_x_px,p);var a=4;if(this.prefs.show_counts&&(q.measureText(k).width+a)<delta_x_px){q.fillStyle="#666";q.textAlign="center";q.fillText(k,m+e+(delta_x_px/2),10)}}},draw_element:function(p,f,g,x,j,r,I,M,N,a,A){var u=x[0],K=x[1],C=x[2]-1,s=x[3],D=Math.floor(Math.max(0,(K-r)*M)),q=Math.ceil(Math.min(a,Math.max(0,(C-r)*M))),B=ERROR_PADDING+(g==="Dense"?0:(0+j))*N,o,G,t=null,O=null,d=this.prefs.block_color,F=this.prefs.label_color;if(g==="Dense"){p.fillStyle=d;p.fillRect(D+A,B,q-D,DENSE_FEATURE_HEIGHT)}else{if(g==="no_detail"){p.fillStyle=d;p.fillRect(D+A,B+5,q-D,DENSE_FEATURE_HEIGHT)}else{var n=x[4],z=x[5],E=x[6],e=x[7];if(z&&E){t=Math.floor(Math.max(0,(z-r)*M));O=Math.ceil(Math.min(a,Math.max(0,(E-r)*M)))}var L,v;if(g==="Squish"){L=1;v=SQUISH_FEATURE_HEIGHT}else{L=5;v=PACK_FEATURE_HEIGHT}if(!e){if(x.strand){if(x.strand==="+"){p.fillStyle=RIGHT_STRAND_INV}else{if(x.strand==="-"){p.fillStyle=LEFT_STRAND_INV}}}else{p.fillStyle=d}p.fillRect(D+A,B,q-D,v)}else{var m,w;if(g==="Squish"){p.fillStyle=CONNECTOR_COLOR;m=B+Math.floor(SQUISH_FEATURE_HEIGHT/2)+1;w=1}else{if(n){var m=B;var w=v;if(n==="+"){p.fillStyle=RIGHT_STRAND}else{if(n==="-"){p.fillStyle=LEFT_STRAND}}}else{p.fillStyle=CONNECTOR_COLOR;m+=(SQUISH_FEATURE_HEIGHT/2)+1;w=1}}p.fillRect(D+A,m,q-D,w);for(var J=0,c=e.length;J<c;J++){var h=e[J],b=Math.floor(Math.max(0,(h[0]-r)*M)),y=Math.ceil(Math.min(a,Math.max((h[1]-1-r)*M)));if(b>y){continue}p.fillStyle=d;p.fillRect(b+A,B+(v-L)/2+1,y-b,L);if(t!==undefined&&!(b>O||y<t)){var H=Math.max(b,t),l=Math.min(y,O);p.fillRect(H+A,B+1,l-H,v)}}}if(g==="Pack"&&K>r){p.fillStyle=F;if(f===0&&D-p.measureText(s).width<0){p.textAlign="left";p.fillText(s,q+A+LABEL_SPACING,B+8)}else{p.textAlign="right";p.fillText(s,D+A-LABEL_SPACING,B+8)}p.fillStyle=d}}}},get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="no_detail"){a=NO_DETAIL_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT}}}return a},draw_tile:function(o,x,C,k,m,d){var t=this;var E=C*DENSITY*x,a=(C+1)*DENSITY*x,q=a-E,F={hda_ldda:t.hda_ldda,dataset_id:t.dataset_id,resolution:this.view.resolution,mode:this.mode};var v=Math.ceil(q*m),s=this.mode,H=25,g=this.left_offset,p,h;var c=document.createElement("canvas");if(window.G_vmlCanvasManager){G_vmlCanvasManager.initElement(c)}c=$(c);if(s==="Auto"){if(o.dataset_type==="summary_tree"){s=o.dataset_type}else{if(o.extra_info==="no_detail"){s="no_detail"}else{var G=o.data;if((G.length&&G.length<4)||(this.view.high-this.view.low>MIN_SQUISH_VIEW_WIDTH)){s="Squish"}else{s="Pack"}}}}var y=this.get_y_scale(s);if(s==="summary_tree"){h=this.summary_draw_height}if(s==="Dense"){h=H}else{if(s==="no_detail"||s==="Squish"||s==="Pack"){h=this.incremental_slots(m,o.data,s)*y+H;p=this.inc_slots[m]}}c.get(0).width=v+g;c.get(0).height=h;if(o.dataset_type==="summary_tree"){c.get(0).height+=LABEL_SPACING+CHAR_HEIGHT_PX}k.parent().css("height",Math.max(this.height_px,h)+"px");var w=c.get(0).getContext("2d");w.fillStyle=this.prefs.block_color;w.font=this.default_font;w.textAlign="right";this.container_div.find(".yaxislabel").remove();if(s==="summary_tree"){this.draw_summary_tree(c,o.data,o.delta,o.max,m,h,E,g);return c}if(o.message){c.css({"border-top":"1px solid red"});w.fillStyle="red";w.textAlign="left";var r=w.textBaseline;w.textBaseline="top";w.fillText(o.message,g,0);w.textBaseline=r;if(!o.data){return c}}this.example_feature=(o.data.length?o.data[0]:undefined);var G=o.data;for(var z=0,B=G.length;z<B;z++){var j=G[z],l=j[0],u=j[1],b=j[2],e=(p&&p[l]!==undefined?p[l]:null);var A=false;var n;for(var D=0;D<this.filters.length;D++){n=this.filters[D];n.update_attrs(j);if(!n.keep(j)){A=true;break}}if(A){continue}if(is_overlap([u,b],[E,a])&&(s=="Dense"||e!==null)){this.draw_element(w,C,s,j,e,E,a,m,y,v,g,d)}}return c}});var VcfTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_type="VcfTrack"};$.extend(VcfTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{draw_element:function(u,p,j,e,x,c,m,v,s){var j=data[i],l=j[0],t=j[1],d=j[2]-1,o=j[3],g=Math.floor(Math.max(0,(t-x)*m)),k=Math.ceil(Math.min(s,Math.max(0,(d-x)*m))),f=ERROR_PADDING+(p==="Dense"?0:(0+e))*v,a,y,b=null,n=null;if(no_label){u.fillStyle=block_color;u.fillRect(g+left_offset,f+5,k-g,1)}else{var w=j[4],r=j[5],h=j[6];a=9;y=1;u.fillRect(g+left_offset,f,k-g,a);if(p!=="Dense"&&o!==undefined&&t>x){u.fillStyle=label_color;if(tile_index===0&&g-u.measureText(o).width<0){u.textAlign="left";u.fillText(o,k+2+left_offset,f+8)}else{u.textAlign="right";u.fillText(o,g-2+left_offset,f+8)}u.fillStyle=block_color}var q=w+" / "+r;if(t>x&&u.measureText(q).width<(k-g)){u.fillStyle="white";u.textAlign="center";u.fillText(q,left_offset+g+(k-g)/2,f+8);u.fillStyle=block_color}}}});var ReadTrack=function(d,b,f,a,c,e){FeatureTrack.call(this,d,b,f,a,c,e);this.track_config=new TrackConfig({track:this,params:[{key:"block_color",label:"Block color",type:"color",default_value:"#444"},{key:"label_color",label:"Label color",type:"color",default_value:"black"},{key:"show_insertions",label:"Show insertions",type:"bool",default_value:false},{key:"show_differences",label:"Show differences only",type:"bool",default_value:true},{key:"show_counts",label:"Show summary counts",type:"bool",default_value:true},{key:"mode",type:"string",default_value:this.mode,hidden:true},],saved_values:c,onchange:function(){this.track.tile_cache.clear();this.track.draw()}});this.prefs=this.track_config.values;this.track_type="ReadTrack";this.make_name_popup_menu()};$.extend(ReadTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{get_y_scale:function(b){var a;if(b==="summary_tree"){}if(b==="Dense"){a=DENSE_TRACK_HEIGHT}else{if(b==="Squish"){a=SQUISH_TRACK_HEIGHT}else{a=PACK_TRACK_HEIGHT;if(this.track_config.values.show_insertions){a*=2}}}return a},draw_read:function(m,d,J,n,D,G,e,o,y,a){m.textAlign="center";var l=this,u=[n,D],C=0,z=0,f=0;var r=[];if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){f=Math.round(J/2)}if(!e){e=[[0,o.length]]}for(var h=0,k=e.length;h<k;h++){var b=e[h],g="MIDNSHP=X"[b[0]],v=b[1];if(g==="H"||g==="S"){C-=v}var x=G+C,B=Math.floor(Math.max(0,(x-n)*J)),E=Math.floor(Math.max(0,(x+v-n)*J));switch(g){case"H":break;case"S":case"M":case"=":var p=compute_overlap([x,x+v],u);if(p!==NO_OVERLAP){var q=o.slice(z,z+v);if(f>0){m.fillStyle=this.prefs.block_color;m.fillRect(B+this.left_offset-f,y+1,E-B,9);m.fillStyle=CONNECTOR_COLOR;for(var I=0,j=q.length;I<j;I++){if(this.track_config.values.show_differences&&a){var s=a[x-n+I];if(!s||s.toLowerCase()===q[I].toLowerCase()){continue}}if(x+I>=n&&x+I<=D){var A=Math.floor(Math.max(0,(x+I-n)*J));m.fillText(q[I],A+this.left_offset,y+9)}}}else{m.fillStyle=this.prefs.block_color;m.fillRect(B+this.left_offset,y+(this.mode!=="Dense"?4:5),E-B,(d!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}z+=v;C+=v;break;case"N":m.fillStyle=CONNECTOR_COLOR;m.fillRect(B+this.left_offset-f,y+5,E-B,1);C+=v;break;case"D":m.fillStyle="red";m.fillRect(B+this.left_offset-f,y+4,E-B,3);C+=v;break;case"P":break;case"I":var p=compute_overlap([x,x+v],u),K=this.left_offset+B-f;if(p!==NO_OVERLAP){var q=o.slice(z,z+v);if(this.track_config.values.show_insertions){var w=this.left_offset+B-(E-B)/2;if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){m.fillStyle="yellow";m.fillRect(w-f,y-9,E-B,9);r[r.length]={type:"triangle",data:[K,y+4,5]};m.fillStyle=CONNECTOR_COLOR;switch(p){case (OVERLAP_START):q=q.slice(n-x);break;case (OVERLAP_END):q=q.slice(0,x-D);break;case (CONTAINED_BY):break;case (CONTAINS):q=q.slice(n-x,x-D);break}for(var I=0,j=q.length;I<j;I++){var A=Math.floor(Math.max(0,(x+I-n)*J));m.fillText(q[I],A+this.left_offset-(E-B)/2,y)}}else{m.fillStyle="yellow";m.fillRect(w,y+(this.mode!=="Dense"?2:5),E-B,(d!=="Dense"?SQUISH_FEATURE_HEIGHT:DENSE_FEATURE_HEIGHT))}}else{if((d==="Pack"||this.mode==="Auto")&&o!==undefined&&J>CHAR_WIDTH_PX){r[r.length]={type:"text",data:[q.length,K,y+9]}}else{}}}z+=v;break;case"X":z+=v;break}}m.fillStyle="yellow";var t,L,H;for(var F=0;F<r.length;F++){t=r[F];L=t.type;H=t.data;if(L==="text"){m.font="bold "+DEFAULT_FONT;m.fillText(H[0],H[1],H[2]);m.font=DEFAULT_FONT}else{if(L=="triangle"){m.drawDownwardEquilateralTriangle(H[0],H[1],H[2])}}}},draw_element:function(w,y,r,k,e,A,b,n,x,u,f,c){var m=k[0],v=k[1],d=k[2],o=k[3],h=Math.floor(Math.max(0,(v-A)*n)),j=Math.ceil(Math.min(u,Math.max(0,(d-A)*n))),g=(r==="Dense"?1:(1+e))*x,z=this.prefs.block_color,l=this.prefs.label_color,t=0;if((r==="Pack"||this.mode==="Auto")&&n>CHAR_WIDTH_PX){var t=Math.round(n/2)}w.fillStyle=z;if(k[5] instanceof Array){var s=Math.floor(Math.max(0,(k[4][0]-A)*n)),q=Math.ceil(Math.min(u,Math.max(0,(k[4][1]-A)*n))),p=Math.floor(Math.max(0,(k[5][0]-A)*n)),a=Math.ceil(Math.min(u,Math.max(0,(k[5][1]-A)*n)));if(k[4][1]>=A&&k[4][0]<=b&&k[4][2]){this.draw_read(w,r,n,A,b,k[4][0],k[4][2],k[4][3],g,c)}if(k[5][1]>=A&&k[5][0]<=b&&k[5][2]){this.draw_read(w,r,n,A,b,k[5][0],k[5][2],k[5][3],g,c)}if(p>q){w.fillStyle=CONNECTOR_COLOR;w.dashedLine(q+f-t,g+5,f+p-t,g+5)}}else{w.fillStyle=z;this.draw_read(w,r,n,A,b,v,k[4],k[5],g,c)}if(r==="Pack"&&v>A){w.fillStyle=this.prefs.label_color;if(y===0&&h-w.measureText(o).width<0){w.textAlign="left";w.fillText(o,j+f+LABEL_SPACING-t,g+8)}else{w.textAlign="right";w.fillText(o,h+f-LABEL_SPACING-t,g+8)}w.fillStyle=z}}});var ToolDataFeatureTrack=function(e,c,g,a,d,f,b){FeatureTrack.call(this,e,c,g,a,d,f,{},b);this.track_type="ToolDataFeatureTrack";this.data_url=raw_data_url;this.data_query_wait=1000;this.dataset_check_url=dataset_state_url};$.extend(ToolDataFeatureTrack.prototype,TiledTrack.prototype,FeatureTrack.prototype,{predraw_init:function(){var b=this;var a=function(){if(b.data_cache.size()===0){setTimeout(a,300)}else{b.data_url=default_data_url;b.data_query_wait=DEFAULT_DATA_QUERY_WAIT;b.dataset_state_url=converted_datasets_state_url;$.getJSON(b.dataset_state_url,{dataset_id:b.dataset_id,hda_ldda:b.hda_ldda},function(c){})}};a()}});
\ No newline at end of file
--- a/static/scripts/trackster.js Thu Mar 31 14:09:47 2011 -0400
+++ b/static/scripts/trackster.js Thu Mar 31 15:07:02 2011 -0400
@@ -817,9 +817,27 @@
/**
* Encapsulation of tools that users can apply to tracks/datasets.
*/
-var Tool = function(name, params) {
- this.name = name;
- this.params = params;
+var Tool = function(tool_dict) {
+ // Unpack tool from dictionary.
+ this.name = tool_dict.name;
+ this.params = [];
+ var params_dict = tool_dict.params;
+ for (var i = 0; i < params_dict.length; i++) {
+ var param_dict = params_dict[i],
+ name = param_dict.name,
+ label = param_dict.label,
+ type = param_dict.type;
+ if (type === "int" || type === "float") {
+ this.params[this.params.length] =
+ new NumberParameter(name, label, param_dict.min, param_dict.max, param_dict.value);
+ }
+ else if (type == "select") {
+ this.params[this.params.length] = new SelectParameter(name, label, param_dict.options);
+ }
+ else {
+ console.log("WARNING: unrecognized tool parameter type:", name, type);
+ }
+ }
};
$.extend(Tool.prototype, {
// Returns a dictionary of parameter values; key is parameter name, value
@@ -828,7 +846,7 @@
var param_dict = {};
for (var i = 0; i < this.params.length; i++) {
var param = this.params[i];
- param_dict[param.name] = param.value;
+ param_dict[param.name] = JSON.stringify(param.value);
}
return param_dict;
},
@@ -842,36 +860,22 @@
}
});
-var NumberToolParameter = function(name, label, min, max, init_value) {
+/**
+ * Tool parameters.
+ */
+var NumberParameter = function(name, label, min, max, init_value) {
this.name = name;
this.label = label;
this.min = min;
this.max = max;
this.value = init_value;
-}
+};
-// Uses a dictionary to construct a tool object.
-var get_tool_from_dict = function(tool_dict) {
- if (obj_length(tool_dict) === 0) {
- return undefined;
- }
-
- // Get tool.
- var tool_name = tool_dict.name;
- var params_dict = tool_dict.params;
- var params = Array();
- for (var i = 0; i < params_dict.length; i++) {
- var param_dict = params_dict[i];
- var
- name = param_dict.name,
- label = param_dict.label,
- type = param_dict.type,
- min = param_dict.min,
- max = param_dict.max,
- value = param_dict.value;
- params[params.length] = new NumberToolParameter(name, label, min, max, value);
- }
- return new Tool(tool_name, params);
+var SelectParameter = function(name, label, options) {
+ this.name = name;
+ this.label = label;
+ this.options = options;
+ this.value = (options.length !== 0 ? options[0][1] : null);
};
/**
@@ -1203,7 +1207,7 @@
}
});
-var TiledTrack = function(filters, tool, parent_track) {
+var TiledTrack = function(filters, tool_dict, parent_track) {
var track = this,
view = track.view;
@@ -1212,7 +1216,7 @@
// filters_available is determined by data, filters_visible is set by user.
this.filters_available = false;
this.filters_visible = false;
- this.tool = (tool !== undefined ? get_tool_from_dict( tool ) : undefined);
+ this.tool = (tool_dict !== undefined && obj_length(tool_dict) > 0 ? new Tool(tool_dict) : undefined);
//
// TODO: Right now there is only the notion of a parent track and multiple child tracks. However,
@@ -1381,42 +1385,65 @@
var tool_params = this.tool.params;
var track = this;
$.each(this.tool.params, function(index, param) {
- var param_div = $("<div>").addClass("slider-row").appendTo(track.dynamic_tool_div);
- // Slider label.
- var label_div = $("<div>").addClass("slider-label").appendTo(param_div);
- var name_span = $("<span/>").addClass("slider-name").text(param.label + " ").appendTo(label_div);
- var values_span = $("<span/>").text(param.value);
- var values_span_container = $("<span/>").addClass("slider-value").appendTo(label_div).append("[").append(values_span).append("]");
+ if (param instanceof NumberParameter) {
+ var param_div = $("<div>").addClass("slider-row").appendTo(track.dynamic_tool_div);
- // Slider.
- var slider_div = $("<div/>").addClass("slider").appendTo(param_div);
- var slider = $("<div id='" + param.name + "-param-control'>").appendTo(slider_div);
- // Step must have a value so that (max-min)%step == 0.
- slider.slider({
- min: param.min,
- max: param.max,
- step: get_slider_step(param.min, param.max),
- value: param.value,
- slide: function(event, ui) {
- var value = ui.value;
- param.value = value;
- // Set new value in UI.
- if (0 < value && value < 1) {
- value = parseFloat(value).toFixed(2);
- }
- values_span.text(value);
- },
- change: function(event, ui) {
- slider.slider("option", "slide").call(slider, event, ui);
- }
- });
+ // Slider label.
+ var label_div = $("<div>").addClass("slider-label").appendTo(param_div);
+ var name_span = $("<span/>").addClass("slider-name").text(param.label + " ").appendTo(label_div);
+ var values_span = $("<span/>").text(param.value);
+ var values_span_container = $("<span/>").addClass("slider-value").appendTo(label_div).append("[").append(values_span).append("]");
- // Enable users to edit parameter's value via text box.
- edit_slider_values(values_span_container, values_span, slider);
+ // Slider.
+ var slider_div = $("<div/>").addClass("slider").appendTo(param_div);
+ var slider = $("<div id='" + param.name + "-param-control'>").appendTo(slider_div);
+ // Step must have a value so that (max-min)%step == 0.
+ slider.slider({
+ min: param.min,
+ max: param.max,
+ step: get_slider_step(param.min, param.max),
+ value: param.value,
+ slide: function(event, ui) {
+ var value = ui.value;
+ param.value = value;
+ // Set new value in UI.
+ if (0 < value && value < 1) {
+ value = parseFloat(value).toFixed(2);
+ }
+ values_span.text(value);
+ },
+ change: function(event, ui) {
+ slider.slider("option", "slide").call(slider, event, ui);
+ }
+ });
- // Add to clear floating layout.
- $("<div style='clear: both;'/>").appendTo(param_div);
+ // Enable users to edit parameter's value via text box.
+ edit_slider_values(values_span_container, values_span, slider);
+
+ // Add to clear floating layout.
+ $("<div style='clear: both;'/>").appendTo(param_div);
+ }
+ else if (param instanceof SelectParameter) {
+ var param_div = $("<div>").addClass("slider-row").appendTo(track.dynamic_tool_div);
+
+ // Param label.
+ var label_div = $("<div>").addClass("slider-label").appendTo(param_div);
+ var name_span = $("<span/>").addClass("slider-name").text(param.label + " ").appendTo(label_div);
+
+ // Param selector.
+ var select_div = $("<div/>").addClass("slider").appendTo(param_div);
+ var select_obj = $("<select/>").appendTo(select_div);
+ select_obj.change(function() {param.value = $(this).val();} );
+ var options = select_obj.attr("options");
+ for (var i = 0; i < param.options.length; i++) {
+ var option_data = param.options[i]
+ options[options.length] = new Option(option_data[0], option_data[1]);
+ }
+
+ // Add to clear floating layout.
+ $("<div style='clear: both;'/>").appendTo(param_div);
+ }
});
// Add 'Go' button.
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.
1
0
31 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/3f3740a5f1c9/
changeset: r5291:3f3740a5f1c9
user: dannon
date: 2011-03-31 20:09:47
summary: mapping change: For data libraries of sufficient size, eager loading related to getting the root_folder of a library was causing huge delays. This should reduce the delay in viewing a library of ~500 items by about 90%.
affected #: 1 file (17 bytes)
--- a/lib/galaxy/model/mapping.py Thu Mar 31 11:27:49 2011 -0400
+++ b/lib/galaxy/model/mapping.py Thu Mar 31 14:09:47 2011 -0400
@@ -1268,7 +1268,7 @@
assign_mapper( context, Library, Library.table,
properties=dict(
root_folder=relation( LibraryFolder, backref=backref( "library_root" ) )
- )
+ )
)
assign_mapper( context, LibraryInfoAssociation, LibraryInfoAssociation.table,
@@ -1281,26 +1281,26 @@
) )
assign_mapper( context, LibraryFolder, LibraryFolder.table,
- properties=dict(
- folders=relation(
- LibraryFolder,
+ properties=dict(
+ folders=relation(
+ LibraryFolder,
primaryjoin=( LibraryFolder.table.c.parent_id == LibraryFolder.table.c.id ),
order_by=asc( LibraryFolder.table.c.name ),
backref=backref( "parent", primaryjoin=( LibraryFolder.table.c.parent_id == LibraryFolder.table.c.id ), remote_side=[LibraryFolder.table.c.id] ) ),
- active_folders=relation( LibraryFolder,
- primaryjoin=( ( LibraryFolder.table.c.parent_id == LibraryFolder.table.c.id ) & ( not_( LibraryFolder.table.c.deleted ) ) ),
- order_by=asc( LibraryFolder.table.c.name ),
+ active_folders=relation( LibraryFolder,
+ primaryjoin=( ( LibraryFolder.table.c.parent_id == LibraryFolder.table.c.id ) & ( not_( LibraryFolder.table.c.deleted ) ) ),
+ order_by=asc( LibraryFolder.table.c.name ),
lazy=True, #"""sqlalchemy.exceptions.ArgumentError: Error creating eager relationship 'active_folders' on parent class '<class 'galaxy.model.LibraryFolder'>' to child class '<class 'galaxy.model.LibraryFolder'>': Cant use eager loading on a self referential relationship."""
viewonly=True ),
datasets=relation( LibraryDataset,
- primaryjoin=( ( LibraryDataset.table.c.folder_id == LibraryFolder.table.c.id ) ),
- order_by=asc( LibraryDataset.table.c._name ),
- lazy=False,
+ primaryjoin=( ( LibraryDataset.table.c.folder_id == LibraryFolder.table.c.id ) ),
+ order_by=asc( LibraryDataset.table.c._name ),
+ lazy=True,
viewonly=True ),
active_datasets=relation( LibraryDataset,
primaryjoin=( ( LibraryDataset.table.c.folder_id == LibraryFolder.table.c.id ) & ( not_( LibraryDataset.table.c.deleted ) ) ),
- order_by=asc( LibraryDataset.table.c._name ),
- lazy=False,
+ order_by=asc( LibraryDataset.table.c._name ),
+ lazy=True,
viewonly=True )
) )
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.
1
0
commit/galaxy-central: dannon: workflows api: patch from Tobias Wohlfrom <twohlfrom@illumina.com>
by Bitbucket 31 Mar '11
by Bitbucket 31 Mar '11
31 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/93ca67adcc56/
changeset: r5290:93ca67adcc56
user: dannon
date: 2011-03-31 17:27:49
summary: workflows api: patch from Tobias Wohlfrom <twohlfrom(a)illumina.com>
Fixed label output in workflow API in order to map the input datasets.
affected #: 1 file (9 bytes)
--- a/lib/galaxy/web/api/workflows.py Thu Mar 31 10:16:40 2011 -0400
+++ b/lib/galaxy/web/api/workflows.py Thu Mar 31 11:27:49 2011 -0400
@@ -57,7 +57,7 @@
inputs = {}
for step in latest_workflow.steps:
if step.type == 'data_input':
- inputs[step.id] = {'label':"Input Dataset", 'value':""}
+ inputs[step.id] = {'label':step.tool_inputs['name'], 'value':""}
else:
pass
# Eventually, allow regular tool parameters to be inserted and modified at runtime.
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.
1
0
commit/galaxy-central: dan: Fix for wrapping input values for change_format and label tags when tool is using check_values="false". Fixes issue seen when e.g. using an output label in a data_source tool.
by Bitbucket 31 Mar '11
by Bitbucket 31 Mar '11
31 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/b453988dce62/
changeset: r5289:b453988dce62
user: dan
date: 2011-03-31 16:16:40
summary: Fix for wrapping input values for change_format and label tags when tool is using check_values="false". Fixes issue seen when e.g. using an output label in a data_source tool.
affected #: 1 file (309 bytes)
--- a/lib/galaxy/tools/actions/__init__.py Wed Mar 30 17:20:10 2011 -0400
+++ b/lib/galaxy/tools/actions/__init__.py Thu Mar 31 10:16:40 2011 -0400
@@ -133,16 +133,18 @@
else:
new_list.append( value )
return new_list
- def wrap_values( inputs, input_values ):
+ def wrap_values( inputs, input_values, skip_missing_values = False ):
# Wrap tool inputs as necessary
for input in inputs.itervalues():
+ if input.name not in input_values and skip_missing_values:
+ continue
if isinstance( input, Repeat ):
for d in input_values[ input.name ]:
- wrap_values( input.inputs, d )
+ wrap_values( input.inputs, d, skip_missing_values = skip_missing_values )
elif isinstance( input, Conditional ):
values = input_values[ input.name ]
current = values[ "__current_case__" ]
- wrap_values( input.cases[current].inputs, values )
+ wrap_values( input.cases[current].inputs, values, skip_missing_values = skip_missing_values )
elif isinstance( input, DataToolParameter ):
input_values[ input.name ] = \
galaxy.tools.DatasetFilenameWrapper( input_values[ input.name ],
@@ -254,7 +256,7 @@
if output.change_format:
if params is None:
params = make_dict_copy( incoming )
- wrap_values( tool.inputs, params )
+ wrap_values( tool.inputs, params, skip_missing_values = not tool.check_values )
for change_elem in output.change_format:
for when_elem in change_elem.findall( 'when' ):
check = when_elem.get( 'input', None )
@@ -304,7 +306,7 @@
# <outputs>
# <data format="input" name="output" label="Blat on ${<input_param>.name}" />
# </outputs>
- wrap_values( tool.inputs, params )
+ wrap_values( tool.inputs, params, skip_missing_values = not tool.check_values )
#tool (only needing to be set once) and on_string (set differently for each label) are overwritten for each output dataset label being determined
params['tool'] = tool
params['on_string'] = on_text
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.
1
0
commit/galaxy-central: jgoecks: Send visual analytics output datasets to user's current history rather than input datasets' history. This enables users not logged in to use visual analytics in shared visualizations.
by Bitbucket 30 Mar '11
by Bitbucket 30 Mar '11
30 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/81b332adf892/
changeset: r5288:81b332adf892
user: jgoecks
date: 2011-03-30 23:20:10
summary: Send visual analytics output datasets to user's current history rather than input datasets' history. This enables users not logged in to use visual analytics in shared visualizations.
affected #: 1 file (11 bytes)
--- a/lib/galaxy/web/controllers/tracks.py Wed Mar 30 11:44:08 2011 -0400
+++ b/lib/galaxy/web/controllers/tracks.py Wed Mar 30 17:20:10 2011 -0400
@@ -2,7 +2,7 @@
Support for constructing and viewing custom "track" browsers within Galaxy.
"""
-import re, time, pkg_resources
+import re, pkg_resources
pkg_resources.require( "bx-python" )
from bx.seq.twobit import TwoBitFile
@@ -13,7 +13,6 @@
from galaxy.web.framework import simplejson
from galaxy.web.framework.helpers import time_ago, grids
from galaxy.util.bunch import Bunch
-from galaxy import util
from galaxy.datatypes.interval import Gff
from galaxy.visualization.tracks.data_providers import *
@@ -703,7 +702,8 @@
# Set input datasets for tool. Input datasets are subsets of full
# datasets and are based on chrom, low, high.
#
- hda_permissions = trans.app.security_agent.history_get_default_permissions( original_dataset.history )
+ job_history = trans.get_history( create=True )
+ hda_permissions = trans.app.security_agent.history_get_default_permissions( job_history )
messages_list = []
for jida in original_job.input_datasets:
input_dataset = jida.dataset
@@ -721,7 +721,7 @@
name="Subset [%s:%i-%i] of data %i" % \
( chrom, low, high, input_dataset.hid ),
visible=False )
- input_dataset.history.add_dataset( subset_dataset )
+ job_history.add_dataset( subset_dataset )
trans.sa_session.add( subset_dataset )
trans.app.security_agent.set_all_dataset_permissions( subset_dataset.dataset, hda_permissions )
@@ -744,7 +744,7 @@
# Start tool and handle outputs.
#
try:
- subset_job, subset_job_outputs = tool.execute( trans, incoming=tool_params, history=original_dataset.history )
+ subset_job, subset_job_outputs = tool.execute( trans, incoming=tool_params, history=job_history )
except Exception, e:
# Lots of things can go wrong when trying to execute tool.
return to_json_string( { "error" : True, "message" : e.__class__.__name__ + ": " + str(e) } )
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.
1
0
commit/galaxy-central: jgoecks: Trackster: add BED and GFF support to visual analytics framework, and enable GOPS intersect and subtract tools to work with visual analytics.
by Bitbucket 30 Mar '11
by Bitbucket 30 Mar '11
30 Mar '11
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/e1f0a0dc1493/
changeset: r5287:e1f0a0dc1493
user: jgoecks
date: 2011-03-30 17:44:08
summary: Trackster: add BED and GFF support to visual analytics framework, and enable GOPS intersect and subtract tools to work with visual analytics.
affected #: 5 files (885 bytes)
--- a/lib/galaxy/visualization/tracks/data_providers.py Wed Mar 30 10:40:09 2011 -0400
+++ b/lib/galaxy/visualization/tracks/data_providers.py Wed Mar 30 11:44:08 2011 -0400
@@ -487,8 +487,19 @@
col_name_data_attr_mapping = { 4 : { 'index': 4 , 'name' : 'Score' } }
def write_data_to_file( self, chrom, start, end, filename ):
- # TODO: write function.
- pass
+ source = open( self.original_dataset.file_name )
+ index = Indexes( self.converted_dataset.file_name )
+ out = open( filename, 'w' )
+ for start, end, offset in index.find(chrom, start, end):
+ source.seek( offset )
+ if isinstance( self.original_dataset.datatype, Gff ):
+ reader = GFFReaderWrapper( source, fix_strand=True )
+ feature = reader.next()
+ for interval in feature.intervals:
+ out.write(interval.raw_line + '\n')
+ elif isinstance( self.original_dataset.datatype, Bed ):
+ out.write( source.readline() )
+ out.close()
def get_filters( self ):
""" Returns a dataset's filters. """
--- a/lib/galaxy/web/controllers/tracks.py Wed Mar 30 10:40:09 2011 -0400
+++ b/lib/galaxy/web/controllers/tracks.py Wed Mar 30 11:44:08 2011 -0400
@@ -724,9 +724,13 @@
input_dataset.history.add_dataset( subset_dataset )
trans.sa_session.add( subset_dataset )
trans.app.security_agent.set_all_dataset_permissions( subset_dataset.dataset, hda_permissions )
- if input_dataset.extension == 'bam':
- data_provider = BamDataProvider( original_dataset=input_dataset, converted_dataset=converted_dataset )
- data_provider.write_data_to_file( chrom, low, high, subset_dataset.file_name )
+
+ # Write data subset to new HDA.
+ data_provider_class = get_data_provider( original_dataset=input_dataset )
+ data_provider = data_provider_class( original_dataset=input_dataset,
+ converted_dataset=converted_dataset )
+ data_provider.write_data_to_file( chrom, low, high, subset_dataset.file_name )
+
# TODO: size not working.
subset_dataset.set_size()
subset_dataset.info = "Data subset for trackster"
--- a/static/scripts/trackster.js Wed Mar 30 10:40:09 2011 -0400
+++ b/static/scripts/trackster.js Wed Mar 30 11:44:08 2011 -0400
@@ -1392,6 +1392,7 @@
// Slider.
var slider_div = $("<div/>").addClass("slider").appendTo(param_div);
var slider = $("<div id='" + param.name + "-param-control'>").appendTo(slider_div);
+ // Step must have a value so that (max-min)%step == 0.
slider.slider({
min: param.min,
max: param.max,
--- a/tools/new_operations/intersect.xml Wed Mar 30 10:40:09 2011 -0400
+++ b/tools/new_operations/intersect.xml Wed Mar 30 11:44:08 2011 -0400
@@ -28,7 +28,7 @@
<param format="interval,gff" name="input2" type="data" help="Second dataset"><label>that intersect</label></param>
- <param name="min" size="4" type="integer" value="1" help="(bp)">
+ <param name="min" size="4" type="integer" value="1" min="0" max="600000" help="(bp)"><label>for at least</label></param></inputs>
--- a/tools/new_operations/subtract.xml Wed Mar 30 10:40:09 2011 -0400
+++ b/tools/new_operations/subtract.xml Wed Mar 30 11:44:08 2011 -0400
@@ -31,7 +31,7 @@
<option value="-p">Non-overlapping pieces of intervals</option></param>
- <param name="min" size="4" type="integer" value="1" help="(bp)">
+ <param name="min" size="4" type="integer" value="1" min="0" max="600000" help="(bp)"><label>where minimal overlap is</label></param></inputs>
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.
1
0
1 new changeset in galaxy-central:
http://bitbucket.org/galaxy/galaxy-central/changeset/3844b6d684b6/
changeset: r5286:3844b6d684b6
user: dan
date: 2011-03-30 16:40:09
summary: Minor fix for 5283:41a416e61021.
affected #: 1 file (4 bytes)
--- a/lib/galaxy/datatypes/data.py Wed Mar 30 10:38:45 2011 -0400
+++ b/lib/galaxy/datatypes/data.py Wed Mar 30 10:40:09 2011 -0400
@@ -441,7 +441,7 @@
est_lines = self.estimate_file_lines(dataset)
dataset.blurb = "~%s %s" % ( util.commaify(util.roundify(str(est_lines))), inflector.cond_plural(est_lines, self.line_class) )
else:
- dataset.blurb = "%s %s" % util.commaify( str(line_count) ), inflector.cond_plural(line_count, self.line_class)
+ dataset.blurb = "%s %s" % ( util.commaify( str(line_count) ), inflector.cond_plural(line_count, self.line_class) )
else:
dataset.peek = 'file does not exist'
dataset.blurb = 'file purged from disk'
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.
1
0