From 2f87cef5be7dcb0ae93b52ad1da6e4c09a04a429 Mon Sep 17 00:00:00 2001 From: Spencer Nelson Date: Mon, 11 Jan 2016 18:18:18 -0500 Subject: [PATCH 1/4] Add exponential scale --- index.js | 4 ++++ src/exp.js | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/log.js | 6 +++--- test/exp-test.js | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/exp.js create mode 100644 test/exp-test.js diff --git a/index.js b/index.js index 81c6c30..b82cf20 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,10 @@ export { point as scalePoint } from "./src/band"; +export { + default as scaleExp +} from "./src/exp"; + export { default as scaleIdentity } from "./src/identity"; diff --git a/src/exp.js b/src/exp.js new file mode 100644 index 0000000..cd4ff5a --- /dev/null +++ b/src/exp.js @@ -0,0 +1,44 @@ +import constant from "./constant"; +import {linearish} from "./linear"; +import { logp, powp } from "./log"; +import {default as continuous, copy, deinterpolateLinear} from "./continuous"; + +export default function exp() { + var base = Math.E, + log = logp(base), + pow = powp(base), + scale = continuous(deinterpolate, interpolate).range([1, base]), + domain = scale.domain; + + function rescale() { + log = logp(base); + return scale; + } + + function deinterpolate(a, b) { + a = pow(a); + b = pow(b) - a; + if (!b) return constant(b); + return function(x) { + return (pow(x) - a) / b; + }; + } + + function interpolate(a, b) { + a = pow(a); + b = pow(b) - a; + return function(t) { + return log(b * t + a); + }; + } + + scale.base = function(_) { + return arguments.length ? (base = +_, rescale()) : base; + }; + + scale.copy = function() { + return copy(scale, exp().base(base)); + }; + + return linearish(scale); +}; diff --git a/src/log.js b/src/log.js index 8eeb785..afd016d 100644 --- a/src/log.js +++ b/src/log.js @@ -23,18 +23,18 @@ function pow10(x) { return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; } -function powp(base) { +export function powp(base) { return base === 10 ? pow10 : base === Math.E ? Math.exp : function(x) { return Math.pow(base, x); }; } -function logp(base) { +export function logp(base) { return base === Math.E ? Math.log : base === 10 && Math.log10 || base === 2 && Math.log2 || (base = Math.log(base), function(x) { return Math.log(x) / base; }); -} +}; function reflect(f) { return function(x) { diff --git a/test/exp-test.js b/test/exp-test.js new file mode 100644 index 0000000..d8604ab --- /dev/null +++ b/test/exp-test.js @@ -0,0 +1,40 @@ +var tape = require("tape"), + scale = require("../"); + +require("./inDelta"); + +tape("scaleExp() has the expected defaults", function(test) { + var s = scale.scaleExp(); + test.deepEqual(s.domain(), [0, 1]); + test.deepEqual(s.range(), [1, Math.E]); + test.equal(s.clamp(), false); + test.equal(s.base(), Math.E); + test.end(); +}); + +tape("exp(x) maps a domain value x to a range value y", function(test) { + var s = scale.scaleExp(); + test.equal(s(0), 1); + test.equal(s(1), Math.E); + test.equal(s(2), Math.E * Math.E); + test.end(); +}); + +tape("exp(x) maps an empty domain to the range start", function(test) { + test.equal(scale.scaleExp().domain([0, 0]).range([1, 2])(0), 1); + test.equal(scale.scaleExp().domain([0, 0]).range([2, 1])(1), 2); + test.end(); +}); + +tape("exp.invert(y) maps a range value y to a domain value x", function(test) { + var s = scale.scaleExp(); + test.inDelta(s.invert(1), 0); + test.inDelta(s.invert(Math.E), 1); + test.inDelta(s.invert(Math.E*Math.E), 2); + + s.base(10); + test.inDelta(s.invert(0.01), -2); + test.inDelta(s.invert(1), 0); + test.inDelta(s.invert(100), 2); + test.end(); +}); From 70fbdf8b5b4b4e04f50ced65df062c15a99a44a0 Mon Sep 17 00:00:00 2001 From: Spencer Nelson Date: Mon, 11 Jan 2016 18:36:42 -0500 Subject: [PATCH 2/4] Minor formatting cleanup --- src/exp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exp.js b/src/exp.js index cd4ff5a..9d506d2 100644 --- a/src/exp.js +++ b/src/exp.js @@ -1,6 +1,6 @@ import constant from "./constant"; import {linearish} from "./linear"; -import { logp, powp } from "./log"; +import {logp, powp} from "./log"; import {default as continuous, copy, deinterpolateLinear} from "./continuous"; export default function exp() { From 9f30ea3c4204545c6b41148d33cedbcce5c18004 Mon Sep 17 00:00:00 2001 From: Spencer Nelson Date: Mon, 11 Jan 2016 19:22:15 -0500 Subject: [PATCH 3/4] Add test of interpolation in a different domain --- test/exp-test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/exp-test.js b/test/exp-test.js index d8604ab..1d3ccde 100644 --- a/test/exp-test.js +++ b/test/exp-test.js @@ -17,6 +17,12 @@ tape("exp(x) maps a domain value x to a range value y", function(test) { test.equal(s(0), 1); test.equal(s(1), Math.E); test.equal(s(2), Math.E * Math.E); + + s.domain([0, 2]).range([1, 100]); + test.equal(s(0), 1); + test.equal(s(1), 10); + test.equal(s(2), 100); + test.end(); }); From 78e333ae42445a06f232041f064b646df6916f4b Mon Sep 17 00:00:00 2001 From: Spencer Nelson Date: Mon, 11 Jan 2016 19:41:39 -0500 Subject: [PATCH 4/4] Add more tests of exponential scale behavior --- src/exp.js | 1 + test/exp-test.js | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/exp.js b/src/exp.js index 9d506d2..58da47f 100644 --- a/src/exp.js +++ b/src/exp.js @@ -12,6 +12,7 @@ export default function exp() { function rescale() { log = logp(base); + pow = powp(base); return scale; } diff --git a/test/exp-test.js b/test/exp-test.js index 1d3ccde..324d155 100644 --- a/test/exp-test.js +++ b/test/exp-test.js @@ -18,10 +18,30 @@ tape("exp(x) maps a domain value x to a range value y", function(test) { test.equal(s(1), Math.E); test.equal(s(2), Math.E * Math.E); - s.domain([0, 2]).range([1, 100]); - test.equal(s(0), 1); - test.equal(s(1), 10); - test.equal(s(2), 100); + s = s.base(10); + s = s.domain([0, 5]).range([1e0, 1e5]); + test.equal(s(0), 1e0); + test.equal(s(1), 1e1); + test.equal(s(2), 1e2); + test.equal(s(3), 1e3); + test.equal(s(4), 1e4); + test.equal(s(5), 1e5); + + s = s.domain([0, 5]).range([1e5, 1e10]); + test.equal(s(0), 1e5); + test.equal(s(1), 1e6); + test.equal(s(2), 1e7); + test.equal(s(3), 1e8); + test.equal(s(4), 1e9); + test.equal(s(5), 1e10); + + s = s.domain([5, 10]).range([1e2, 1e7]); + test.equal(s(5), 1e2); + test.equal(s(6), 1e3); + test.equal(s(7), 1e4); + test.equal(s(8), 1e5); + test.equal(s(9), 1e6); + test.equal(s(10), 1e7); test.end(); });