UNPKG

ucsc-xena-client

Version:

UCSC Xena Client. Functional genomics visualizations.

228 lines (194 loc) 7.45 kB
// 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. 'use strict'; 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;