Skip to content

Commit

Permalink
Merge pull request #315 from hammerlab/sever-d3
Browse files Browse the repository at this point in the history
Sever dependence on d3.{scale,format}
  • Loading branch information
danvk committed Oct 20, 2015
2 parents 543f518 + cd77d19 commit f16c5ef
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 28 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"mocha-lcov-reporter": ">=0.0.2",
"mocha-phantomjs": "3.5.3",
"mocha-phantomjs-istanbul": "0.0.2",
"number-to-locale-string": "^1.0.0",
"parse-data-uri": "^0.2.0",
"phantomjs": "^1.9.17",
"prepush-hook": "^0.1.0",
Expand Down
2 changes: 0 additions & 2 deletions scripts/make-mini-d3.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@

smash \
node_modules/d3/src/start.js \
node_modules/d3/src/format/format.js \
node_modules/d3/src/scale/linear.js \
node_modules/d3/src/behavior/drag.js \
node_modules/d3/src/end.js \
| perl -pe 's/ε/EPSILON/g' \
Expand Down
4 changes: 2 additions & 2 deletions src/main/CoverageTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type * as Interval from './Interval';
import type {TwoBitSource} from './TwoBitDataSource';

var React = require('react'),
d3 = require('d3/minid3'),
scale = require('./scale'),
shallowEquals = require('shallow-equals'),
types = require('./react-types'),
d3utils = require('./d3utils'),
Expand Down Expand Up @@ -149,7 +149,7 @@ class CoverageTrack extends React.Component {
if (width === 0) return;
d3utils.sizeCanvas(canvas, width, height);

var yScale = d3.scale.linear()
var yScale = scale.linear()
.domain([this.state.maxCoverage, 0])
.range([padding, height - padding])
.nice();
Expand Down
12 changes: 6 additions & 6 deletions src/main/GeneTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import type {Gene} from './BigBedDataSource';
var React = require('react'),
ReactDOM = require('react-dom'),
_ = require('underscore'),
d3 = require('d3/minid3'),
shallowEquals = require('shallow-equals');

var types = require('./react-types'),
bedtools = require('./bedtools'),
Interval = require('./Interval'),
d3utils = require('./d3utils'),
scale = require('./scale'),
ContigInterval = require('./ContigInterval'),
canvasUtils = require('./canvas-utils'),
dataCanvas = require('data-canvas'),
Expand Down Expand Up @@ -111,10 +111,10 @@ var GeneTrack = React.createClass({
// Hold off until height & width are known.
if (width === 0) return;

var scale = this.getScale(),
var sc = this.getScale(),
// We can't clamp scale directly because of offsetPx.
clampedScale = d3.scale.linear()
.domain([scale.invert(0), scale.invert(width)])
clampedScale = scale.linear()
.domain([sc.invert(0), sc.invert(width)])
.range([0, width])
.clamp(true);

Expand All @@ -141,9 +141,9 @@ var GeneTrack = React.createClass({
// TODO: only compute all these intervals when data becomes available.
var exons = bedtools.splitCodingExons(gene.exons, gene.codingRegion);
exons.forEach(exon => {
ctx.fillRect(scale(exon.start),
ctx.fillRect(sc(exon.start),
geneLineY - 3 * (exon.isCoding ? 2 : 1),
scale(exon.stop + 1) - scale(exon.start),
sc(exon.stop + 1) - sc(exon.start),
6 * (exon.isCoding ? 2 : 1));
});

Expand Down
6 changes: 2 additions & 4 deletions src/main/LocationTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

var React = require('react'),
ReactDOM = require('react-dom'),
d3 = require('d3/minid3'),
EmptySource = require('./EmptySource'),
types = require('./react-types'),
canvasUtils = require('./canvas-utils'),
Expand Down Expand Up @@ -58,12 +57,11 @@ class LocationTrack extends React.Component {
canvasUtils.drawLine(ctx, rightLineX, 0, rightLineX, height);

// Mid label
var midLabelFormat = d3.format(',d'),
midY = height / 2;
var midY = height / 2;

ctx.fillStyle = style.LOC_FONT_COLOR;
ctx.font = style.LOC_FONT_STYLE;
ctx.fillText(midLabelFormat(midPoint) + ' bp',
ctx.fillText(midPoint.toLocaleString() + ' bp',
rightLineX + style.LOC_TICK_LENGTH + style.LOC_TEXT_PADDING,
midY + style.LOC_TEXT_Y_OFFSET);

Expand Down
10 changes: 5 additions & 5 deletions src/main/PileupTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {VisualAlignment, VisualGroup} from './PileupCache';
import type {DataCanvasRenderingContext2D} from 'data-canvas';

var React = require('react'),
d3 = require('d3/minid3'),
scale = require('./scale'),
shallowEquals = require('shallow-equals'),
types = require('./react-types'),
d3utils = require('./d3utils'),
Expand Down Expand Up @@ -153,10 +153,10 @@ function yForRow(row) {
// This is adapted from IGV.
var MIN_Q = 5, // these are Phred-scaled scores
MAX_Q = 20,
Q_SCALE = d3.scale.linear()
.domain([MIN_Q, MAX_Q])
.range([0.1, 0.9])
.clamp(true); // clamp output to [0.1, 0.9]
Q_SCALE = scale.linear()
.domain([MIN_Q, MAX_Q])
.range([0.1, 0.9])
.clamp(true); // clamp output to [0.1, 0.9]
function opacityForQuality(quality: number): number {
var alpha = Q_SCALE(quality);

Expand Down
33 changes: 25 additions & 8 deletions src/main/d3utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,47 @@

import type {GenomeRange} from './react-types';

var d3 = require('d3/minid3');
var scale = require('./scale');

/**
* Shared x-axis scaling logic for tracks
*/
function getTrackScale(range: GenomeRange, width: number) {
if (!range) return d3.scale.linear();
function getTrackScale(range: GenomeRange, width: number): any {
if (!range) return scale.linear();
var offsetPx = range.offsetPx || 0;
var scale = d3.scale.linear()
return scale.linear()
.domain([range.start, range.stop + 1]) // 1 bp wide
.range([-offsetPx, width - offsetPx]);
return scale;
}

var formatPrefixes = ["","k","M","G","T","P","E","Z","Y"];

// Returns the SI-prefix for num, ala d3.formatPrefix.
// See https://github.com/mbostock/d3/blob/5b981a18/src/format/formatPrefix.js
function formatPrefix(value: number) {
var i = 0;
if (value) {
if (value < 0) value *= -1;
i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
i = Math.max(0, Math.min(24, Math.floor((i - 1) / 3) * 3));
}
var k = Math.pow(10, i);
return {
symbol: formatPrefixes[i / 3],
scale: d => d / k
};
}

/**
* Formats the size of a view and infers what prefix/unit to show.
* This formatting follows IGV's conventions regarding range display:
* "1 bp", "101 bp", "1,001 bp", "1,001 kbp", ...
*/
function formatRange(viewSize: number): any {
function formatRange(viewSize: number): {prefix: string, unit: string} {
var tmpViewSize = viewSize / 1000,
fprefix = d3.formatPrefix(Math.max(1, tmpViewSize)),
fprefix = formatPrefix(Math.max(1, tmpViewSize)),
unit = fprefix.symbol + "bp", // bp, kbp, Mbp, Gbp
prefix = d3.format(',f.0')(fprefix.scale(viewSize));
prefix = Math.round(fprefix.scale(viewSize)).toLocaleString();
return {prefix, unit};
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/pileup.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function create(elOrId: string|Element, params: PileupParams): Pileup {
vizTracks.forEach(({source}) => {
source.off();
});
React.unmountComponentAtNode(el);
ReactDOM.unmountComponentAtNode(el);
reactElement = null;
referenceTrack = null;
vizTracks = null;
Expand Down
80 changes: 80 additions & 0 deletions src/main/scale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Lightweight replacement for d3.scale.linear().
* This only supports numeric scales, e.g. scale.range(['red', 'blue']) is invalid.
* @flow
*/
'use strict';

function linear(): any {
var clamped = false,
domain = [0, 1],
range = [0, 1];

var me = function(x) {
if (clamped) {
x = Math.max(Math.min(x, domain[1]), domain[0]);
}
// TODO: compute coefficients once.
return (x - domain[0]) / (domain[1] - domain[0]) * (range[1] - range[0]) + range[0];
};

me.clamp = function(x) {
if (x === undefined) return clamped;
clamped = x;
return this;
};
me.domain = function(x) {
if (x === undefined) return domain;
domain = x;
return this;
};
me.range = function(x) {
if (x === undefined) return range;
range = x;
return this;
};
me.invert = function(x) {
if (clamped) {
throw `Can't invert a clamped linear scale.`;
}
return (x - range[0]) / (range[1] - range[0]) * (domain[1] - domain[0]) + domain[0];
};
me.nice = function() {
// This method is adapted directly from d3's linear scale nice method.
// See https://github.com/mbostock/d3/blob/5b981a18d/src/scale/linear.js#L94-L112
var m = 10;
var extent = domain,
span = Math.abs(extent[1] - extent[0]),
step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)),
err = m / span * step;

// Filter ticks to get closer to the desired count.
if (err <= 0.15) step *= 10;
else if (err <= 0.35) step *= 5;
else if (err <= 0.75) step *= 2;

var nice = {
floor: function(x) { return Math.floor(x / step) * step; },
ceil: function(x) { return Math.ceil(x / step) * step; }
};

var i0 = 0,
i1 = 1,
x0 = domain[i0],
x1 = domain[i1],
dx;

if (x1 < x0) {
dx = i0; i0 = i1; i1 = dx;
dx = x0; x0 = x1; x1 = dx;
}

domain[i0] = nice.floor(x0);
domain[i1] = nice.ceil(x1);
return this;
};

return me;
}

module.exports = { linear };
1 change: 1 addition & 0 deletions src/test/coverage.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<script src="../../node_modules/es5-shim/es5-shim.min.js"></script>
<script src="../../node_modules/es5-shim/es5-sham.min.js"></script>
<script src="../../node_modules/arraybuffer-slice/index.js"></script>
<script src="../../node_modules/number-to-locale-string/polyfill.number.toLocaleString.js"></script>

<!-- Mocha -->
<script src="../../node_modules/mocha/mocha.js"></script>
Expand Down
24 changes: 24 additions & 0 deletions src/test/d3utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,28 @@ describe('d3utils', function() {
expect(r.unit).to.be.equal("Mbp");
});
});

describe('getTrackScale', function() {
var getTrackScale = d3utils.getTrackScale;
it('should define a linear scale', function() {
var scale = getTrackScale({start: 100, stop: 200}, 1000);
expect(scale(100)).to.equal(0);
expect(scale(201)).to.equal(1000);
});

it('should be invertible', function() {
var scale = getTrackScale({start: 100, stop: 200}, 1000);
expect(scale.invert(0)).to.equal(100);
expect(scale.invert(1000)).to.equal(201);
});

it('should be clampable', function() {
var scale = getTrackScale({start: 100, stop: 200}, 1000);
scale = scale.clamp(true);
expect(scale(0)).to.equal(0);
expect(scale(100)).to.equal(0);
expect(scale(201)).to.equal(1000);
expect(scale(500)).to.equal(1000);
});
});
});
1 change: 1 addition & 0 deletions src/test/runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<script src="../../node_modules/es5-shim/es5-shim.min.js"></script>
<script src="../../node_modules/es5-shim/es5-sham.min.js"></script>
<script src="../../node_modules/arraybuffer-slice/index.js"></script>
<script src="../../node_modules/number-to-locale-string/polyfill.number.toLocaleString.js"></script>

<!-- Mocha -->
<script src="../../node_modules/mocha/mocha.js"></script>
Expand Down
36 changes: 36 additions & 0 deletions src/test/scale-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** @flow */
'use strict';

var expect = require('chai').expect;
var scale = require('../main/scale');

describe('scale', function() {
it('should define a linear scale', function() {
var sc = scale.linear().domain([100, 201]).range([0, 1000]);
expect(sc(100)).to.equal(0);
expect(sc(201)).to.equal(1000);
});

it('should be invertible', function() {
var sc = scale.linear().domain([100, 201]).range([0, 1000]);
expect(sc.invert(0)).to.equal(100);
expect(sc.invert(1000)).to.equal(201);
});

it('should be clampable', function() {
var sc = scale.linear().domain([100, 201]).range([0, 1000]);
sc = sc.clamp(true);
expect(sc(0)).to.equal(0);
expect(sc(100)).to.equal(0);
expect(sc(201)).to.equal(1000);
expect(sc(500)).to.equal(1000);
});

it('should have nice values', function() {
var sc = scale.linear().domain([33, 0]).range(0, 100).nice();
expect(sc.domain()).to.deep.equal([35, 0]);

sc = scale.linear().domain([0, 33]).range(0, 100).nice();
expect(sc.domain()).to.deep.equal([0, 35]);
});
});

0 comments on commit f16c5ef

Please sign in to comment.