Skip to content

Commit

Permalink
Merge pull request #43 from hammerlab/nviz-bigbed
Browse files Browse the repository at this point in the history
Visualize genes
  • Loading branch information
danvk committed Mar 18, 2015
2 parents 82cbe9f + 4be6876 commit 6a58a5c
Show file tree
Hide file tree
Showing 17 changed files with 697 additions and 30 deletions.
2 changes: 2 additions & 0 deletions lib/mocha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare function describe(description: string, spec: () => void): void;
declare function it(expectation: string, assertion: (done: () => void) => void): void;
6 changes: 5 additions & 1 deletion lib/underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ declare module "underscore" {

declare function isEqual<S, T>(a: S, b: T): boolean;
declare function range(a: number, b: number): Array<number>;
declare function extend(...o: Object): Object;
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;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"mocha": "^2.1.0",
"mocha-lcov-reporter": ">=0.0.2",
"parse-data-uri": "^0.2.0",
"react-tools": "^0.12.2",
"react-tools": "^0.13.1",
"reactify": "^1.0.0",
"sinon": "^1.12.2",
"source-map": "^0.3.0"
Expand Down
30 changes: 29 additions & 1 deletion playground.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
/* track dimensions */
.reference {
width: 1200px;
height: 50px;
}

/* reference track */
.background {
fill: white;
}
.basepair.a { fill: #188712; }
.basepair.g { fill: #C45C16; }
.basepair.c { fill: #0600F9; }
.basepair.t { fill: #F70016; }
.basepair.u { fill: #F70016; }
text {
text.basepair {
font-size: 36;
font-weight: bold;
}

/* gene track */
.genes {
width: 1200px;
height: 50px;
}
.gene {
stroke-width: 1;
stroke: blue;
}
.gene text {
font-size: 16px;
text-anchor: middle;
stroke: black;
alignment-baseline: hanging;
}
#sense, #antisense {
stroke: blue;
}
.exon {
fill: blue;
}
</style>
</head>

Expand Down
112 changes: 112 additions & 0 deletions src/BigBedDataSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/* @flow */
'use strict';

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


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



type Gene = {
position: ContigInterval;
id: string; // transcript ID, e.g. "ENST00000269305"
strand: string; // '+' or '-'
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),
x = f.rest.split('\t'),
exonLengths = x[7].split(',').map(Number),
exonStarts = x[8].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],
exons
};
}


function createBigBedDataSource(remoteSource: BigBed): BigBedSource {
// Collection of genes that have already been loaded.
var genes: Array<Gene> = [];
window.genes = genes;

function addGene(newGene) {
if (!_.findWhere(genes, {id: newGene.id})) {
genes.push(newGene);
}
}

function getGenesInRange(range: ContigInterval): Gene[] {
if (!range) return [];
return genes.filter(gene => range.intersects(gene.position));
}

function fetch(range: GenomeRange) {
// TODO: add an API for requesting the entire block of genes.
return remoteSource.getFeaturesInRange(range.contig, range.start, range.stop)
.then(features => {
var genes = features.map(parseBedFeature);
genes.forEach(gene => addGene(gene));
});
}

var o = {
rangeChanged: function(newRange: GenomeRange) {
fetch(newRange)
.then(() => o.trigger('newdata', newRange))
.done();
},
getGenesInRange,

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

return o;
}

module.exports = createBigBedDataSource;
22 changes: 22 additions & 0 deletions src/Controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ var Controls = React.createClass({
this.refs.contig.getDOMNode().selectedIndex = contigIdx;
}
},
zoomIn: function() {
this.zoomByFactor(0.5);
},
zoomOut: function() {
this.zoomByFactor(2.0);
},
zoomByFactor: function(factor: number) {
var r = this.props.range;
if (!r) return;

var span = r.stop - r.start,
center = r.start + span / 2,
newSpan = factor * span;
this.props.onChange({
contig: r.contig,
start: Math.max(0, center - newSpan / 2),
stop: center + newSpan / 2 // TODO: clamp
});
},
render: function(): any {
var contigOptions = this.props.contigList
? this.props.contigList.map((contig, i) => <option key={i}>{contig}</option>)
Expand All @@ -54,6 +73,9 @@ var Controls = React.createClass({
<input ref='start' type='text' />
<input ref='stop' type='text' />
<button>Go</button>

<button onClick={this.zoomOut}>-</button>
<button onClick={this.zoomIn}>+</button>
</form>
);
},
Expand Down
Loading

0 comments on commit 6a58a5c

Please sign in to comment.