diff --git a/static/scripts/trackster.js b/static/scripts/trackster.js index aaf520a4856..e021b06e89f 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, { @@ -1948,20 +1949,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"); } @@ -2009,10 +2007,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 = $( "
" ) @@ -2040,9 +2042,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); @@ -2083,99 +2083,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); } }); @@ -2235,187 +2159,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; - }, - /** - * Draw summary tree on canvas. - */ - draw_summary_tree: function(canvas, points, delta, max, w_scale, required_height, tile_low, left_offset) { - var - // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases - // start. However, height of each rectangle is relative to required_height; hence, the - // max rectangle is required_height. - base_y = required_height + LABEL_SPACING + CHAR_HEIGHT_PX; - delta_x_px = Math.ceil(delta * w_scale); - - var max_label = $("
").addClass('yaxislabel'); - max_label.text(max); - - max_label.css({ position: "absolute", top: "22px", left: "10px" }); - max_label.prependTo(this.container_div); - - var ctx = canvas.get(0).getContext("2d"); - for (var i = 0, len = points.length; i < len; i++) { - var x = Math.floor( (points[i][0] - tile_low) * w_scale ); - var y = points[i][1]; - - if (!y) { continue; } - var y_px = y / max * required_height; - - ctx.fillStyle = "black"; - ctx.fillRect(x + left_offset, base_y - y_px, delta_x_px, y_px); - - // Draw number count if it can fit the number with some padding, otherwise things clump up - var text_padding_req_x = 4; - if (this.prefs.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) { - ctx.fillStyle = "#666"; - ctx.textAlign = "center"; - ctx.fillText(y, x + left_offset + (delta_x_px/2), 10); - } } + + return inc_slots.slot_features( features ); }, /** * Draw feature. @@ -2578,29 +2333,18 @@ $.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; - var canvas = document.createElement("canvas"); - if (window.G_vmlCanvasManager) { G_vmlCanvasManager.initElement(canvas); } // EXCANVAS HACK - canvas = $(canvas); - - // - // Set mode if Auto. - // + // Set display mode if Auto. if (mode === "Auto") { if (result.dataset_type === "summary_tree") { mode = result.dataset_type; @@ -2625,55 +2369,68 @@ $.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 = this.inc_slots[w_scale].slots; } // // Set up for drawing. // - canvas.get(0).width = width + left_offset; - canvas.get(0).height = required_height; - if (result.dataset_type === "summary_tree") { - // Increase canvas height in order to display max label. - canvas.get(0).height += LABEL_SPACING + CHAR_HEIGHT_PX; - } + var canvas = this.view.canvas_manager.new_canvas(); + + canvas.width = width + left_offset; + canvas.height = required_height; + parent_element.parent().css("height", Math.max(this.height_px, required_height) + "px"); // console.log(( tile_low - this.view.low ) * w_scale, tile_index, w_scale); - var ctx = canvas.get(0).getContext("2d"); + var ctx = canvas.getContext("2d"); ctx.fillStyle = this.prefs.block_color; ctx.font = this.default_font; ctx.textAlign = "right"; this.container_div.find(".yaxislabel").remove(); - // - // Draw summary tree. If tree is drawn, canvas is returned. - // - if (mode === "summary_tree") { - this.draw_summary_tree(canvas, result.data, result.delta, result.max, w_scale, required_height, - tile_low, left_offset); - return canvas; - } - - // // If there is a message, draw it on canvas so that it moves around with canvas, and make the border red // to indicate region where message is applicable if (result.message) { - canvas.css({ + $(canvas).css({ "border-top": "1px solid red" }); @@ -2686,7 +2443,7 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { // If there's no data, return. if (!result.data) { - return canvas; + return $(canvas); } } @@ -2729,7 +2486,7 @@ $.extend(FeatureTrack.prototype, TiledTrack.prototype, { width, left_offset, ref_seq); } } - return canvas; + return $(canvas); } }); @@ -3130,4 +2887,326 @@ $.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. + * + * This implementation is incremental, any feature assigned a slot will be + * retained for slotting future features. + */ +var FeatureSlotter = function ( w_scale, include_label, measureText ) { + this.slots = {}; + this.start_end_dct = {}; + this.w_scale = w_scale; + this.include_label = include_label; + this.measureText = measureText; +} + +/** + * Slot a set of features, `this.slots` will be updated with slots by id, and + * the largest slot required for the passed set of features is returned + */ +FeatureSlotter.prototype.slot_features = function( features ) { + var w_scale = this.w_scale, inc_slots = this.slots, start_end_dct = this.start_end_dct, + undone = [], slotted = [], highest_slot = 0; + + // If feature already exists in slots (from previously seen tiles), use the same slot, + // otherwise if not seen, add to "undone" list for slot calculation. + + // TODO: Should calculate zoom tile index, which will improve performance + // by only having to look at a smaller subset + // if (this.start_end_dct[0] === undefined) { this.start_end_dct[0] = []; } + for (var i = 0, len = features.length; i < len; i++) { + var feature = features[i], + feature_uid = feature[0]; + if (inc_slots[feature_uid] !== undefined) { + highest_slot = Math.max(highest_slot, inc_slots[feature_uid]); + slotted.push(inc_slots[feature_uid]); + } else { + undone.push(i); + } + } + + // Slot unslotted features. + + // Find the first slot such that current feature doesn't overlap any other features in that slot. + // Returns -1 if no slot was found. + var find_slot = function(f_start, f_end) { + // TODO: Fix constants + for (var slot_num = 0; slot_num <= MAX_FEATURE_DEPTH; slot_num++) { + var has_overlap = false, + slot = start_end_dct[slot_num]; + if (slot !== undefined) { + // Iterate through features already in slot to see if current feature will fit. + for (var k = 0, k_len = slot.length; k < k_len; k++) { + var s_e = slot[k]; + if (f_end > s_e[0] && f_start < s_e[1]) { + // There is overlap + has_overlap = true; + break; + } + } + } + if (!has_overlap) { + return slot_num; + } + } + return -1; + }; + + // Do slotting. + for (var i = 0, len = undone.length; i < len; i++) { + var feature = features[undone[i]], + feature_uid = feature[0], + feature_start = feature[1], + feature_end = feature[2], + feature_name = feature[3], + // Where to start, end drawing on screen. + f_start = Math.floor( feature_start * w_scale ), + f_end = Math.ceil( feature_end * w_scale ), + text_len = this.measureText(feature_name).width, + text_align; + + // Update start, end drawing locations to include feature name. + // Try to put the name on the left, if not, put on right. + if (feature_name !== undefined && this.include_label ) { + // Add gap for label spacing and extra pack space padding + // TODO: Fix constants + text_len += (LABEL_SPACING + PACK_SPACING); + if (f_start - text_len >= 0) { + f_start -= text_len; + text_align = "left"; + } else { + f_end += text_len; + text_align = "right"; + } + } + + // Find slot. + var slot_num = find_slot(f_start, f_end); + /* + if (slot_num < 0) { + + TODO: this is not yet working -- + console.log(feature_uid, "looking for slot with text on the right"); + // Slot not found. If text was on left, try on right and see + // if slot can be found. + // TODO: are there any checks we need to do to ensure that text + // will fit on tile? + if (text_align === "left") { + f_start -= text_len; + f_end -= text_len; + text_align = "right"; + slot_num = find_slot(f_start, f_end); + } + if (slot_num >= 0) { + console.log(feature_uid, "found slot with text on the right"); + } + + } + */ + // Do slotting. + if (slot_num >= 0) { + // Add current feature to slot. + if (start_end_dct[slot_num] === undefined) { + start_end_dct[slot_num] = []; + } + start_end_dct[slot_num].push([f_start, f_end]); + inc_slots[feature_uid] = slot_num; + highest_slot = Math.max(highest_slot, slot_num); + } + else { + // TODO: remove this warning when skipped features are handled. + // Show warning for skipped feature. + //console.log("WARNING: not displaying feature", feature_uid, f_start, f_end); + } + } + + // Debugging: view slots data. + /* + for (var i = 0; i < MAX_FEATURE_DEPTH; i++) { + var slot = start_end_dct[i]; + if (slot !== undefined) { + console.log(i, "*************"); + for (var k = 0, k_len = slot.length; k < k_len; k++) { + console.log("\t", slot[k][0], slot[k][1]); + } + } + } + */ + return highest_slot + 1; +} + +// ---- Painters ---- + +/** + * SummaryTreePainter, a histogram showing number of intervals in a region + */ +var SummaryTreePainter = function( data, delta, max, view_start, view_end, show_counts ) { + // Data and data properties + this.data = data; + this.delta = delta; + this.max = max; + // View + this.view_start = view_start; + this.view_end = view_end; + // Drawing prefs + this.show_counts = show_counts; +} + +SummaryTreePainter.prototype.draw = function( ctx, width, height ) { + + var view_start = this.view_start, + view_range = this.view_end - this.view_start, + w_scale = width / view_range; + + var points = this.data, delta = this.delta, max = this.max, + // Set base Y so that max label and data do not overlap. Base Y is where rectangle bases + // start. However, height of each rectangle is relative to required_height; hence, the + // max rectangle is required_height. + base_y = height + LABEL_SPACING + CHAR_HEIGHT_PX; + delta_x_px = Math.ceil(delta * w_scale); + + ctx.save(); + + for (var i = 0, len = points.length; i < len; i++) { + + var x = Math.floor( (points[i][0] - view_start) * w_scale ); + var y = points[i][1]; + + if (!y) { continue; } + var y_px = y / max * height; + + ctx.fillStyle = "black"; + ctx.fillRect( x, base_y - y_px, delta_x_px, y_px ); + + // Draw number count if it can fit the number with some padding, otherwise things clump up + var text_padding_req_x = 4; + if (this.show_counts && (ctx.measureText(y).width + text_padding_req_x) < delta_x_px) { + ctx.fillStyle = "#666"; + ctx.textAlign = "center"; + ctx.fillText(y, x + (delta_x_px/2), 10); + } + } + + ctx.restore(); +} + +var LinePainter = function( data, view_start, view_end, min_value, max_value, color, mode ) { + // Data and data properties + this.data = data; + // View + this.view_start = view_start; + this.view_end = view_end; + // Drawing prefs + this.min_value = min_value; + this.max_value = max_value; + this.color = color; + this.mode = mode; +} + +LinePainter.prototype.draw = function( ctx, width, height ) { + var + in_path = false, + min_value = this.min_value, + max_value = this.max_value, + vertical_range = max_value - min_value, + height_px = height, + view_start = this.view_start, + view_range = this.view_end - this.view_start, + w_scale = width / view_range, + mode = this.mode, + data = this.data; + + ctx.save(); + + // Pixel position of 0 on the y axis + var y_zero = Math.round( height + min_value / vertical_range * height ); + + // Line at 0.0 + if ( mode !== "Intensity" ) { + ctx.beginPath(); + ctx.moveTo( 0, y_zero ); + ctx.lineTo( width, y_zero ); + // ctx.lineWidth = 0.5; + ctx.fillStyle = "#aaa"; + ctx.stroke(); + } + + ctx.beginPath(); + ctx.fillStyle = this.color; + var x_scaled, y, delta_x_px; + if (data.length > 1) { + delta_x_px = Math.ceil((data[1][0] - data[0][0]) * w_scale); + } else { + delta_x_px = 10; + } + for (var i = 0, len = data.length; i < len; i++) { + x_scaled = Math.round((data[i][0] - view_start) * w_scale); + y = data[i][1]; + if (y === null) { + if (in_path && mode === "Filled") { + ctx.lineTo(x_scaled, height_px); + } + in_path = false; + continue; + } + if (y < min_value) { + y = min_value; + } else if (y > max_value) { + y = max_value; + } + + if (mode === "Histogram") { + // y becomes the bar height in pixels, which is the negated for canvas coords + y = Math.round( y / vertical_range * height_px ); + ctx.fillRect(x_scaled, y_zero, delta_x_px, - y ); + } else if (mode === "Intensity" ) { + y = 255 - Math.floor( (y - min_value) / vertical_range * 255 ); + ctx.fillStyle = "rgb(" +y+ "," +y+ "," +y+ ")"; + ctx.fillRect(x_scaled, 0, delta_x_px, height_px); + } else { + // console.log(y, track.min_value, track.vertical_range, (y - track.min_value) / track.vertical_range * track.height_px); + y = Math.round( height_px - (y - min_value) / vertical_range * height_px ); + // console.log(canvas.get(0).height, canvas.get(0).width); + if (in_path) { + ctx.lineTo(x_scaled, y); + } else { + in_path = true; + if (mode === "Filled") { + ctx.moveTo(x_scaled, height_px); + ctx.lineTo(x_scaled, y); + } else { + ctx.moveTo(x_scaled, y); + } + } + } + } + if (mode === "Filled") { + if (in_path) { + ctx.lineTo( x_scaled, y_zero ); + ctx.lineTo( 0, y_zero ); + } + ctx.fill(); + } else { + ctx.stroke(); + } + + ctx.restore(); +} \ No newline at end of file