Skip to content

Commit

Permalink
Merge pull request #478 from hammerlab/bedFeatures
Browse files Browse the repository at this point in the history
fixed BigBedDataSource to work with FeatureTrack
  • Loading branch information
akmorrow13 committed Jun 28, 2018
2 parents 2c36d99 + c0a855e commit 3f07ddd
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/main/Alignment.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type CigarOp = {
}


export type Strand = '-' | '+';
export type Strand = '-' | '+' | '.';

export type MateProperties = {
ref: ?string;
Expand Down
25 changes: 10 additions & 15 deletions src/main/data/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,29 @@ import ContigInterval from '../ContigInterval';
class Feature {
id: string;
featureType: string;
contig: string;
start: number;
stop: number;
position: ContigInterval<string>;
score: number;

constructor(feature: Object) {
this.id = feature.id;
this.featureType = feature.featureType;
this.contig = feature.contig;
this.start = parseInt(feature.start);
this.stop = parseInt(feature.stop);
this.score = feature.score;
this.id = feature.id;
this.featureType = feature.featureType;
this.position = feature.position;
this.score = feature.score;
}

static fromGA4GH(ga4ghFeature: Object): Feature {
return new Feature(
{
var position = new ContigInterval(ga4ghFeature.referenceName, parseInt(ga4ghFeature.start), parseInt(ga4ghFeature.end));
return new Feature(
{
id: ga4ghFeature.id,
featureType: ga4ghFeature.featureType,
contig: ga4ghFeature.referenceName,
start: parseInt(ga4ghFeature.start),
stop: parseInt(ga4ghFeature.end),
position: position,
score: 1000
});
}

intersects(range: ContigInterval<string>): boolean {
return range.intersects(new ContigInterval(this.contig, this.start, this.stop));
return range.intersects(this.position);
}
}

Expand Down
44 changes: 30 additions & 14 deletions src/main/sources/BigBedDataSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Feature from '../data/feature';
export type Gene = {
position: ContigInterval<string>;
id: string; // transcript ID, e.g. "ENST00000269305"
score: number;
strand: Strand;
codingRegion: Interval; // locus of coding start
exons: Array<Interval>;
Expand All @@ -44,29 +45,44 @@ export type BigBedSource = {
}

// The fields are described at http://genome.ucsc.edu/FAQ/FAQformat#format1
// Fields 4-12 are optional
function parseBedFeature(f): Gene {
var position = new ContigInterval(f.contig, f.start, f.stop),
x = f.rest.split('\t'),
// exons arrays sometimes have trailing commas
exonLengths = x[7].replace(/,*$/, '').split(',').map(Number),
exonStarts = x[8].replace(/,*$/, '').split(',').map(Number),
exons = _.zip(exonStarts, exonLengths)
.map(function([start, length]) {
return new Interval(f.start + start, f.start + start + length);
});
x = f.rest.split('\t');

// if no id, generate randomly for unique storage
var id = x[0] ? x[0] : position.toString(); // e.g. ENST00000359597
var score = x[1] ? x[1] : 1000; // number from 0-1000
var strand = x[2] ? x[2] : '.'; // either +, - or .
var codingRegion = (x[3] && x[4]) ? new Interval(Number(x[3]), Number(x[4])) :new Interval(f.start, f.stop);
var geneId = x[9] ? x[9] : id;
var name = x[10] ? x[10] : "";

// parse exons
var exons = [];
if (x[7] && x[8]) {
// exons arrays sometimes have trailing commas
var exonLengths = x[7].replace(/,*$/, '').split(',').map(Number),
exonStarts = x[8].replace(/,*$/, '').split(',').map(Number);

exons = _.zip(exonStarts, exonLengths)
.map(function([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 -
codingRegion: new Interval(Number(x[3]), Number(x[4])),
geneId: x[9],
name: x[10],
id: id,
score: score,
strand: strand,
codingRegion: codingRegion,
geneId: geneId,
name: name,
exons
};
}


function createFromBigBedFile(remoteSource: BigBed): BigBedSource {
// Collection of genes that have already been loaded.
var genes: {[key:string]: Gene} = {};
Expand Down
10 changes: 5 additions & 5 deletions src/main/viz/FeatureTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function renderFeatures(ctx: DataCanvasRenderingContext2D,
ctx.textAlign = 'center';

features.forEach(feature => {
var position = new ContigInterval(feature.contig, feature.start, feature.stop);
var position = new ContigInterval(feature.position.contig, feature.position.start(), feature.position.stop());
if (!position.intersects(range)) return;
ctx.pushObject(feature);
ctx.lineWidth = 1;
Expand All @@ -76,8 +76,8 @@ function renderFeatures(ctx: DataCanvasRenderingContext2D,
var alphaScore = Math.max(feature.score / 1000.0, 0.2);
ctx.fillStyle = 'rgba(0, 0, 0, ' + alphaScore + ')';

var x = Math.round(scale(feature.start));
var width = Math.ceil(scale(feature.stop) - scale(feature.start));
var x = Math.round(scale(feature.position.start()));
var width = Math.ceil(scale(feature.position.stop()) - scale(feature.position.start()));
ctx.fillRect(x - 0.5, 0, width, style.VARIANT_HEIGHT);
ctx.popObject();
});
Expand Down Expand Up @@ -195,14 +195,14 @@ class FeatureTrack extends React.Component {
// If click-tracking gets slow, this range could be narrowed to one
// closer to the click coordinate, rather than the whole visible range.
vFeatures = this.props.source.getFeaturesInRange(range);
var feature = _.find(vFeatures, f => utils.tupleRangeOverlaps([[f.start], [f.stop]], [[clickStart], [clickEnd]]));
var feature = _.find(vFeatures, f => utils.tupleRangeOverlaps([[f.position.start()], [f.position.stop()]], [[clickStart], [clickEnd]]));
var alert = window.alert || console.log;
if (feature) {
// Construct a JSON object to show the user.
var messageObject = _.extend(
{
'id': feature.id,
'range': `${feature.contig}:${feature.start}-${feature.stop}`,
'range': `${feature.position.contig}:${feature.position.start()}-${feature.position.stop()}`,
'score': feature.score
});
alert(JSON.stringify(messageObject, null, ' '));
Expand Down
6 changes: 3 additions & 3 deletions src/test/data/feature-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ describe('Feature', function() {
var features = _.values(parsedJson.features).map(feature => Feature.fromGA4GH(feature));

expect(features).to.have.length(9);
expect(features[0].contig).to.equal("chr1");
expect(features[0].start).to.equal(89295);
expect(features[0].stop).to.equal(120932);
expect(features[0].position.contig).to.equal("chr1");
expect(features[0].position.start()).to.equal(89295);
expect(features[0].position.stop()).to.equal(120932);
expect(features[0].id).to.equal("WyIxa2dlbm9tZXMiLCJnZW5jb2RlX3YyNGxpZnQzNyIsIjE0MDUwOTE3MjM1NDE5MiJd");
expect(features[0].score).to.equal(1000);
done();
Expand Down
47 changes: 43 additions & 4 deletions src/test/viz/FeatureTrack-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ describe('FeatureTrack', function() {

function ready() {
return testDiv.querySelector('canvas') &&
drawnObjects(testDiv, '.features').length > 2;
drawnObjects(testDiv, '.features').length > 0;
}

it('should render features', function() {
it('should render features with json', function() {

var p = pileup.create(testDiv, {
range: range,
Expand All @@ -67,14 +67,53 @@ describe('FeatureTrack', function() {
// there can be duplicates in the case where features are
// overlapping more than one section of the canvas
features = _.uniq(features, false, function(x) {
return x.start;
return x.position.start();
});

expect(features).to.have.length(4);
expect(features.map(f => f.start)).to.deep.equal(
expect(features.map(f => f.position.start())).to.deep.equal(
[89295, 92230, 110953, 120725]);
p.destroy();
});
});

it('should render features with bigBed file', function() {

var p = pileup.create(testDiv, {
range: {contig: 'chr17', start: 10000, stop: 10500},
tracks: [
{
viz: pileup.viz.genome(),
data: pileup.formats.twoBit({
url: '/test-data/test.2bit'
}),
isReference: true
},
{
viz: pileup.viz.features(),
data: pileup.formats.bigBed({
url: '/test-data/chr17.10000-100000.bb'
}),
name: 'Features'
}
]
});


return waitFor(ready, 2000)
.then(() => {
var features = drawnObjects(testDiv, '.features');
// there can be duplicates in the case where features are
// overlapping more than one section of the canvas
features = _.uniq(features, false, function(x) {
return x.position.start();
});

expect(features).to.have.length(3);
expect(features.map(f => f.position.start())).to.deep.equal(
[10000, 10200, 10400]);
p.destroy();
});
});

});
Binary file added test-data/chr17.10000-100000.bb
Binary file not shown.

0 comments on commit 3f07ddd

Please sign in to comment.