Skip to content

Commit

Permalink
Merge dbb887e into 60857a0
Browse files Browse the repository at this point in the history
  • Loading branch information
danvk committed Mar 4, 2015
2 parents 60857a0 + dbb887e commit 4228d89
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 23 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ Interactive in-browser track viewer
npm install
grunt prod

To play with the demo, you'll need [RangeHTTPServer][rs], which adds support
for byte range requests to SimpleHTTPServer:
To play with the demo, you'll need to install the node.js [http-server][hs]:

pip install rangehttpserver
python -m RangeHTTPServer
npm install http-server
http-server

Then open `http://localhost:8000/playground.html` in your browser of choice.
Then open `http://localhost:8080/playground.html` in your browser of choice.

## Development

Expand All @@ -37,4 +36,4 @@ To continuously regenerate the combined JS, run:

grunt watchFlowProd

[rs]: https://github.com/danvk/RangeHTTPServer
[hs]: https://github.com/nodeapps/http-server
2 changes: 1 addition & 1 deletion lib/underscore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

declare module "underscore" {
declare function findWhere<T>(list: Array<T>, properties: {}): ?T;

declare function clone<T>(obj: T): T;
}

3 changes: 2 additions & 1 deletion scripts/post-coverage.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/bin/bash
# Generate code coverage and post it to Coveralls.
set -o errexit
set -x

# Generate LCOV data for the bundled tests
Expand All @@ -11,3 +10,5 @@ grunt coverage
build/tests.map \
build/bundled.lcov \
| ./node_modules/.bin/coveralls

