ucsc-xena-client
Version:
UCSC Xena Client. Functional genomics visualizations.
105 lines (88 loc) • 3.31 kB
JavaScript
var _ = require('./underscore_ext');
var colorHelper = require('./color_helper');
var labelMargin = 1; // left & right margin
var labelFont = 12;
// Pick a stripe sample height that is at least one line of text, and is
// a roundish number of samples (10, 50, 100, 500, 1000, ...).
//
// We do this by computing the number of samples in a text line, then
// rounding that up to the next half-order.
//
// XXX 1.2 is lifted from vgmixed, which uses this to set line height for
// a given font. The 0.1 fudge is due to floating point noise, I think. Otherwise
// we can end up with a height that is smaller than one line, and the labels
// will not be drawn due to clipping in vgmixed.
function stripeHeight(start, end, height) {
var in10 = (end - start) * (1.2 * labelFont + 0.1) / height,
order = Math.pow(10, Math.floor(Math.log10(in10))),
fsd = in10 / order,
// 1st significant digit, 1..9
rounded = fsd > 5 ? 10 : fsd > 1 ? 5 : 1;
return Math.max(rounded * order, 1);
}
function draw(vg, opts) {
var height = opts.height,
width = opts.width,
index = opts.index,
count = opts.count,
data = opts.data,
codes = opts.codes,
minTxtWidth = vg.textWidth(labelFont, 'WWWW'),
first = Math.floor(index),
last = Math.ceil(index + count),
samplesInStripe = stripeHeight(first, last, height),
sh = height / (last - first) * samplesInStripe;
vg.smoothing(false); // For some reason this works better if we do it every time.
_.range(0, height, sh).forEach(function (y, i) {
return vg.box(0, y, width, sh, i % 2 === 0 ? '#CCCCDD' : '#FFFFFF');
});
var labelColors = ['#CCCCDD', '#FFFFFF'].map(colorHelper.contrastColor);
// Add labels
var rowData = data[0].slice(first, last);
if (width - 2 * labelMargin >= minTxtWidth) {
if (samplesInStripe === 1) {
var h = height / count;
vg.clip(labelMargin, 0, width - labelMargin, height, function () {
return rowData.forEach(function (v, i) {
return vg.textCenteredPushRight(labelMargin, h * i - 1, width - labelMargin, h, labelColors[i % 2], labelFont, codes[v]);
});
});
} else {
var mid = Math.floor(height / sh / 2),
label = samplesInStripe + ' samples',
labelWidth = vg.textWidth(labelFont, label);
vg.box((width - labelWidth) / 2 - 2, mid * sh, 1, sh, 'black');
vg.box((width - labelWidth) / 2 - 6, mid * sh, 4, 1, 'black');
vg.box((width - labelWidth) / 2 - 6, (mid + 1) * sh - 1, 4, 1, 'black');
vg.clip(labelMargin, 0, width - labelMargin, height, function () {
return vg.textCenteredPushRight(labelMargin, mid * sh, width - labelMargin, sh, labelColors[mid % 2], labelFont, samplesInStripe + ' samples');
});
}
}
}
var drawSamples = function drawSamples(vg, props) {
var heatmapData = props.heatmapData,
codes = props.codes,
width = props.width,
zoom = props.zoom,
count = zoom.count,
height = zoom.height,
index = zoom.index;
if (_.isEmpty(heatmapData)) {
// no features to draw
vg.box(0, 0, width, height, "gray");
return;
}
vg.labels(function () {
draw(vg, {
height: height,
width: width,
index: index,
count: count,
data: heatmapData,
codes: codes
});
});
};
module.exports = { drawSamples: drawSamples, stripeHeight: stripeHeight };
;