From ba71264790f01daf15b56847fae8ceb7fac45d99 Mon Sep 17 00:00:00 2001 From: Daniel Blankenberg Date: Thu, 31 Mar 2011 10:16:40 -0400 Subject: [PATCH 01/13] 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. --- lib/galaxy/tools/actions/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py index f238cdfd123..3dbe8b6821f 100644 --- a/lib/galaxy/tools/actions/__init__.py +++ b/lib/galaxy/tools/actions/__init__.py @@ -133,16 +133,18 @@ class DefaultToolAction( object ): 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 @@ class DefaultToolAction( object ): 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 @@ class DefaultToolAction( object ): # # # - 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 From c5dda0127692a6d88c23d43928f51a373f08d62b Mon Sep 17 00:00:00 2001 From: Dannon Baker Date: Thu, 31 Mar 2011 11:27:49 -0400 Subject: [PATCH 02/13] workflows api: patch from Tobias Wohlfrom Fixed label output in workflow API in order to map the input datasets. --- lib/galaxy/web/api/workflows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/web/api/workflows.py b/lib/galaxy/web/api/workflows.py index 0daacdcb3c2..3623e735924 100644 --- a/lib/galaxy/web/api/workflows.py +++ b/lib/galaxy/web/api/workflows.py @@ -57,7 +57,7 @@ class WorkflowsAPIController(BaseController): 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. From 7c1b42e3f4e0f6800a3dfd6e0ec8903934098573 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 28 Mar 2011 20:32:43 -0400 Subject: [PATCH 03/13] Extract feature slotting into a new class --- static/scripts/trackster.js | 294 +++++++++++++++++++----------------- 1 file changed, 156 insertions(+), 138 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 3a406d787e3..6b8da664c4e 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -2236,150 +2236,18 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { * 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prot } }); +// ---- 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; +} From fe79a2ecaf4a05f97c154a89e69bfdccf55e5bb0 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 28 Mar 2011 21:35:29 -0400 Subject: [PATCH 04/13] Extract SummaryTree painter, abstract canvas creation into CanvasManager class --- static/scripts/trackster.js | 128 +++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 46 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 6b8da664c4e..2a73781ae39 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -372,6 +372,7 @@ var View = function( container, title, vis_id, dbkey, callback ) { 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 @@ $.extend(ReferenceTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { * 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 = $("
").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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // // 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // If there's no data, return. if (!result.data) { - return canvas; + return $(canvas); } } @@ -2598,7 +2573,7 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { width, left_offset, ref_seq); } } - return canvas; + return $(canvas); } }); @@ -3001,6 +2976,20 @@ $.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prot // ---- 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 @@ FeatureSlotter.prototype.slot_features = function( features ) { */ 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(); +} + From 8fa49921f8cc98ac1ec725d8f2fd130f6f6a0c16 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 29 Mar 2011 13:56:44 -0400 Subject: [PATCH 05/13] Extract LinePainter from LineTrack --- static/scripts/trackster.js | 220 ++++++++++++++++++++---------------- 1 file changed, 123 insertions(+), 97 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 2a73781ae39..1d8a6681db8 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -2008,10 +2008,14 @@ var LineTrack = function (name, view, hda_ldda, dataset_id, prefs) { 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 = $( "
" ) @@ -2039,9 +2043,7 @@ var LineTrack = function (name, view, hda_ldda, dataset_id, prefs) { 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 @@ $.extend(LineTrack.prototype, TiledTrack.prototype, { 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 @@ SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, til 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 From f09185dc71a6539d4867c8e541948fc98356787c Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 29 Mar 2011 13:56:57 -0400 Subject: [PATCH 06/13] Automated merge with https://bitbucket.org/galaxy/galaxy-central From a2deb073d80d8f3c70d4edc774a879210e0eb4be Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 29 Mar 2011 18:40:18 -0400 Subject: [PATCH 07/13] Streamlining feature track drawing in preparation for pulling out a Painter, cleaning up Painter interface (not quite a common interface yet) --- static/scripts/trackster.js | 117 ++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 1d8a6681db8..616987827b7 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -2173,22 +2173,6 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 = $("
").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. */ @@ -2350,25 +2334,18 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { * 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { } } } + + // 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 = $("
").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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ FeatureSlotter.prototype.slot_features = function( features ) { /** * 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 @@ SummaryTreePainter.prototype.draw = function( ctx, w_scale, required_height, til } 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 From affd7e292b64ae83b1bb7408836fc98419a99cdc Mon Sep 17 00:00:00 2001 From: James Taylor Date: Tue, 29 Mar 2011 18:40:24 -0400 Subject: [PATCH 08/13] Automated merge with https://bitbucket.org/galaxy/galaxy-central From 84117841d65f2260b9c15f0aff086939a2e6f09c Mon Sep 17 00:00:00 2001 From: James Taylor Date: Wed, 30 Mar 2011 13:01:41 -0400 Subject: [PATCH 09/13] Automated merge with https://bitbucket.org/galaxy/galaxy-central From 6d1f5d85548ca4b27c3ebe08edddece10a9d5ce9 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Wed, 30 Mar 2011 13:09:42 -0400 Subject: [PATCH 10/13] Fix accidently introduced tabs --- static/scripts/trackster.js | 468 ++++++++++++++++++------------------ 1 file changed, 234 insertions(+), 234 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 616987827b7..91ef564a58d 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -2012,10 +2012,10 @@ var LineTrack = function (name, view, hda_ldda, dataset_id, prefs) { }; $.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 = $( "
" ) @@ -2086,20 +2086,20 @@ $.extend(LineTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { */ 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { } } } - - // 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 = $("
").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 = $("
").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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // // 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 @@ FeatureSlotter.prototype.slot_features = function( features ) { // 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 @@ FeatureSlotter.prototype.slot_features = function( 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; + // 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 @@ var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_ 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 @@ var LinePainter = function( data, view_start, view_end, min_value, max_value, co 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 @@ LinePainter.prototype.draw = function( ctx, width, 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.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 +} From 693009f15329844b6df6129f0f28c2e3bffdbb8c Mon Sep 17 00:00:00 2001 From: James Taylor Date: Wed, 30 Mar 2011 18:30:22 -0400 Subject: [PATCH 11/13] Extracted feature painters for generic linked features, VCF, and reads. Not all types have been completely tested yet --- static/scripts/trackster.js | 1134 +++++++++++++++++++---------------- 1 file changed, 617 insertions(+), 517 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 91ef564a58d..5cd3b5a8620 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -1960,7 +1960,7 @@ $.extend(ReferenceTrack.prototype, TiledTrack.prototype, { 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 @@ $.extend(LineTrack.prototype, TiledTrack.prototype, { 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 @@ var FeatureTrack = function (name, view, hda_ldda, dataset_id, prefs, filters, t 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, { /** @@ -2173,141 +2175,6 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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. */ @@ -2394,27 +2261,44 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // 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 @@ var ReadTrack = function (name, view, hda_ldda, dataset_id, prefs, filters) { 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 - - 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 @@ $.extend(ToolDataFeatureTrack.prototype, TiledTrack.prototype, FeatureTrack.prot // ---- 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 @@ LinePainter.prototype.draw = function( ctx, width, height ) { 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 - + 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; + } + } +}); From a127f8c67720d38c8de634c95fce5e237eaf1f11 Mon Sep 17 00:00:00 2001 From: James Taylor Date: Wed, 30 Mar 2011 18:30:35 -0400 Subject: [PATCH 12/13] Automated merge with https://bitbucket.org/galaxy/galaxy-central From 2a96970ef3b04b74cc2e08b802e47739d172fc7e Mon Sep 17 00:00:00 2001 From: James Taylor Date: Thu, 31 Mar 2011 13:05:24 -0400 Subject: [PATCH 13/13] Remove left_offset stuff from ReadTracks, fix some other stuff related to padding and height calculation, read tracks now work fine --- static/scripts/trackster.js | 42 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index 5cd3b5a8620..a7e060153ef 100644 --- a/static/scripts/trackster.js +++ b/static/scripts/trackster.js @@ -2298,7 +2298,8 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // 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 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { 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 @@ extend( FeaturePainter.prototype, { // 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 @@ extend( LinkedFeaturePainter.prototype, FeaturePainter.prototype, { 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 @@ extend( VariantPainter.prototype, FeaturePainter.prototype, { // 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { } 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { } 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { // 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 - - 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { // 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { // 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 @@ extend( ReadPainter.prototype, FeaturePainter.prototype, { 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; }