echo '' # reset last exit code
7 changes: 5 additions & 2 deletions src/Controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@ var Controls = React.createClass({
? this.props.contigList.map((contig, i) => <option key={i}>{contig}</option>)
: null;

// TODO: this is broken. The UI should show the current range _and_ allow editing.
var r = this.props.range || {contig: null, start: null, stop: null};

return (
<div className='controls'>
Contig:
<select ref='contig' onChange={this.handleChange}>
{contigOptions}
</select>
<input ref='start' type='text' />
<input ref='stop' type='text' />
<input ref='start' type='text' value={r.start} readOnly />
<input ref='stop' type='text' value={r.stop} readOnly />
<button onClick={this.handleChange}>Update</button>
</div>
);
Expand Down
67 changes: 60 additions & 7 deletions src/GenomeTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
*/

var React = require('react'),
_ = require('underscore'),
d3 = require('d3'),
types = require('./types');

var GenomeTrack = React.createClass({
propTypes: {
range: types.GenomeRange,
basePairs: React.PropTypes.string
basePairs: React.PropTypes.string,
onRangeChange: React.PropTypes.func.isRequired
},
render: function(): any {
var range = this.props.range;
Expand All @@ -33,16 +35,67 @@ var GenomeTrack = React.createClass({
var NonEmptyGenomeTrack = React.createClass({
propTypes: {
range: types.GenomeRange.isRequired,
basePairs: React.PropTypes.string.isRequired
basePairs: React.PropTypes.string.isRequired,
onRangeChange: React.PropTypes.func.isRequired
},
render: function(): any {
return <div className="reference"></div>;
},
componentDidMount: function() {
var svg = d3.select(this.getDOMNode())
var div = this.getDOMNode(),
svg = d3.select(div)
.append('svg');

var originalRange, originalScale, dx=0;
var dragstarted = () => {
d3.event.sourceEvent.stopPropagation();
dx = 0;
originalRange = _.clone(this.props.range);
originalScale = this.getScale();
};
var updateRange = () => {
if (!originalScale) return; // can never happen, but Flow don't know.
if (!originalRange) return; // can never happen, but Flow don't know.
var newStart = originalScale.invert(-dx),
intStart = Math.round(newStart),
offsetPx = originalScale(newStart) - originalScale(intStart);

var newRange = {
contig: originalRange.contig,
start: intStart,
stop: intStart + (originalRange.stop - originalRange.start),
offsetPx: offsetPx
};
this.props.onRangeChange(newRange);
};
var dragmove = () => {
dx += d3.event.dx; // these are integers, so no roundoff issues.
updateRange();
};
function dragended() {
updateRange();
}

var drag = d3.behavior.drag()
.on("dragstart", dragstarted)
.on("drag", dragmove)
.on("dragend", dragended);

var g = svg.append('g')
.call(drag);

this.updateVisualization();
},
getScale: function() {
var div = this.getDOMNode(),
range = this.props.range,
width = div.offsetWidth,
offsetPx = range.offsetPx || 0;
var scale = d3.scale.linear()
.domain([range.start, range.stop + 1]) // 1 bp wide
.range([-offsetPx, width - offsetPx]);
return scale;
},
componentDidUpdate: function(prevProps: any, prevState: any) {
this.updateVisualization();
},
Expand All @@ -53,9 +106,7 @@ var NonEmptyGenomeTrack = React.createClass({
height = div.offsetHeight,
svg = d3.select(div).select('svg');

var scale = d3.scale.linear()
.domain([range.start, range.stop + 1]) // 1 bp wide
.range([0, width]);
var scale = this.getScale();

var absBasePairs = [].map.call(this.props.basePairs, (bp, i) => ({
position: i + range.start,
Expand All @@ -65,7 +116,9 @@ var NonEmptyGenomeTrack = React.createClass({
svg.attr('width', width)
.attr('height', height);

var letter = svg.selectAll('text')
var g = svg.select('g');

var letter = g.selectAll('text')
.data(absBasePairs, bp => bp.position);

// Enter
Expand Down
14 changes: 11 additions & 3 deletions src/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,31 @@ var Root = React.createClass({
var ref: TwoBit = this.props.referenceSource;
ref.getContigList().then(contigList => {
this.setState({contigList});
});
// this is here to facilitate faster iteration
this.handleRangeChange({
contig: 'chr1',
start: 123456,
stop: 123500
});
}).done();
},
handleRangeChange: function(newRange: GenomeRange) {
this.setState({range: newRange, basePairs: null});
var ref = this.props.referenceSource;
ref.getFeaturesInRange(newRange.contig, newRange.start, newRange.stop)
.then(basePairs => {
this.setState({basePairs});
});
}).done();
},
render: function(): any {
return (
<div>
<Controls contigList={this.state.contigList}
range={this.state.range}
onChange={this.handleRangeChange} />
<GenomeTrack range={this.state.range}
basePairs={this.state.basePairs} />
basePairs={this.state.basePairs}
onRangeChange={this.handleRangeChange} />
</div>
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/TwoBit.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ class TwoBit {

// Returns the base pairs for contig:start-stop. The range is inclusive.
getFeaturesInRange(contig: string, start: number, stop: number): Q.Promise<string> {
if (start > stop) {
throw `Requested a 2bit range with start > stop (${start}, ${stop})`;
}

start--; // switch to zero-based indices
stop--;
return this._getSequenceHeader(contig).then(header => {
Expand Down
3 changes: 3 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ genome.getFeaturesInRange('chr22', 19178140, 19178170).then(basePairs => {
}
}).done();

// pre-load some data to allow network-free panning
genome.getFeaturesInRange('chr1', 123000, 124000).done();

var root = React.render(<Root referenceSource={genome} />,
document.getElementById('root'));
3 changes: 2 additions & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = {
GenomeRange: React.PropTypes.shape({
contig: React.PropTypes.string,
start: React.PropTypes.number,
stop: React.PropTypes.number
stop: React.PropTypes.number,
offsetPx: React.PropTypes.number
})
};
4 changes: 2 additions & 2 deletions 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;
start: number;
stop: number;
}

0 comments on commit 4228d89

Please sign in to comment.