Skip to content

Instantly share code, notes, and snippets.

@armish
Created July 16, 2015 15:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save armish/fc1dc24349f1270753a6 to your computer and use it in GitHub Desktop.
Save armish/fc1dc24349f1270753a6 to your computer and use it in GitHub Desktop.
Pileup Visualization Boilerplate
/**
* Visualization of genes, including exons and coding regions.
* @flow
*/
'use strict';
var React = require('./react-shim'),
_ = require('underscore'),
d3 = require('d3'),
shallowEquals = require('shallow-equals'),
types = require('./react-types'),
bedtools = require('./bedtools'),
Interval = require('./Interval'),
d3utils = require('./d3utils'),
ContigInterval = require('./ContigInterval');
var GeneTrack = React.createClass({
displayName: 'genes',
propTypes: {
range: types.GenomeRange,
source: React.PropTypes.object.isRequired,
onRangeChange: React.PropTypes.func.isRequired,
},
render: function(): any {
var range = this.props.range;
if (!range) {
return <EmptyTrack />;
}
return <NonEmptyGeneTrack {...this.props} />;
}
});
var NonEmptyGeneTrack = React.createClass({
propTypes: {
range: types.GenomeRange.isRequired,
source: React.PropTypes.object.isRequired,
onRangeChange: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
width: 0,
height: 0,
genes: []
};
},
render: function() {
return <div></div>;
},
updateSize: function() {
var parentDiv = this.getDOMNode().parentNode;
this.setState({
width: parentDiv.offsetWidth,
height: parentDiv.offsetHeight
});
},
componentDidMount: function() {
var div = this.getDOMNode(),
svg = d3.select(div)
.append('svg');
window.addEventListener('resize', () => this.updateSize());
this.updateSize();
// Visualize new reference data as it comes in from the network.
this.props.source.on('newdata', () => {
var range = this.props.range,
ci = new ContigInterval(range.contig, range.start, range.stop);
this.setState({
genes: this.props.source.getGenesInRange(ci)
});
});
this.updateVisualization();
},
getScale: function() {
return d3utils.getTrackScale(this.props.range, this.state.width);
},
componentDidUpdate: function(prevProps: any, prevState: any) {
if (!shallowEquals(prevProps, this.props) ||
!shallowEquals(prevState, this.state)) {
this.updateVisualization();
}
},
updateVisualization: function() {
var div = this.getDOMNode(),
width = this.state.width,
height = this.state.height,
svg = d3.select(div).select('svg');
// Hold off until height & width are known.
if (width === 0) return;
var scale = this.getScale(),
// We can't clamp scale directly because of offsetPx.
clampedScale = d3.scale.linear()
.domain([scale.invert(0), scale.invert(width)])
.range([0, width])
.clamp(true);
svg.attr('width', width)
.attr('height', height);
var genes = svg.selectAll('g.gene')
.data(this.state.genes, gene => gene.id);
// Enter
var geneGs = genes.enter()
.append('g')
.attr('class', 'gene');
geneGs.append('text');
var geneLineG = geneGs.append('g').attr('class', 'track');
geneLineG.selectAll('rect.exon')
.data(g => bedtools.splitCodingExons(g.exons, g.codingRegion))
.enter()
.append('rect')
.attr('class', 'exon');
geneLineG.append('line');
geneLineG.append('rect').attr('class', 'strand');
// The gene name goes in the center of the gene, modulo boundary effects.
var textCenterX = g => {
var p = g.position;
return 0.5 * (clampedScale(p.start()) + clampedScale(p.stop()));
};
var scaledWidth = g => scale(g.position.stop()) - scale(g.position.start());
var geneLineY = height / 4;
// Enter & update
var track = genes.selectAll('g.track')
.attr('transform',
g => `translate(0 ${geneLineY})`);
genes.selectAll('text')
.attr({
'x': textCenterX,
'y': geneLineY + 15
})
.text(g => g.name || g.id)
.call(removeOverlapping);
track.selectAll('line').attr({
'x1': g => scale(g.position.start()),
'x2': g => scale(g.position.stop()),
'y1': 0,
'y2': 0
});
track.selectAll('rect.strand')
.attr({
'y': -4,
'height': 9,
'x': g => scale(g.position.start()),
'width': scaledWidth,
'fill': g => `url(#${g.strand == '+' ? '' : 'anti'}sense)`,
'stroke': 'none'
});
track.selectAll('rect.exon')
.attr({
'x': exon => scale(exon.start),
'width': exon => scale(exon.stop) - scale(exon.start),
'y': exon => -3 * (exon.isCoding ? 2 : 1),
'height': exon => 6 * (exon.isCoding ? 2 : 1)
});
// Exit
genes.exit().remove();
}
});
var EmptyTrack = React.createClass({
render: function() {
return <div className="genes empty">Zoom in to see genes</div>;
}
});
module.exports = GeneTrack;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment