UNPKG

ideogram

Version:

Chromosome visualization for the web

238 lines (197 loc) 6.53 kB
import {d3} from '../lib'; import {writeHistogramAnnots} from './histogram'; import {writeLegend} from './legend'; function parseFriendlyAnnots(friendlyAnnots, rawAnnots) { var i, j, annot, rawAnnot; for (i = 0; i < friendlyAnnots.length; i++) { annot = friendlyAnnots[i]; for (j = 0; j < rawAnnots.length; j++) { if (annot.chr === rawAnnots[j].chr) { rawAnnot = [ annot.name, annot.start, annot.stop - annot.start ]; if ('color' in annot) rawAnnot.push(annot.color); if ('shape' in annot) rawAnnot.push(annot.shape); rawAnnots[j].annots.push(rawAnnot); break; } } } return rawAnnots; } function parseFriendlyKeys(friendlyAnnots) { var keys = ['name', 'start', 'length']; if ('color' in friendlyAnnots[0]) { keys.push('color'); } if ('shape' in friendlyAnnots[0]) { keys.push('shape'); } return keys; } /** * Draws annotations defined by user */ function drawAnnots(friendlyAnnots, layout, keep=false, isOtherLayout=false) { var keys, chr, rawAnnots = [], ideo = this, chrs = ideo.chromosomes[ideo.config.taxid]; // TODO: multiorganism if ( 'annots' in friendlyAnnots[0] || // When filtering 'values' in friendlyAnnots[0] // When drawing cached expression matrices ) { return ideo.drawProcessedAnnots(friendlyAnnots, layout); } for (chr in chrs) { rawAnnots.push({chr: chr, annots: []}); } rawAnnots = parseFriendlyAnnots(friendlyAnnots, rawAnnots); keys = parseFriendlyKeys(friendlyAnnots); ideo.rawAnnots = {keys: keys, annots: rawAnnots}; const processedAnnots = ideo.processAnnotData(ideo.rawAnnots); if (!isOtherLayout) { ideo.annots = processedAnnots; } else { ideo.annotsOther = processedAnnots; } ideo.drawProcessedAnnots(processedAnnots, layout, keep); } function getShapes(annotHeight) { var triangle, circle, rectangle, r; triangle = 'm0,0 l -' + annotHeight + ' ' + (2 * annotHeight) + ' l ' + (2 * annotHeight) + ' 0 z'; // From http://stackoverflow.com/a/10477334, with a minor change ("m -r, r") // Circles are supported natively via <circle>, but having it as a path // simplifies handling triangles, circles and other shapes in the same // D3 call r = annotHeight; circle = 'm -' + r + ', ' + r + 'a ' + r + ',' + r + ' 0 1,0 ' + (r * 2) + ',0' + 'a ' + r + ',' + r + ' 0 1,0 -' + (r * 2) + ',0'; rectangle = 'm0,0 l 0 ' + (2 * annotHeight) + 'l ' + annotHeight + ' 0' + 'l 0 -' + (2 * annotHeight) + 'z'; return {triangle: triangle, circle: circle, rectangle: rectangle}; } function getChrAnnotNodes(filledAnnots, ideo) { return d3.selectAll(ideo.selector + ' .chromosome') .data(filledAnnots) .selectAll('path.annot') .data(function(d) { return d.annots; }) .enter(); } function determineShape(d, shapes) { if (!d.shape || d.shape === 'triangle') { return shapes.triangle; } else if (d.shape === 'circle') { return shapes.circle; } else if (d.shape === 'rectangle') { return shapes.rectangle; } else { return d.shape; } } function writeTrackAnnots(chrAnnot, ideo) { var shapes, annotHeight = ideo.config.annotationHeight; shapes = getShapes(annotHeight); chrAnnot.append('g') .attr('id', function(d) {return d.domId;}) .attr('class', 'annot') .attr('transform', function(d) { var y = ideo.config.chrWidth + (d.trackIndex * annotHeight * 2); return 'translate(' + d.px + ',' + y + ')'; }) .append('path') .attr('d', function(d) {return determineShape(d, shapes);}) .attr('fill', function(d) {return d.color;}) .on('mouseover', function(event, d) {ideo.showAnnotTooltip(d, this);}) .on('mouseout', function() {ideo.startHideAnnotTooltipTimeout();}) .on('click', function(event, d) {ideo.onClickAnnot(d);}); } /** * Overlaid annotations appear directly on chromosomes */ function writeOverlayAnnots(chrAnnot, ideo) { chrAnnot.append('polygon') .attr('id', function(d) {return d.id;}) .attr('class', 'annot') .attr('points', function(d) { var x1, x2, chrWidth = ideo.config.chrWidth; if (d.stopPx - d.startPx > 1) { x1 = d.startPx; x2 = d.stopPx; } else { x1 = d.px - 0.5; x2 = d.px + 0.5; } return ( x1 + ',' + chrWidth + ' ' + x2 + ',' + chrWidth + ' ' + x2 + ',0 ' + x1 + ',0' ); }) .attr('fill', function(d) {return d.color;}) .on('mouseover', function(event, d) {ideo.showAnnotTooltip(d, this);}) .on('mouseout', function() {ideo.startHideAnnotTooltipTimeout();}); } function warnIfTooManyAnnots(layout, annots) { var i, numAnnots; if (!/heatmap/.test(layout) && layout !== 'histogram') { numAnnots = 0; for (i = 0; i < annots.length; i++) { numAnnots += annots[i].annots.length; } if (numAnnots > 2000) { console.warn( 'Rendering more than 2000 annotations in Ideogram?\n' + 'Try setting "annotationsLayout" to "heatmap" or "histogram" in your ' + 'Ideogram configuration object for better layout and performance.' ); } } } function drawAnnotsByLayoutType(layout, annots, ideo) { var filledAnnots, chrAnnot; warnIfTooManyAnnots(layout, annots); if (layout === 'histogram') annots = ideo.getHistogramBars(annots); filledAnnots = ideo.fillAnnots(annots); chrAnnot = getChrAnnotNodes(filledAnnots, ideo); if (layout === 'tracks') { writeTrackAnnots(chrAnnot, ideo); } else if (layout === 'overlay') { writeOverlayAnnots(chrAnnot, ideo); } else if (layout === 'histogram') { writeHistogramAnnots(chrAnnot, ideo); } } /** * Draws genome annotations on chromosomes. * Annotations can be rendered as either overlaid directly * on a chromosome, or along one or more "tracks" * running parallel to each chromosome. */ function drawProcessedAnnots(annots, layout, keep=false) { var ideo = this; if (!keep) { d3.selectAll(ideo.selector + ' .annot').remove(); } if (layout === undefined) layout = 'tracks'; if (ideo.config.annotationsLayout) layout = ideo.config.annotationsLayout; if ('legend' in ideo.config) writeLegend(ideo); if (/heatmap/.test(layout)) { ideo.drawHeatmaps(annots); return; } drawAnnotsByLayoutType(layout, annots, ideo); if (ideo.onDrawAnnotsCallback) ideo.onDrawAnnotsCallback(); } export {drawAnnots, drawProcessedAnnots, getShapes};