Skip to content

Commit

Permalink
thread viewAsPairs through to Pileup{Track,Cache}
Browse files Browse the repository at this point in the history
  • Loading branch information
danvk committed Oct 26, 2015
1 parent 4c118dd commit b424763
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 26 deletions.
40 changes: 25 additions & 15 deletions src/main/PileupCache.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,36 @@ export type VisualGroup = {
alignments: VisualAlignment[];
};

// read name would make a good key, but paired reads from different contigs
// shouldn't be grouped visually. Hence we use read name + contig.
function groupKey(read: Alignment): string {
return `${read.name}:${read.ref}`;
}

// This class provides data management for the visualization, grouping paired
// reads and managing the pileup.
class PileupCache {
// maps groupKey to VisualGroup
groups: {[key: string]: VisualGroup};
refToPileup: {[key: string]: Array<Interval[]>};
referenceSource: TwoBitSource;
viewAsPairs: boolean;

constructor(referenceSource: TwoBitSource) {
constructor(referenceSource: TwoBitSource, viewAsPairs: boolean) {
this.groups = {};
this.refToPileup = {};
this.referenceSource = referenceSource;
this.viewAsPairs = viewAsPairs;
}

// read name would make a good key, but paired reads from different contigs
// shouldn't be grouped visually. Hence we use read name + contig.
groupKey(read: Alignment): string {
if (this.viewAsPairs) {
return `${read.name}:${read.ref}`;
} else {
return read.getKey();
}
}

// Load a new read into the visualization cache.
// Calling this multiple times with the same read is a no-op.
addAlignment(read: Alignment) {
var key = groupKey(read),
var key = this.groupKey(read),
range = read.getInterval();

if (!(key in this.groups)) {
Expand Down Expand Up @@ -91,13 +97,17 @@ class PileupCache {
if (group.alignments.length == 1) {
// This is the first read in the group. Infer its span from its mate properties.
// TODO: if the mate Alignment is also available, it would be better to use that.
var intervals = [range];
var mateProps = read.getMateProperties();
if (mateProps && mateProps.ref && mateProps.ref == read.ref) {
mateInterval = new ContigInterval(mateProps.ref, mateProps.pos, mateProps.pos + range.length());
intervals.push(mateInterval);
if (this.viewAsPairs) {
var mateProps = read.getMateProperties();
var intervals = [range];
if (mateProps && mateProps.ref && mateProps.ref == read.ref) {
mateInterval = new ContigInterval(mateProps.ref, mateProps.pos, mateProps.pos + range.length());
intervals.push(mateInterval);
}
group = _.extend(group, spanAndInsert(intervals));
} else {
group.span = range;
}
group = _.extend(group, spanAndInsert(intervals));

if (!(read.ref in this.refToPileup)) {
this.refToPileup[read.ref] = [];
Expand Down Expand Up @@ -160,7 +170,7 @@ function spanAndInsert(intervals: ContigInterval<string>[]) {
if (intervals.length == 1) {
return {insert: null, span: intervals[0]};
} else if (intervals.length !=2) {
throw `Called spanAndInsert with ${intervals.length} \in [1, 2]`;
throw `Called spanAndInsert with ${intervals.length} \notin [1, 2]`;
}

if (!intervals[0].chrOnContig(intervals[1].contig)) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/PileupTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ class PileupTrack extends React.Component {
}

componentDidMount() {
this.cache = new PileupCache(this.props.referenceSource);
this.cache = new PileupCache(this.props.referenceSource, this.props.options.viewAsPairs);
this.props.source.on('newdata', range => {
this.updateReads(range);
this.updateVisualization();
Expand Down Expand Up @@ -363,6 +363,7 @@ PileupTrack.propTypes = {
range: types.GenomeRange.isRequired,
source: React.PropTypes.object.isRequired,
referenceSource: React.PropTypes.object.isRequired,
options: React.PropTypes.object
};
PileupTrack.displayName = 'pileup';

Expand Down
9 changes: 6 additions & 3 deletions src/main/pileup.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,11 @@ function create(elOrId: string|Element, params: PileupParams): Pileup {

type VizObject = ((options: ?Object) => {component: React.Component, options:?Object});

function makeVizObject(component: React.Component): VizObject {
return options => ({component, options});
function makeVizObject(component: React.Component, defaultOptions: ?Object): VizObject {
return options => {
options = _.extend({}, defaultOptions, options);
return {component, options};
};
}

var pileup = {
Expand All @@ -129,7 +132,7 @@ var pileup = {
location: makeVizObject(LocationTrack),
scale: makeVizObject(ScaleTrack),
variants: makeVizObject(VariantTrack),
pileup: makeVizObject(PileupTrack)
pileup: makeVizObject(PileupTrack, {viewAsPairs: false})
}
};

Expand Down
26 changes: 19 additions & 7 deletions src/test/PileupCache-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ describe('PileupCache', function() {
return new ContigInterval(chr, start, end);
}

function makeCache(args) {
var cache = new PileupCache(fakeSource);
function makeCache(args, viewAsPairs: boolean) {
var cache = new PileupCache(fakeSource, viewAsPairs);
_.flatten(args).forEach(read => cache.addAlignment(read));
return cache;
}

it('should group read pairs', function() {
var cache = makeCache(makeReadPair(ci('chr1', 100, 200),
ci('chr1', 300, 400)));
ci('chr1', 300, 400)), true /* viewAsPairs */);

var groups = _.values(cache.groups);
expect(groups).to.have.length(1);
Expand All @@ -104,7 +104,7 @@ describe('PileupCache', function() {
makeReadPair(ci('chr1', 100, 200), ci('chr1', 300, 400)), // A
makeReadPair(ci('chr1', 300, 400), ci('chr1', 500, 600)), // B
makeReadPair(ci('chr1', 700, 800), ci('chr1', 500, 600)) // C
]);
], true /* viewAsPairs */);

var groups = _.values(cache.groups);
expect(groups).to.have.length(3);
Expand All @@ -119,7 +119,7 @@ describe('PileupCache', function() {
var cache = makeCache([
makeReadPair(ci('chr1', 100, 200), ci('chr1', 800, 900)),
makeReadPair(ci('chr1', 300, 400), ci('chr1', 500, 600))
]);
], true /* viewAsPairs */);

var groups = _.values(cache.groups);
expect(groups).to.have.length(2);
Expand All @@ -128,9 +128,21 @@ describe('PileupCache', function() {
expect(cache.pileupHeightForRef('chr1')).to.equal(2);
});

it('should pack unpaired reads more tightly', function() {
// Same as the previous test, but with viewAsPairs = false.
// When the inserts aren't rendered, the reads all fit on a single line.
var cache = makeCache([
makeReadPair(ci('chr1', 100, 200), ci('chr1', 800, 900)),
makeReadPair(ci('chr1', 300, 400), ci('chr1', 500, 600))
], false /* viewAsPairs */);
var groups = _.values(cache.groups);
expect(groups).to.have.length(4);
expect(cache.pileupHeightForRef('chr1')).to.equal(1);
});

it('should separate pairs on differing contigs', function() {
var cache = makeCache(makeReadPair(ci('chr1', 100, 200),
ci('chr2', 150, 250)));
ci('chr2', 150, 250)), true /* viewAsPairs */);

var groups = _.values(cache.groups);
expect(groups).to.have.length(2);
Expand All @@ -151,7 +163,7 @@ describe('PileupCache', function() {
makeReadPair(ci('chr1', 100, 200), ci('chr1', 800, 900)),
makeReadPair(ci('chr1', 300, 400), ci('chr1', 500, 600)),
makeReadPair(ci('chr2', 100, 200), ci('chr2', 300, 400))
]);
], true /* viewAsPairs */);

expect(cache.getGroupsOverlapping(ci('chr1', 50, 150))).to.have.length(1);
expect(cache.getGroupsOverlapping(ci('chr1', 50, 350))).to.have.length(2);
Expand Down

0 comments on commit b424763

Please sign in to comment.