ucsc-xena-client
Version:
UCSC Xena Client. Functional genomics visualizations.
228 lines (194 loc) • 7.45 kB
JavaScript
// It is unfortunate that this duplicates the code in KmPlot and Axis.js, however it's
// hard to repurpose the react views to drive a different renderer. The React data
// structures are opaque to the caller. We could reverse-engineer them, but then would
// be subject to breakage when React updates. We could write a custom declarative
// representation that maps to react or pdf, but this quickly becomes complex. This
// port of KmPlot to pdf is a compromise.
// Another strategy worth exploring is walking the DOM to generate pdf calls. That
// might also work for highcharts.
;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
var _ = require('./underscore_ext');
var _require = require('./scale'),
linear = _require.linear,
linearTicks = _require.linearTicks;
var margin = { top: 20, right: 30, bottom: 30, left: 50 };
var bounds = function bounds(x) {
return [_.min(x), _.max(x)];
};
// XXX duplicated in km.css
var style = {
outline: {
width: 2
},
line: {
width: 1
},
axisLabel: {
height: 12
}
};
// XXX duplicated in Axis.js.
var tickPadding = 3;
function horzLayout(domain, range, scale, tickfn, tickHeight) {
return tickfn.apply(null, domain).map(function (x) {
return [[scale(x), 0], [0, tickHeight], [0, tickHeight + tickPadding], x.toLocaleString(), 'middle', -0.3 * style.axisLabel.height];
});
}
function vertLayout(domain, range, scale, tickfn, tickHeight) {
return tickfn.apply(null, domain).map(function (y) {
return [[0, scale(y)], [-tickHeight, 0], [-(tickHeight + tickPadding), 0], y.toLocaleString(), 'end', 0.32 * style.axisLabel.height];
});
}
var layout = {
bottom: horzLayout,
left: vertLayout
};
var textAnchored = {
middle: function middle(vg, x, y, c, h, txt) {
var width = vg.textWidth(h, txt);
vg.text(x - width / 2, y + h, c, h, txt);
},
end: function end(vg, x, y, c, h, txt) {
var width = vg.textWidth(h, txt);
vg.text(x - width, y, c, h, txt);
}
};
var domainPath = {
bottom: function bottom(vg, _ref, height) {
var _ref2 = _slicedToArray(_ref, 2),
start = _ref2[0],
end = _ref2[1];
return vg.drawPoly([[start, height, start, 0], [end, 0], [end, height]], { strokeStyle: 'black', lineWidth: 1 });
},
left: function left(vg, _ref3, height) {
var _ref4 = _slicedToArray(_ref3, 2),
start = _ref4[0],
end = _ref4[1];
return vg.drawPoly([[-height, start, 0, start], [0, end], [-height, end]], { strokeStyle: 'black', lineWidth: 1 });
}
};
function axis(vg, _ref5) {
var domain = _ref5.domain,
range = _ref5.range,
scale = _ref5.scale,
tickfn = _ref5.tickfn,
orientation = _ref5.orientation,
_ref5$tickHeight = _ref5.tickHeight,
tickHeight = _ref5$tickHeight === undefined ? 6 : _ref5$tickHeight;
var ticks = layout[orientation](domain, range, scale, tickfn, tickHeight);
domainPath[orientation](vg, range, tickHeight);
ticks.forEach(function (_ref6) {
var _ref7 = _slicedToArray(_ref6, 6),
_ref7$ = _slicedToArray(_ref7[0], 2),
x = _ref7$[0],
y = _ref7$[1],
_ref7$2 = _slicedToArray(_ref7[1], 2),
dx = _ref7$2[0],
dy = _ref7$2[1],
_ref7$3 = _slicedToArray(_ref7[2], 2),
lx = _ref7$3[0],
ly = _ref7$3[1],
label = _ref7[3],
anchor = _ref7[4],
off = _ref7[5];
//eslint-disable-line no-unused-vars
vg.translate(x, y, function () {
vg.drawPoly([[0, 0, dx, dy]], { strokeStyle: 'black', lineWidth: 1 });
textAnchored[anchor](vg, lx, ly + off, 'black', style.axisLabel.height, label);
});
});
}
function censorLines(vg, xScale, yScale, censors, style, color) {
censors.forEach(function (_ref8) {
var t = _ref8.t,
s = _ref8.s;
vg.translate(xScale(t), yScale(s), function () {
vg.drawPoly([[0, -5, 0, 5]], { strokeStyle: color,
lineWidth: style.width });
});
});
}
// Expand coords as step function
function stepPath(coords) {
var acc = [],
y0 = 0;
coords.forEach(function (_ref9) {
var _ref10 = _slicedToArray(_ref9, 2),
x = _ref10[0],
y = _ref10[1];
acc.push([x, y0]);
acc.push([x, y]);
y0 = y;
});
return acc;
}
function line(vg, xScale, yScale, values, style, color) {
var coords = _.map(values, function (_ref11) {
var t = _ref11.t,
s = _ref11.s;
return [xScale(t), yScale(s)];
}),
path = stepPath(coords);
vg.drawPoly([[0, 0, 0, 0]].concat(_toConsumableArray(path)), { strokeStyle: color,
lineWidth: style.width });
}
function lineGroup(vg, _ref12) {
var g = _ref12.g,
xScale = _ref12.xScale,
yScale = _ref12.yScale;
var _g = _slicedToArray(g, 3),
color = _g[0],
curve = _g[2],
censors = curve.filter(function (pt) {
return !pt.e;
});
line(vg, xScale, yScale, curve, style.outline, 'black');
line(vg, xScale, yScale, curve, style.line, color);
censorLines(vg, xScale, yScale, censors, style.outline, 'black');
censorLines(vg, xScale, yScale, censors, style.line, color);
}
// Fix height to be square, as this is the convention.
var size = { height: 500, width: 500 };
function download(_ref13) {
var colors = _ref13.colors,
labels = _ref13.labels,
curves = _ref13.curves;
require.ensure(['pdfkit', 'blob-stream', './vgpdf'], function () {
var PDFDocument = require('pdfkit');
var blobStream = require('blob-stream');
var vgpdf = require('./vgpdf');
var height = size.height - margin.top - margin.bottom,
width = size.width - margin.left - margin.right,
xdomain = bounds(_.pluck(_.flatten(curves), 't')),
xrange = [0, width],
ydomain = [0, 1],
yrange = [height, 0],
xScale = linear(xdomain, xrange),
yScale = linear(ydomain, yrange),
doc = new PDFDocument({ compress: false, size: [size.width, size.height] }),
stream = doc.pipe(blobStream()),
vg = vgpdf(doc);
vg.translate(margin.left, margin.top, function () {
axis(vg, { domain: ydomain, range: yrange, scale: yScale, tickfn: linearTicks, orientation: 'left' });
vg.translate(0, height, function () {
axis(vg, { domain: xdomain, range: xrange, scale: xScale, tickfn: linearTicks, orientation: 'bottom' });
});
_.each(_.zip(colors, labels, curves), function (g) {
lineGroup(vg, { g: g, xScale: xScale, yScale: yScale });
});
});
doc.end();
stream.on('finish', function () {
var url = stream.toBlobURL('application/pdf');
var a = document.createElement('a');
var filename = 'xenaDownload.pdf';
Object.assign(a, { id: filename, download: filename, href: url });
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
});
};
module.exports = download;