Skip to content

Commit

Permalink
Rendering UTRs
Browse files Browse the repository at this point in the history
  • Loading branch information
danvk committed Mar 18, 2015
1 parent feb0e8e commit 081a797
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 22 deletions.
4 changes: 4 additions & 0 deletions lib/underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ declare module "underscore" {
declare function range(a: number, b: number): Array<number>;
declare function extend<S, T>(o1: S, o2: T): S & T;

declare function zip<S, T>(a1: S[], a2: T[]): Array<[S, T]>;

declare function flatten<S>(a: S[][]): S[];

declare function chain<S>(obj: S): any;
}

45 changes: 36 additions & 9 deletions src/BigBedDataSource.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,49 @@
/* @flow */
'use strict';

var Events = require('backbone').Events,
_ = require('underscore');
_ = require('underscore'),
Q = require('q');


var ContigInterval = require('./ContigInterval'),
Interval = require('./Interval');


// TODO: move this into BigBed.js

type Gene = {
position: ContigInterval;
id: string; // transcript ID, e.g. "ENST00000269305"
strand: string; // '+' or '-'
codingStart: number; // locus of coding start
codingStop: number;
codingRegion: Interval; // locus of coding start
exons: Array<Interval>;
geneId: string; // ensembl gene ID
name: string; // human-readable name, e.g. "TP53"
}

// TODO: move this into BigBed.js and get it to type check.
type BedRow = {
// Half-open interval for the BED row.
contig: string;
start: number;
stop: number;
// Remaining fields in the BED row (typically tab-delimited)
rest: string;
}

declare class BigBed {
getFeaturesInRange: (contig: string, start: number, stop: number) => Q.Promise<Array<BedRow>>;
}


// Flow type for export.
type BigBedSource = {
rangeChanged: (newRange: GenomeRange) => void;
getGenesInRange: (range: ContigInterval) => Gene[];
on: (event: string, handler: Function) => void;
off: (event: string) => void;
}

