Skip to content

Commit

Permalink
Refactor heatmap module into smaller methods
Browse files Browse the repository at this point in the history
  • Loading branch information
eweitz committed Sep 4, 2018
1 parent ea33d74 commit 03d2a77
Showing 1 changed file with 146 additions and 114 deletions.
260 changes: 146 additions & 114 deletions src/js/annotations/heatmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,42 @@ import * as d3selection from 'd3-selection';

var d3 = Object.assign({}, d3selection);

function writeCanvases(chr, chrLeft, chrWidth, ideoHeight, ideo) {
var j, trackLeft, trackWidth, canvas, context,
contextArray = [],
numAnnotTracks = ideo.config.numAnnotTracks;

// Create a canvas for each annotation track on this chromosome
for (j = 0; j < numAnnotTracks; j++) {
trackWidth = chrWidth - 1;
trackLeft = chrLeft + j * chrWidth - (trackWidth * numAnnotTracks);
canvas = d3.select(ideo.config.container + ' #_ideogramOuterWrap')
.append('canvas')
.attr('id', chr.id + '-canvas-' + j)
.attr('width', chrWidth - 1)
.attr('height', ideoHeight)
.style('position', 'absolute')
.style('left', trackLeft + 'px');
context = canvas.nodes()[0].getContext('2d');
contextArray.push(context);
}

return contextArray;
}

function fillCanvasAnnots(annots, contextArray, chrWidth, ideoMarginTop) {
var j, annot, context, x;

// Fill in the canvas(es) with annotation colors to draw a heatmap
for (j = 0; j < annots.length; j++) {
annot = annots[j];
context = contextArray[annot.trackIndex];
context.fillStyle = annot.color;
x = annot.trackIndex - 1;
context.fillRect(x, annot.startPx + ideoMarginTop, chrWidth, 0.5);
}
}

/**
* Draws a 1D heatmap of annotations along each chromosome.
* Ideal for representing very dense annotation sets in a granular manner
Expand All @@ -14,13 +50,10 @@ var d3 = Object.assign({}, d3selection);
* @param annots {Array} Processed annotation objects
*/
function drawHeatmaps(annotContainers) {

var ideo = this,
var annots, chrLeft, contextArray, chrWidth, i, chr,
ideo = this,
ideoMarginTop = ideo._layout.margin.top,
ideoHeight = ideo.config.chrHeight + ideoMarginTop,
numAnnotTracks = ideo.config.numAnnotTracks,
annots, chr, chrLeft, trackLeft, trackWidth, canvas, contextArray,
chrWidth, context, annot, x, i, j;
ideoHeight = ideo.config.chrHeight + ideoMarginTop;

d3.selectAll(ideo.config.container + ' canvas').remove();

Expand All @@ -30,40 +63,116 @@ function drawHeatmaps(annotContainers) {
annots = annotContainers[i].annots;
chr = ideo.chromosomesArray[i];
chrWidth = ideo.config.chrWidth;
chrLeft = ideo._layout.getChromosomeSetYTranslate(i)

contextArray = [];

// Create a canvas for each annotation track on this chromosome
for (j = 0; j < numAnnotTracks; j++) {
trackWidth = chrWidth - 1;
trackLeft = chrLeft + j * chrWidth - (trackWidth * numAnnotTracks);
canvas = d3.select(ideo.config.container + ' #_ideogramOuterWrap')
.append('canvas')
.attr('id', chr.id + '-canvas-' + j)
.attr('width', chrWidth - 1)
.attr('height', ideoHeight)
.style('position', 'absolute')
.style('left', trackLeft + 'px');
context = canvas.nodes()[0].getContext('2d');
contextArray.push(context);
}
chrLeft = ideo._layout.getChromosomeSetYTranslate(i);

// Fill in the canvas(es) with annotation colors to draw a heatmap
for (j = 0; j < annots.length; j++) {
annot = annots[j];
context = contextArray[annot.trackIndex];
context.fillStyle = annot.color;
x = annot.trackIndex - 1;
context.fillRect(x, annot.startPx + ideoMarginTop, chrWidth, 0.5);
}
contextArray = writeCanvases(chr, chrLeft, chrWidth, ideoHeight, ideo)
fillCanvasAnnots(annots, contextArray, chrWidth, ideoMarginTop);
}

if (ideo.onDrawAnnotsCallback) {
ideo.onDrawAnnotsCallback();
}
}

function shouldUseThresholdColor(m, numThresholds, value, prevThreshold,
threshold) {

return (
// If this is the last threshold, and
// its value is "+" and the value is above the previous threshold...
m === numThresholds && (
threshold === '+' && value > prevThreshold
) ||

// ... or if the value matches the threshold...
value === threshold ||

// ... or if this isn't the first or last threshold, and
// the value is between this threshold and the previous one...
m !== 0 && m !== numThresholds && (
value <= threshold &&
value > prevThreshold
) ||

// ... or if this is the first threshold and the value is
// at or below the threshold
m === 0 && value <= threshold
);
}

function getHeatmapAnnotColor(thresholds, value) {
var m, numThresholds, thresholdList, threshold, tvInt, thresholdColor,
prevThreshold, useThresholdColor, color;

for (m = 0; m < thresholds.length; m++) {
numThresholds = thresholds.length - 1;
thresholdList = thresholds[m];
threshold = thresholdList[0];

// The threshold value is usually an integer,
// but can also be a "+" character indicating that
// this threshold is anything greater than the previous threshold.
tvInt = parseInt(threshold);
if (isNaN(tvInt) === false) threshold = tvInt;
if (m !== 0) prevThreshold = parseInt(thresholds[m - 1][0]);
thresholdColor = thresholdList[1];

useThresholdColor = shouldUseThresholdColor(m, numThresholds, value,
prevThreshold, threshold);

if (useThresholdColor) {
color = thresholdColor;
}
}

return color;
}

function getNewRawAnnots(heatmapKeyIndexes, rawAnnots, ideo) {
var j, k, ra, newRa, value, thresholds, color, trackIndex,
newRas = [];

for (j = 0; j < rawAnnots.length; j++) {
ra = rawAnnots[j];
for (k = 0; k < heatmapKeyIndexes.length; k++) {
newRa = ra.slice(0, 3); // name, start, length

value = ra[heatmapKeyIndexes[k]];
thresholds = ideo.config.heatmaps[k].thresholds;
color = getHeatmapAnnotColor(thresholds, value);

trackIndex = k;
newRa.push(trackIndex, color, value);
newRas.push(newRa);
}
}

return newRas;
}

function getNewRawAnnotContainers(heatmapKeyIndexes, rawAnnotBoxes, ideo) {
var raContainer, chr, rawAnnots, newRas, i,
newRaContainers = [];

for (i = 0; i < rawAnnotBoxes.length; i++) {
raContainer = rawAnnotBoxes[i];
chr = raContainer.chr;

rawAnnots = raContainer.annots;
newRas = getNewRawAnnots(heatmapKeyIndexes, rawAnnots, ideo);

newRaContainers.push({chr: chr, annots: newRas});
}
return newRaContainers;
}

function reportPerformance(t0, ideo) {
var t1 = new Date().getTime();
if (ideo.config.debug) {
console.log('Time in deserializeAnnotsForHeatmap: ' + (t1 - t0) + ' ms');
}
}

/**
* Deserializes compressed annotation data into a format suited for heatmaps.
*
Expand All @@ -77,105 +186,28 @@ function drawHeatmaps(annotContainers) {
* @param rawAnnotsContainer {Object} Raw annotations as passed from server
*/
function deserializeAnnotsForHeatmap(rawAnnotsContainer) {

var t0 = new Date().getTime();

var raContainer, chr, ra, i, j, k, m, trackIndex, rawAnnots,
newRaContainers, newRa, newRas, color,
heatmapKey, heatmapKeyIndexes, value,
thresholds, thresholdList, thresholdColor, threshold, prevThreshold,
tvInt, numThresholds,
var newRaContainers, heatmapKey, heatmapKeyIndexes, i,
t0 = new Date().getTime(),
keys = rawAnnotsContainer.keys,
rawAnnotBoxes = rawAnnotsContainer.annots,
ideo = this;

newRaContainers = [];

heatmapKeyIndexes = [];
for (i = 0; i < ideo.config.heatmaps.length; i++) {
heatmapKey = ideo.config.heatmaps[i].key;
heatmapKeyIndexes.push(keys.indexOf(heatmapKey));
}

for (i = 0; i < rawAnnotBoxes.length; i++) {

raContainer = rawAnnotBoxes[i];
chr = raContainer.chr;
rawAnnots = raContainer.annots;
newRas = [];

for (j = 0; j < rawAnnots.length; j++) {

ra = rawAnnots[j];

for (k = 0; k < heatmapKeyIndexes.length; k++) {

newRa = ra.slice(0, 3); // name, start, length

value = ra[heatmapKeyIndexes[k]];
thresholds = ideo.config.heatmaps[k].thresholds;

for (m = 0; m < thresholds.length; m++) {
numThresholds = thresholds.length - 1;
thresholdList = thresholds[m];
threshold = thresholdList[0];

// The threshold value is usually an integer,
// but can also be a "+" character indicating that
// this threshold is anything greater than the previous threshold.
tvInt = parseInt(threshold);
if (isNaN(tvInt) === false) {
threshold = tvInt;
}
if (m !== 0) {
prevThreshold = parseInt(thresholds[m - 1][0]);
}
thresholdColor = thresholdList[1];

if (

// If this is the last threshold, and
// its value is "+" and the value is above the previous threshold...
m === numThresholds && (
threshold === '+' && value > prevThreshold
) ||

// ... or if the value matches the threshold...
value === threshold ||

// ... or if this isn't the first or last threshold, and
// the value is between this threshold and the previous one...
m !== 0 && m !== numThresholds && (
value <= threshold &&
value > prevThreshold
) ||

// ... or if this is the first threshold and the value is
// at or below the threshold
m === 0 && value <= threshold
) {
color = thresholdColor;
}
}

trackIndex = k;
newRa.push(trackIndex, color, value);
newRas.push(newRa);
}
}
newRaContainers.push({chr: chr, annots: newRas});
}
newRaContainers =
getNewRawAnnotContainers(heatmapKeyIndexes, rawAnnotBoxes, ideo);

keys.splice(3, 0, 'trackIndex');
keys.splice(4, 0, 'color');

ideo.rawAnnots.keys = keys;
ideo.rawAnnots.annots = newRaContainers;

var t1 = new Date().getTime();
if (ideogram.config.debug) {
console.log('Time in deserializeAnnotsForHeatmap: ' + (t1 - t0) + ' ms');
}
reportPerformance(t0, ideo);
}

export {drawHeatmaps, deserializeAnnotsForHeatmap}

0 comments on commit 03d2a77

Please sign in to comment.