Skip to content

Commit

Permalink
Merge 50bf169 into f118690
Browse files Browse the repository at this point in the history
  • Loading branch information
danvk committed Mar 5, 2015
2 parents f118690 + 50bf169 commit fd31c85
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 14 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"homepage": "https://github.com/danvk/pileup.js",
"dependencies": {
"backbone": "^1.1.2",
"d3": "^3.5.5",
"q": "^1.1.2",
"react": "^0.12.2",
Expand Down
32 changes: 21 additions & 11 deletions src/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
var React = require('react'),
Controls = require('./Controls'),
GenomeTrack = require('./GenomeTrack'),
types = require('./types'),
types = require('./types');
// TODO: make this an "import type" when react-tools 0.13.0 is out.
TwoBit = require('./TwoBit');

var Root = React.createClass({
propTypes: {
Expand All @@ -23,24 +22,35 @@ var Root = React.createClass({
},
componentDidMount: function() {
// Note: flow is unable to infer this type through `this.propTypes`.
var ref: TwoBit = this.props.referenceSource;
ref.getContigList().then(contigList => {
this.setState({contigList});
var source: TwoBitDataSource = this.props.referenceSource;
source.needContigs();

source.on('contigs', () => this.update)
.on('newdata', () => this.update);

source.on('contigs', () => {
// this is here to facilitate faster iteration
this.handleRangeChange({
contig: 'chr1',
start: 123456,
stop: 123500
});
}).done();
});

this.update();
},
update: function() {
this.setState({
contigList: this.props.referenceSource.contigList(),
basePairs: this.props.referenceSource.getRange(this.state.range)
});
},
handleRangeChange: function(newRange: GenomeRange) {
this.setState({range: newRange, basePairs: null});
this.setState({range: newRange});
this.update();

var ref = this.props.referenceSource;
ref.getFeaturesInRange(newRange.contig, newRange.start, newRange.stop)
.then(basePairs => {
this.setState({basePairs});
}).done();
ref.rangeChanged(newRange);
},
render: function(): any {
return (
Expand Down
79 changes: 79 additions & 0 deletions src/TwoBitDataSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* The "glue" between TwoBit.js and GenomeTrack.js.
*
* GenomeTrack is pure view code -- it renders data which is already in-memory
* in the browser.
*
* TwoBit is purely for data parsing and fetching. It only knows how to return
* promises for various genome features.
*
* This code acts as a bridge between the two. It maintains a local version of
* the data, fetching remote data and informing the view when it becomes
* available.
*
* @flow
*/
'use strict';

// import type * as TwoBit from './TwoBit';

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

// Factor by which to over-request data from the network.
// If a range of 100bp is shown and this is 2.0, then 300 base pairs will be
// requested over the network (100 * 2.0 too much)
var EXPANSION_FACTOR = 2.0;


var createTwoBitDataSource = function(remoteSource: TwoBit) {
// Local cache of genomic data.
var contigList = [];
var basePairs = {}; // contig -> locus -> letter
function getBasePair(contig: string, position: number) {
return basePairs[contig] && basePairs[contig][position];
}
function setBasePair(contig: string, position: number, letter: string) {
if (!basePairs[contig]) basePairs[contig] = {};
basePairs[contig][position] = letter;
}

function fetch(range: GenomeRange) {
return remoteSource.getFeaturesInRange(range.contig, range.start, range.stop)
.then(letters => {
for (var i = 0; i < letters.length; i++) {
setBasePair(range.contig, range.start + i, letters[i]);
}
});
}

function getRange(range: GenomeRange) {
if (!range) return null;
return _.range(range.start, range.stop)
.map(x => getBasePair(range.contig, x))
.join('');
}

var o = {
rangeChanged: function(newRange: GenomeRange) {
// Range has changed! Fetch new data.
// TODO: only fetch it if it isn't cached already.
fetch(newRange)
.then(() => o.trigger('newdata', newRange))
.done();
},
needContigs: () => {
remoteSource.getContigList().then(c => {
contigList = c;
o.trigger('contigs', contigList);
}).done();
},
getRange: getRange,
contigList: () => contigList
};
_.extend(o, Events); // Make this an event emitter

return o;
};

module.exports = createTwoBitDataSource;
6 changes: 4 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* @flow */
var React = require('react'),
TwoBit = require('./TwoBit'),
Root = require('./Root');
Root = require('./Root'),
createTwoBitDataSource = require('./TwoBitDataSource');

var startMs = Date.now();
// var genome = new TwoBit('http://www.biodalliance.org/datasets/hg19.2bit');
var genome = new TwoBit('/hg19.2bit');
var dataSource = createTwoBitDataSource(genome);

genome.getFeaturesInRange('chr22', 19178140, 19178170).then(basePairs => {
var endMs = Date.now();
Expand All @@ -19,5 +21,5 @@ genome.getFeaturesInRange('chr22', 19178140, 19178170).then(basePairs => {
// pre-load some data to allow network-free panning
genome.getFeaturesInRange('chr1', 123000, 124000).done();

var root = React.render(<Root referenceSource={genome} />,
var root = React.render(<Root referenceSource={dataSource} />,
document.getElementById('root'));
2 changes: 1 addition & 1 deletion types/types.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
declare class GenomeRange {
contig: string;
start: number;
stop: number;
stop: number; // XXX inclusive or exclusive?
}

0 comments on commit fd31c85

Please sign in to comment.