diff --git a/src/index.js b/src/index.js index 86ab489..510103c 100644 --- a/src/index.js +++ b/src/index.js @@ -29,6 +29,10 @@ export { sqrt as scaleSqrt } from "./pow.js"; +export { + default as scaleRadial +} from "./radial.js"; + export { default as scaleQuantile } from "./quantile.js"; diff --git a/src/radial.js b/src/radial.js new file mode 100644 index 0000000..5c76707 --- /dev/null +++ b/src/radial.js @@ -0,0 +1,51 @@ +import linear, {linearish} from "./linear.js"; +import number from "./number.js"; + +function square(x) { + return (x < 0 ? -1 : 1) * x * x; +} + +function unsquare(x) { + return (x < 0 ? -1 : 1) * Math.sqrt(Math.abs(x)); +} + +export default function radial() { + var squared = linear(), + range = [0, 1], + round = false; + + function scale(x) { + var y = unsquare(squared(x)); + return round ? Math.round(y) : y; + } + + scale.invert = function(y) { + return squared.invert(square(y)); + }; + + scale.domain = function(_) { + return arguments.length ? (squared.domain(_), scale) : squared.domain(); + }; + + scale.range = function(_) { + return arguments.length ? (squared.range((range = Array.from(_, number)).map(square)), scale) : range.slice(); + }; + + scale.round = function(_) { + return arguments.length ? (round = !!_, scale) : round; + }; + + scale.clamp = function(_) { + return arguments.length ? (squared.clamp(_), scale) : squared.clamp(); + }; + + scale.copy = function() { + return radial() + .domain(squared.domain()) + .range(range) + .round(round) + .clamp(squared.clamp()); + }; + + return linearish(scale); +} diff --git a/test/radial-test.js b/test/radial-test.js new file mode 100644 index 0000000..e34c6ec --- /dev/null +++ b/test/radial-test.js @@ -0,0 +1,50 @@ +var tape = require("tape"), + scale = require("../"); + +tape("scaleRadial() has the expected defaults", function(test) { + var s = scale.scaleRadial(); + test.deepEqual(s.domain(), [0, 1]); + test.deepEqual(s.range(), [0, 1]); + test.equal(s.clamp(), false); + test.equal(s.round(), false); + test.end(); +}); + +tape("radial(x) maps a domain value x to a range value y", function(test) { + test.equal(scale.scaleRadial().range([1, 2])(0.5), 1.5811388300841898); + test.end(); +}); + +tape("radial(x) ignores extra range values if the domain is smaller than the range", function(test) { + test.equal(scale.scaleRadial().domain([-10, 0]).range([2, 3, 4]).clamp(true)(-5), 2.5495097567963922); + test.equal(scale.scaleRadial().domain([-10, 0]).range([2, 3, 4]).clamp(true)(50), 3); + test.end(); +}); + +tape("radial(x) ignores extra domain values if the range is smaller than the domain", function(test) { + test.equal(scale.scaleRadial().domain([-10, 0, 100]).range([2, 3]).clamp(true)(-5), 2.5495097567963922); + test.equal(scale.scaleRadial().domain([-10, 0, 100]).range([2, 3]).clamp(true)(50), 3); + test.end(); +}); + +tape("radial(x) maps an empty domain to the middle of the range", function(test) { + test.equal(scale.scaleRadial().domain([0, 0]).range([1, 2])(0), 1.5811388300841898); + test.equal(scale.scaleRadial().domain([0, 0]).range([2, 1])(1), 1.5811388300841898); + test.end(); +}); + +tape("radial(x) can map a bilinear domain with two values to the corresponding range", function(test) { + var s = scale.scaleRadial().domain([1, 2]); + test.deepEqual(s.domain(), [1, 2]); + test.equal(s(0.5), -0.7071067811865476); + test.equal(s(1.0), 0.0); + test.equal(s(1.5), 0.7071067811865476); + test.equal(s(2.0), 1.0); + test.equal(s(2.5), 1.224744871391589); + test.equal(s.invert(-0.5), 0.75); + test.equal(s.invert( 0.0), 1.0); + test.equal(s.invert( 0.5), 1.25); + test.equal(s.invert( 1.0), 2.0); + test.equal(s.invert( 1.5), 3.25); + test.end(); +});