// The fields are described at http://genome.ucsc.edu/FAQ/FAQformat#format1
function parseBedFeature(f): Gene {
var position = new ContigInterval(f.contig, f.start, f.stop),
Expand All @@ -28,23 +52,22 @@ function parseBedFeature(f): Gene {
exonStarts = x[8].split(',').map(Number),
exons = _.zip(exonStarts, exonLengths)
.map(function([start, length]) {
return new Interval(start, start + length);
return new Interval(f.start + start, f.start + start + length);
});

return {
position,
id: x[0], // e.g. ENST00000359597
strand: x[2], // either + or -
codingStart: Number(x[3]),
codingStop: Number(x[4]),
codingRegion: new Interval(Number(x[3]), Number(x[4])),
geneId: x[9],
name: x[10],
exons
};
}


function createBigBedDataSource(remoteSource: BigBed) {
function createBigBedDataSource(remoteSource: BigBed): BigBedSource {
// Collection of genes that have already been loaded.
var genes: Array<Gene> = [];
window.genes = genes;
Expand Down Expand Up @@ -75,7 +98,11 @@ function createBigBedDataSource(remoteSource: BigBed) {
.then(() => o.trigger('newdata', newRange))
.done();
},
getGenesInRange
getGenesInRange,

// These are here to make Flow happy.
on: () => {},
off: () => {}
};
_.extend(o, Events); // Make this an event emitter

Expand Down
22 changes: 9 additions & 13 deletions src/GeneTrack.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
var React = require('react/addons'),
_ = require('underscore'),
d3 = require('d3'),
types = require('./types');
types = require('./types'),
bedtools = require('./bedtools');

var GeneTrack = React.createClass({
propTypes: {
Expand Down Expand Up @@ -111,9 +112,8 @@ var NonEmptyGeneTrack = React.createClass({
geneLineG.append('rect').attr('class', 'strand');

geneLineG.selectAll('rect.exon')
.data(g => g.exons.map(function(x) {
return [x.start, x.stop, g.position.start()]
}))
.data(g => bedtools.splitCodingExons(g.exons, g.codingRegion)
.map(x => [x, g.position.start()]))
.enter()
.append('rect')
.attr('class', 'exon')
Expand Down Expand Up @@ -158,15 +158,11 @@ var NonEmptyGeneTrack = React.createClass({

track.selectAll('rect.exon')
.attr({
'x': function([start, stop, gStart]) {
return scale(start) - scale(0);
},
'y': -3,
'height': 6,
'width': function([start, stop]) {
return scale(stop) - scale(start);
}
})
'x': ([exon, gStart]) => scale(exon.start - gStart) - scale(0),
'y': ([exon]) => -3 * (exon.isCoding ? 2 : 1),
'height': ([exon]) => 6 * (exon.isCoding ? 2 : 1),
'width': ([exon]) => scale(exon.stop) - scale(exon.start)
});

// Exit
genes.exit().remove();
Expand Down
4 changes: 4 additions & 0 deletions src/Interval.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class Interval {
return value >= this.start && value <= this.stop;
}

containsInterval(other: Interval): boolean {
return this.contains(other.start) && this.contains(other.stop);
}

clone(): Interval {
return new Interval(this.start, this.stop);
}
Expand Down
49 changes: 49 additions & 0 deletions src/bedtools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* @flow */

var _ = require('underscore');
var Interval = require('./Interval');

class CodingInterval extends Interval {
isCoding: boolean;
constructor(start, stop, isCoding: boolean) {
super(start, stop);
this.isCoding = isCoding;
}
}

/**
* Split exons which cross the coding/non-coding boundary into purely coding &
* non-coding parts.
*/
function splitCodingExons(exons: Interval[],
codingRegion: Interval): CodingInterval[] {
return _.flatten(exons.map(exon => {
// Special case: the coding region is entirely contained by this exon.
if (exon.containsInterval(codingRegion)) {
// split into three parts.
return [
new CodingInterval(exon.start, codingRegion.start - 1, false),
new CodingInterval(codingRegion.start, codingRegion.stop, true),
new CodingInterval(codingRegion.stop + 1, exon.stop, false)
].filter(interval => interval.start <= interval.stop);
}

var startIsCoding = codingRegion.contains(exon.start),
stopIsCoding = codingRegion.contains(exon.stop);
if (startIsCoding == stopIsCoding) {
return [new CodingInterval(exon.start, exon.stop, startIsCoding)];
} else if (startIsCoding) {
return [
new CodingInterval(exon.start, codingRegion.stop, true),
new CodingInterval(codingRegion.stop + 1, exon.stop, false)
];
} else {
return [
new CodingInterval(exon.start, codingRegion.start - 1, false),
new CodingInterval(codingRegion.start, exon.stop, true)
];
}
}));
}

module.exports = {splitCodingExons, CodingInterval};
57 changes: 57 additions & 0 deletions test/bedtools-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* @flow */
var chai = require('chai');
var expect = chai.expect;

var bedtools = require('../src/bedtools'),
Interval = require('../src/Interval');

describe('bedtools', function() {
describe('splitCodingExons', function() {
var splitCodingExons = bedtools.splitCodingExons;
var CodingInterval = bedtools.CodingInterval;

it('should split one exon', function() {
var exon = new Interval(10, 20);

expect(splitCodingExons([exon], new Interval(13, 17))).to.deep.equal([
new CodingInterval(10, 12, false),
new CodingInterval(13, 17, true),
new CodingInterval(18, 20, false)
]);

expect(splitCodingExons([exon], new Interval(5, 15))).to.deep.equal([
new CodingInterval(10, 15, true),
new CodingInterval(16, 20, false)
]);

expect(splitCodingExons([exon], new Interval(15, 25))).to.deep.equal([
new CodingInterval(10, 14, false),
new CodingInterval(15, 20, true)
]);

expect(splitCodingExons([exon], new Interval(10, 15))).to.deep.equal([
new CodingInterval(10, 15, true),
new CodingInterval(16, 20, false)
]);

expect(splitCodingExons([exon], new Interval(15, 20))).to.deep.equal([
new CodingInterval(10, 14, false),
new CodingInterval(15, 20, true)
]);
});

it('should handle purely coding or non-coding exons', function() {
var exon = new Interval(10, 20);

expect(splitCodingExons([exon], new Interval(0, 9))).to.deep.equal([
new CodingInterval(10, 20, false)
]);
expect(splitCodingExons([exon], new Interval(21, 25))).to.deep.equal([
new CodingInterval(10, 20, false)
]);
expect(splitCodingExons([exon], new Interval(10, 20))).to.deep.equal([
new CodingInterval(10, 20, true)
]);
});
});
});

0 comments on commit 081a797

Please sign in to comment.