├── img ├── band.png └── point.png ├── src ├── number.js ├── constant.js ├── colors.js ├── nice.js ├── utcTime.js ├── init.js ├── identity.js ├── symlog.js ├── threshold.js ├── sequentialQuantile.js ├── tickFormat.js ├── ordinal.js ├── pow.js ├── quantize.js ├── quantile.js ├── index.js ├── radial.js ├── linear.js ├── time.js ├── band.js ├── sequential.js ├── diverging.js ├── continuous.js └── log.js ├── .gitignore ├── test ├── roundEpsilon.js ├── .eslintrc.json ├── sqrt-test.js ├── asserts.js ├── sequentialQuantile-test.js ├── date.js ├── point-test.js ├── tickFormat-test.js ├── threshold-test.js ├── radial-test.js ├── sequential-test.js ├── quantize-test.js ├── quantile-test.js ├── diverging-test.js ├── symlog-test.js ├── identity-test.js ├── ordinal-test.js ├── band-test.js ├── utcTime-test.js ├── time-test.js ├── log-test.js ├── pow-test.js └── linear-test.js ├── .eslintrc.json ├── .github ├── eslint.json └── workflows │ └── node.js.yml ├── LICENSE ├── README.md ├── rollup.config.js └── package.json /img/band.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3/d3-scale/HEAD/img/band.png -------------------------------------------------------------------------------- /img/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3/d3-scale/HEAD/img/point.png -------------------------------------------------------------------------------- /src/number.js: -------------------------------------------------------------------------------- 1 | export default function number(x) { 2 | return +x; 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | .DS_Store 3 | dist/ 4 | node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /test/roundEpsilon.js: -------------------------------------------------------------------------------- 1 | export function roundEpsilon(x) { 2 | return Math.round(x * 1e12) / 1e12; 3 | } 4 | -------------------------------------------------------------------------------- /src/constant.js: -------------------------------------------------------------------------------- 1 | export default function constants(x) { 2 | return function() { 3 | return x; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /src/colors.js: -------------------------------------------------------------------------------- 1 | export default function colors(s) { 2 | return s.match(/.{6}/g).map(function(x) { 3 | return "#" + x; 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 8 6 | }, 7 | "env": { 8 | "es6": true 9 | }, 10 | "rules": { 11 | "no-cond-assign": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 8 6 | }, 7 | "env": { 8 | "mocha": true 9 | }, 10 | "rules": { 11 | "no-sparse-arrays": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/nice.js: -------------------------------------------------------------------------------- 1 | export default function nice(domain, interval) { 2 | domain = domain.slice(); 3 | 4 | var i0 = 0, 5 | i1 = domain.length - 1, 6 | x0 = domain[i0], 7 | x1 = domain[i1], 8 | t; 9 | 10 | if (x1 < x0) { 11 | t = i0, i0 = i1, i1 = t; 12 | t = x0, x0 = x1, x1 = t; 13 | } 14 | 15 | domain[i0] = interval.floor(x0); 16 | domain[i1] = interval.ceil(x1); 17 | return domain; 18 | } 19 | -------------------------------------------------------------------------------- /.github/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "eslint-compact", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "severity": 4, 12 | "message": 5, 13 | "code": 6 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/utcTime.js: -------------------------------------------------------------------------------- 1 | import {utcYear, utcMonth, utcWeek, utcDay, utcHour, utcMinute, utcSecond, utcTicks, utcTickInterval} from "d3-time"; 2 | import {utcFormat} from "d3-time-format"; 3 | import {calendar} from "./time.js"; 4 | import {initRange} from "./init.js"; 5 | 6 | export default function utcTime() { 7 | return initRange.apply(calendar(utcTicks, utcTickInterval, utcYear, utcMonth, utcWeek, utcDay, utcHour, utcMinute, utcSecond, utcFormat).domain([Date.UTC(2000, 0, 1), Date.UTC(2000, 0, 2)]), arguments); 8 | } 9 | -------------------------------------------------------------------------------- /test/sqrt-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleSqrt} from "../src/index.js"; 3 | 4 | it("scaleSqrt() has the expected defaults", () => { 5 | const s = scaleSqrt(); 6 | assert.deepStrictEqual(s.domain(), [0, 1]); 7 | assert.deepStrictEqual(s.range(), [0, 1]); 8 | assert.strictEqual(s.clamp(), false); 9 | assert.strictEqual(s.exponent(), 0.5); 10 | assert.deepStrictEqual(s.interpolate()({array: ["red"]}, {array: ["blue"]})(0.5), {array: ["rgb(128, 0, 128)"]}); 11 | }); 12 | 13 | it("sqrt(x) maps a domain value x to a range value y", () => { 14 | assert.strictEqual(scaleSqrt()(0.5), Math.SQRT1_2); 15 | }); 16 | -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | export function initRange(domain, range) { 2 | switch (arguments.length) { 3 | case 0: break; 4 | case 1: this.range(domain); break; 5 | default: this.range(range).domain(domain); break; 6 | } 7 | return this; 8 | } 9 | 10 | export function initInterpolator(domain, interpolator) { 11 | switch (arguments.length) { 12 | case 0: break; 13 | case 1: { 14 | if (typeof domain === "function") this.interpolator(domain); 15 | else this.range(domain); 16 | break; 17 | } 18 | default: { 19 | this.domain(domain); 20 | if (typeof interpolator === "function") this.interpolator(interpolator); 21 | else this.range(interpolator); 22 | break; 23 | } 24 | } 25 | return this; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010-2021 Mike Bostock 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /src/identity.js: -------------------------------------------------------------------------------- 1 | import {linearish} from "./linear.js"; 2 | import number from "./number.js"; 3 | 4 | export default function identity(domain) { 5 | var unknown; 6 | 7 | function scale(x) { 8 | return x == null || isNaN(x = +x) ? unknown : x; 9 | } 10 | 11 | scale.invert = scale; 12 | 13 | scale.domain = scale.range = function(_) { 14 | return arguments.length ? (domain = Array.from(_, number), scale) : domain.slice(); 15 | }; 16 | 17 | scale.unknown = function(_) { 18 | return arguments.length ? (unknown = _, scale) : unknown; 19 | }; 20 | 21 | scale.copy = function() { 22 | return identity(domain).unknown(unknown); 23 | }; 24 | 25 | domain = arguments.length ? Array.from(domain, number) : [0, 1]; 26 | 27 | return linearish(scale); 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 2 | 3 | name: Node.js CI 4 | 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn --frozen-lockfile 27 | - run: | 28 | echo ::add-matcher::.github/eslint.json 29 | yarn run eslint src test --format=compact 30 | - run: yarn test 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # d3-scale 2 | 3 | 4 | 5 | Scales map a dimension of abstract data to a visual representation. Although most often used for encoding data as position, say to map time and temperature to a horizontal and vertical position in a scatterplot, scales can represent virtually any visual encoding, such as color, stroke width, or symbol size. Scales can also be used with virtually any type of data, such as named categorical data or discrete data that requires sensible breaks. 6 | 7 | ## Resources 8 | 9 | - [Documentation](https://d3js.org/d3-scale) 10 | - [Examples](https://observablehq.com/collection/@d3/d3-scale) 11 | - [Releases](https://github.com/d3/d3-scale/releases) 12 | - [Getting help](https://d3js.org/community) 13 | -------------------------------------------------------------------------------- /src/symlog.js: -------------------------------------------------------------------------------- 1 | import {linearish} from "./linear.js"; 2 | import {copy, transformer} from "./continuous.js"; 3 | import {initRange} from "./init.js"; 4 | 5 | function transformSymlog(c) { 6 | return function(x) { 7 | return Math.sign(x) * Math.log1p(Math.abs(x / c)); 8 | }; 9 | } 10 | 11 | function transformSymexp(c) { 12 | return function(x) { 13 | return Math.sign(x) * Math.expm1(Math.abs(x)) * c; 14 | }; 15 | } 16 | 17 | export function symlogish(transform) { 18 | var c = 1, scale = transform(transformSymlog(c), transformSymexp(c)); 19 | 20 | scale.constant = function(_) { 21 | return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c; 22 | }; 23 | 24 | return linearish(scale); 25 | } 26 | 27 | export default function symlog() { 28 | var scale = symlogish(transformer()); 29 | 30 | scale.copy = function() { 31 | return copy(scale, symlog()).constant(scale.constant()); 32 | }; 33 | 34 | return initRange.apply(scale, arguments); 35 | } 36 | -------------------------------------------------------------------------------- /test/asserts.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | 3 | export function assertInDelta(actual, expected, delta = 1e-6) { 4 | assert(inDelta(actual, expected, delta), `${actual} should be within ${delta} of ${expected}`); 5 | } 6 | 7 | function inDelta(actual, expected, delta) { 8 | return (Array.isArray(expected) ? inDeltaArray 9 | : typeof expected === "object" ? inDeltaObject 10 | : inDeltaNumber)(actual, expected, delta); 11 | } 12 | 13 | function inDeltaArray(actual, expected, delta) { 14 | let n = expected.length, i = -1; 15 | if (actual.length !== n) return false; 16 | while (++i < n) if (!inDelta(actual[i], expected[i], delta)) return false; 17 | return true; 18 | } 19 | 20 | function inDeltaObject(actual, expected, delta) { 21 | for (let i in expected) if (!inDelta(actual[i], expected[i], delta)) return false; 22 | for (let i in actual) if (!(i in expected)) return false; 23 | return true; 24 | } 25 | 26 | function inDeltaNumber(actual, expected, delta) { 27 | return actual >= expected - delta && actual <= expected + delta; 28 | } 29 | -------------------------------------------------------------------------------- /test/sequentialQuantile-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleSequentialQuantile} from "../src/index.js"; 3 | 4 | it("sequentialQuantile() clamps", () => { 5 | const s = scaleSequentialQuantile().domain([0, 1, 2, 3, 10]); 6 | assert.strictEqual(s(-1), 0); 7 | assert.strictEqual(s(0), 0); 8 | assert.strictEqual(s(1), 0.25); 9 | assert.strictEqual(s(10), 1); 10 | assert.strictEqual(s(20), 1); 11 | }); 12 | 13 | it("sequentialQuantile().domain() sorts the domain", () => { 14 | const s = scaleSequentialQuantile().domain([0, 2, 9, 0.1, 10]); 15 | assert.deepStrictEqual(s.domain(), [0, 0.1, 2, 9, 10]); 16 | }); 17 | 18 | it("sequentialQuantile().range() returns the computed range", () => { 19 | const s = scaleSequentialQuantile().domain([0, 2, 9, 0.1, 10]); 20 | assert.deepStrictEqual(s.range(), [0 / 4, 1 / 4, 2 / 4, 3 / 4, 4 / 4]); 21 | }); 22 | 23 | it("sequentialQuantile().quantiles(n) computes n + 1 quantiles", () => { 24 | const s = scaleSequentialQuantile().domain(Array.from({length: 2000}, (_, i) => 2 * i / 1999)); 25 | assert.deepStrictEqual(s.quantiles(4), [0, 0.5, 1, 1.5, 2]); 26 | }); 27 | -------------------------------------------------------------------------------- /src/threshold.js: -------------------------------------------------------------------------------- 1 | import {bisect} from "d3-array"; 2 | import {initRange} from "./init.js"; 3 | 4 | export default function threshold() { 5 | var domain = [0.5], 6 | range = [0, 1], 7 | unknown, 8 | n = 1; 9 | 10 | function scale(x) { 11 | return x != null && x <= x ? range[bisect(domain, x, 0, n)] : unknown; 12 | } 13 | 14 | scale.domain = function(_) { 15 | return arguments.length ? (domain = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : domain.slice(); 16 | }; 17 | 18 | scale.range = function(_) { 19 | return arguments.length ? (range = Array.from(_), n = Math.min(domain.length, range.length - 1), scale) : range.slice(); 20 | }; 21 | 22 | scale.invertExtent = function(y) { 23 | var i = range.indexOf(y); 24 | return [domain[i - 1], domain[i]]; 25 | }; 26 | 27 | scale.unknown = function(_) { 28 | return arguments.length ? (unknown = _, scale) : unknown; 29 | }; 30 | 31 | scale.copy = function() { 32 | return threshold() 33 | .domain(domain) 34 | .range(range) 35 | .unknown(unknown); 36 | }; 37 | 38 | return initRange.apply(scale, arguments); 39 | } 40 | -------------------------------------------------------------------------------- /src/sequentialQuantile.js: -------------------------------------------------------------------------------- 1 | import {ascending, bisect, quantile} from "d3-array"; 2 | import {identity} from "./continuous.js"; 3 | import {initInterpolator} from "./init.js"; 4 | 5 | export default function sequentialQuantile() { 6 | var domain = [], 7 | interpolator = identity; 8 | 9 | function scale(x) { 10 | if (x != null && !isNaN(x = +x)) return interpolator((bisect(domain, x, 1) - 1) / (domain.length - 1)); 11 | } 12 | 13 | scale.domain = function(_) { 14 | if (!arguments.length) return domain.slice(); 15 | domain = []; 16 | for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d); 17 | domain.sort(ascending); 18 | return scale; 19 | }; 20 | 21 | scale.interpolator = function(_) { 22 | return arguments.length ? (interpolator = _, scale) : interpolator; 23 | }; 24 | 25 | scale.range = function() { 26 | return domain.map((d, i) => interpolator(i / (domain.length - 1))); 27 | }; 28 | 29 | scale.quantiles = function(n) { 30 | return Array.from({length: n + 1}, (_, i) => quantile(domain, i / n)); 31 | }; 32 | 33 | scale.copy = function() { 34 | return sequentialQuantile(interpolator).domain(domain); 35 | }; 36 | 37 | return initInterpolator.apply(scale, arguments); 38 | } 39 | -------------------------------------------------------------------------------- /src/tickFormat.js: -------------------------------------------------------------------------------- 1 | import {tickStep} from "d3-array"; 2 | import {format, formatPrefix, formatSpecifier, precisionFixed, precisionPrefix, precisionRound} from "d3-format"; 3 | 4 | export default function tickFormat(start, stop, count, specifier) { 5 | var step = tickStep(start, stop, count), 6 | precision; 7 | specifier = formatSpecifier(specifier == null ? ",f" : specifier); 8 | switch (specifier.type) { 9 | case "s": { 10 | var value = Math.max(Math.abs(start), Math.abs(stop)); 11 | if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision; 12 | return formatPrefix(specifier, value); 13 | } 14 | case "": 15 | case "e": 16 | case "g": 17 | case "p": 18 | case "r": { 19 | if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e"); 20 | break; 21 | } 22 | case "f": 23 | case "%": { 24 | if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2; 25 | break; 26 | } 27 | } 28 | return format(specifier); 29 | } 30 | -------------------------------------------------------------------------------- /test/date.js: -------------------------------------------------------------------------------- 1 | export function local(year, month, day, hours, minutes, seconds, milliseconds) { 2 | if (year == null) year = 0; 3 | if (month == null) month = 0; 4 | if (day == null) day = 1; 5 | if (hours == null) hours = 0; 6 | if (minutes == null) minutes = 0; 7 | if (seconds == null) seconds = 0; 8 | if (milliseconds == null) milliseconds = 0; 9 | if (0 <= year && year < 100) { 10 | const date = new Date(-1, month, day, hours, minutes, seconds, milliseconds); 11 | date.setFullYear(year); 12 | return date; 13 | } 14 | return new Date(year, month, day, hours, minutes, seconds, milliseconds); 15 | } 16 | 17 | export function utc(year, month, day, hours, minutes, seconds, milliseconds) { 18 | if (year == null) year = 0; 19 | if (month == null) month = 0; 20 | if (day == null) day = 1; 21 | if (hours == null) hours = 0; 22 | if (minutes == null) minutes = 0; 23 | if (seconds == null) seconds = 0; 24 | if (milliseconds == null) milliseconds = 0; 25 | if (0 <= year && year < 100) { 26 | const date = new Date(Date.UTC(-1, month, day, hours, minutes, seconds, milliseconds)); 27 | date.setUTCFullYear(year); 28 | return date; 29 | } 30 | return new Date(Date.UTC(year, month, day, hours, minutes, seconds, milliseconds)); 31 | } 32 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import {readFileSync} from "fs"; 2 | import {terser} from "rollup-plugin-terser"; 3 | import * as meta from "./package.json"; 4 | 5 | // Extract copyrights from the LICENSE. 6 | const copyright = readFileSync("./LICENSE", "utf-8") 7 | .split(/\n/g) 8 | .filter(line => /^Copyright\s+/.test(line)) 9 | .map(line => line.replace(/^Copyright\s+/, "")) 10 | .join(", "); 11 | 12 | const config = { 13 | input: "src/index.js", 14 | external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)), 15 | output: { 16 | file: `dist/${meta.name}.js`, 17 | name: "d3", 18 | format: "umd", 19 | indent: false, 20 | extend: true, 21 | banner: `// ${meta.homepage} v${meta.version} Copyright ${copyright}`, 22 | globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"}))) 23 | }, 24 | plugins: [] 25 | }; 26 | 27 | export default [ 28 | config, 29 | { 30 | ...config, 31 | output: { 32 | ...config.output, 33 | file: `dist/${meta.name}.min.js` 34 | }, 35 | plugins: [ 36 | ...config.plugins, 37 | terser({ 38 | output: { 39 | preamble: config.output.banner 40 | } 41 | }) 42 | ] 43 | } 44 | ]; 45 | -------------------------------------------------------------------------------- /src/ordinal.js: -------------------------------------------------------------------------------- 1 | import {InternMap} from "d3-array"; 2 | import {initRange} from "./init.js"; 3 | 4 | export const implicit = Symbol("implicit"); 5 | 6 | export default function ordinal() { 7 | var index = new InternMap(), 8 | domain = [], 9 | range = [], 10 | unknown = implicit; 11 | 12 | function scale(d) { 13 | let i = index.get(d); 14 | if (i === undefined) { 15 | if (unknown !== implicit) return unknown; 16 | index.set(d, i = domain.push(d) - 1); 17 | } 18 | return range[i % range.length]; 19 | } 20 | 21 | scale.domain = function(_) { 22 | if (!arguments.length) return domain.slice(); 23 | domain = [], index = new InternMap(); 24 | for (const value of _) { 25 | if (index.has(value)) continue; 26 | index.set(value, domain.push(value) - 1); 27 | } 28 | return scale; 29 | }; 30 | 31 | scale.range = function(_) { 32 | return arguments.length ? (range = Array.from(_), scale) : range.slice(); 33 | }; 34 | 35 | scale.unknown = function(_) { 36 | return arguments.length ? (unknown = _, scale) : unknown; 37 | }; 38 | 39 | scale.copy = function() { 40 | return ordinal(domain, range).unknown(unknown); 41 | }; 42 | 43 | initRange.apply(scale, arguments); 44 | 45 | return scale; 46 | } 47 | -------------------------------------------------------------------------------- /src/pow.js: -------------------------------------------------------------------------------- 1 | import {linearish} from "./linear.js"; 2 | import {copy, identity, transformer} from "./continuous.js"; 3 | import {initRange} from "./init.js"; 4 | 5 | function transformPow(exponent) { 6 | return function(x) { 7 | return x < 0 ? -Math.pow(-x, exponent) : Math.pow(x, exponent); 8 | }; 9 | } 10 | 11 | function transformSqrt(x) { 12 | return x < 0 ? -Math.sqrt(-x) : Math.sqrt(x); 13 | } 14 | 15 | function transformSquare(x) { 16 | return x < 0 ? -x * x : x * x; 17 | } 18 | 19 | export function powish(transform) { 20 | var scale = transform(identity, identity), 21 | exponent = 1; 22 | 23 | function rescale() { 24 | return exponent === 1 ? transform(identity, identity) 25 | : exponent === 0.5 ? transform(transformSqrt, transformSquare) 26 | : transform(transformPow(exponent), transformPow(1 / exponent)); 27 | } 28 | 29 | scale.exponent = function(_) { 30 | return arguments.length ? (exponent = +_, rescale()) : exponent; 31 | }; 32 | 33 | return linearish(scale); 34 | } 35 | 36 | export default function pow() { 37 | var scale = powish(transformer()); 38 | 39 | scale.copy = function() { 40 | return copy(scale, pow()).exponent(scale.exponent()); 41 | }; 42 | 43 | initRange.apply(scale, arguments); 44 | 45 | return scale; 46 | } 47 | 48 | export function sqrt() { 49 | return pow.apply(null, arguments).exponent(0.5); 50 | } 51 | -------------------------------------------------------------------------------- /src/quantize.js: -------------------------------------------------------------------------------- 1 | import {bisect} from "d3-array"; 2 | import {linearish} from "./linear.js"; 3 | import {initRange} from "./init.js"; 4 | 5 | export default function quantize() { 6 | var x0 = 0, 7 | x1 = 1, 8 | n = 1, 9 | domain = [0.5], 10 | range = [0, 1], 11 | unknown; 12 | 13 | function scale(x) { 14 | return x != null && x <= x ? range[bisect(domain, x, 0, n)] : unknown; 15 | } 16 | 17 | function rescale() { 18 | var i = -1; 19 | domain = new Array(n); 20 | while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1); 21 | return scale; 22 | } 23 | 24 | scale.domain = function(_) { 25 | return arguments.length ? ([x0, x1] = _, x0 = +x0, x1 = +x1, rescale()) : [x0, x1]; 26 | }; 27 | 28 | scale.range = function(_) { 29 | return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice(); 30 | }; 31 | 32 | scale.invertExtent = function(y) { 33 | var i = range.indexOf(y); 34 | return i < 0 ? [NaN, NaN] 35 | : i < 1 ? [x0, domain[0]] 36 | : i >= n ? [domain[n - 1], x1] 37 | : [domain[i - 1], domain[i]]; 38 | }; 39 | 40 | scale.unknown = function(_) { 41 | return arguments.length ? (unknown = _, scale) : scale; 42 | }; 43 | 44 | scale.thresholds = function() { 45 | return domain.slice(); 46 | }; 47 | 48 | scale.copy = function() { 49 | return quantize() 50 | .domain([x0, x1]) 51 | .range(range) 52 | .unknown(unknown); 53 | }; 54 | 55 | return initRange.apply(linearish(scale), arguments); 56 | } 57 | -------------------------------------------------------------------------------- /src/quantile.js: -------------------------------------------------------------------------------- 1 | import {ascending, bisect, quantileSorted as threshold} from "d3-array"; 2 | import {initRange} from "./init.js"; 3 | 4 | export default function quantile() { 5 | var domain = [], 6 | range = [], 7 | thresholds = [], 8 | unknown; 9 | 10 | function rescale() { 11 | var i = 0, n = Math.max(1, range.length); 12 | thresholds = new Array(n - 1); 13 | while (++i < n) thresholds[i - 1] = threshold(domain, i / n); 14 | return scale; 15 | } 16 | 17 | function scale(x) { 18 | return x == null || isNaN(x = +x) ? unknown : range[bisect(thresholds, x)]; 19 | } 20 | 21 | scale.invertExtent = function(y) { 22 | var i = range.indexOf(y); 23 | return i < 0 ? [NaN, NaN] : [ 24 | i > 0 ? thresholds[i - 1] : domain[0], 25 | i < thresholds.length ? thresholds[i] : domain[domain.length - 1] 26 | ]; 27 | }; 28 | 29 | scale.domain = function(_) { 30 | if (!arguments.length) return domain.slice(); 31 | domain = []; 32 | for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d); 33 | domain.sort(ascending); 34 | return rescale(); 35 | }; 36 | 37 | scale.range = function(_) { 38 | return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); 39 | }; 40 | 41 | scale.unknown = function(_) { 42 | return arguments.length ? (unknown = _, scale) : unknown; 43 | }; 44 | 45 | scale.quantiles = function() { 46 | return thresholds.slice(); 47 | }; 48 | 49 | scale.copy = function() { 50 | return quantile() 51 | .domain(domain) 52 | .range(range) 53 | .unknown(unknown); 54 | }; 55 | 56 | return initRange.apply(scale, arguments); 57 | } 58 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | default as scaleBand, 3 | point as scalePoint 4 | } from "./band.js"; 5 | 6 | export { 7 | default as scaleIdentity 8 | } from "./identity.js"; 9 | 10 | export { 11 | default as scaleLinear 12 | } from "./linear.js"; 13 | 14 | export { 15 | default as scaleLog 16 | } from "./log.js"; 17 | 18 | export { 19 | default as scaleSymlog 20 | } from "./symlog.js"; 21 | 22 | export { 23 | default as scaleOrdinal, 24 | implicit as scaleImplicit 25 | } from "./ordinal.js"; 26 | 27 | export { 28 | default as scalePow, 29 | sqrt as scaleSqrt 30 | } from "./pow.js"; 31 | 32 | export { 33 | default as scaleRadial 34 | } from "./radial.js"; 35 | 36 | export { 37 | default as scaleQuantile 38 | } from "./quantile.js"; 39 | 40 | export { 41 | default as scaleQuantize 42 | } from "./quantize.js"; 43 | 44 | export { 45 | default as scaleThreshold 46 | } from "./threshold.js"; 47 | 48 | export { 49 | default as scaleTime 50 | } from "./time.js"; 51 | 52 | export { 53 | default as scaleUtc 54 | } from "./utcTime.js"; 55 | 56 | export { 57 | default as scaleSequential, 58 | sequentialLog as scaleSequentialLog, 59 | sequentialPow as scaleSequentialPow, 60 | sequentialSqrt as scaleSequentialSqrt, 61 | sequentialSymlog as scaleSequentialSymlog 62 | } from "./sequential.js"; 63 | 64 | export { 65 | default as scaleSequentialQuantile 66 | } from "./sequentialQuantile.js"; 67 | 68 | export { 69 | default as scaleDiverging, 70 | divergingLog as scaleDivergingLog, 71 | divergingPow as scaleDivergingPow, 72 | divergingSqrt as scaleDivergingSqrt, 73 | divergingSymlog as scaleDivergingSymlog 74 | } from "./diverging.js"; 75 | 76 | export { 77 | default as tickFormat 78 | } from "./tickFormat.js"; 79 | -------------------------------------------------------------------------------- /src/radial.js: -------------------------------------------------------------------------------- 1 | import continuous from "./continuous.js"; 2 | import {initRange} from "./init.js"; 3 | import {linearish} from "./linear.js"; 4 | import number from "./number.js"; 5 | 6 | function square(x) { 7 | return Math.sign(x) * x * x; 8 | } 9 | 10 | function unsquare(x) { 11 | return Math.sign(x) * Math.sqrt(Math.abs(x)); 12 | } 13 | 14 | export default function radial() { 15 | var squared = continuous(), 16 | range = [0, 1], 17 | round = false, 18 | unknown; 19 | 20 | function scale(x) { 21 | var y = unsquare(squared(x)); 22 | return isNaN(y) ? unknown : round ? Math.round(y) : y; 23 | } 24 | 25 | scale.invert = function(y) { 26 | return squared.invert(square(y)); 27 | }; 28 | 29 | scale.domain = function(_) { 30 | return arguments.length ? (squared.domain(_), scale) : squared.domain(); 31 | }; 32 | 33 | scale.range = function(_) { 34 | return arguments.length ? (squared.range((range = Array.from(_, number)).map(square)), scale) : range.slice(); 35 | }; 36 | 37 | scale.rangeRound = function(_) { 38 | return scale.range(_).round(true); 39 | }; 40 | 41 | scale.round = function(_) { 42 | return arguments.length ? (round = !!_, scale) : round; 43 | }; 44 | 45 | scale.clamp = function(_) { 46 | return arguments.length ? (squared.clamp(_), scale) : squared.clamp(); 47 | }; 48 | 49 | scale.unknown = function(_) { 50 | return arguments.length ? (unknown = _, scale) : unknown; 51 | }; 52 | 53 | scale.copy = function() { 54 | return radial(squared.domain(), range) 55 | .round(round) 56 | .clamp(squared.clamp()) 57 | .unknown(unknown); 58 | }; 59 | 60 | initRange.apply(scale, arguments); 61 | 62 | return linearish(scale); 63 | } 64 | -------------------------------------------------------------------------------- /src/linear.js: -------------------------------------------------------------------------------- 1 | import {ticks, tickIncrement} from "d3-array"; 2 | import continuous, {copy} from "./continuous.js"; 3 | import {initRange} from "./init.js"; 4 | import tickFormat from "./tickFormat.js"; 5 | 6 | export function linearish(scale) { 7 | var domain = scale.domain; 8 | 9 | scale.ticks = function(count) { 10 | var d = domain(); 11 | return ticks(d[0], d[d.length - 1], count == null ? 10 : count); 12 | }; 13 | 14 | scale.tickFormat = function(count, specifier) { 15 | var d = domain(); 16 | return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier); 17 | }; 18 | 19 | scale.nice = function(count) { 20 | if (count == null) count = 10; 21 | 22 | var d = domain(); 23 | var i0 = 0; 24 | var i1 = d.length - 1; 25 | var start = d[i0]; 26 | var stop = d[i1]; 27 | var prestep; 28 | var step; 29 | var maxIter = 10; 30 | 31 | if (stop < start) { 32 | step = start, start = stop, stop = step; 33 | step = i0, i0 = i1, i1 = step; 34 | } 35 | 36 | while (maxIter-- > 0) { 37 | step = tickIncrement(start, stop, count); 38 | if (step === prestep) { 39 | d[i0] = start 40 | d[i1] = stop 41 | return domain(d); 42 | } else if (step > 0) { 43 | start = Math.floor(start / step) * step; 44 | stop = Math.ceil(stop / step) * step; 45 | } else if (step < 0) { 46 | start = Math.ceil(start * step) / step; 47 | stop = Math.floor(stop * step) / step; 48 | } else { 49 | break; 50 | } 51 | prestep = step; 52 | } 53 | 54 | return scale; 55 | }; 56 | 57 | return scale; 58 | } 59 | 60 | export default function linear() { 61 | var scale = continuous(); 62 | 63 | scale.copy = function() { 64 | return copy(scale, linear()); 65 | }; 66 | 67 | initRange.apply(scale, arguments); 68 | 69 | return linearish(scale); 70 | } 71 | -------------------------------------------------------------------------------- /test/point-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleBand, scalePoint} from "../src/index.js"; 3 | 4 | it("scalePoint() has the expected defaults", () => { 5 | const s = scalePoint(); 6 | assert.deepStrictEqual(s.domain(), []); 7 | assert.deepStrictEqual(s.range(), [0, 1]); 8 | assert.strictEqual(s.bandwidth(), 0); 9 | assert.strictEqual(s.step(), 1); 10 | assert.strictEqual(s.round(), false); 11 | assert.strictEqual(s.padding(), 0); 12 | assert.strictEqual(s.align(), 0.5); 13 | }); 14 | 15 | it("scalePoint() does not expose paddingInner and paddingOuter", () => { 16 | const s = scalePoint(); 17 | assert.strictEqual(s.paddingInner, undefined); 18 | assert.strictEqual(s.paddingOuter, undefined); 19 | }); 20 | 21 | it("scalePoint() is similar to scaleBand().paddingInner(1)", () => { 22 | const p = scalePoint().domain(["foo", "bar"]).range([0, 960]); 23 | const b = scaleBand().domain(["foo", "bar"]).range([0, 960]).paddingInner(1); 24 | assert.deepStrictEqual(p.domain().map(p), b.domain().map(b)); 25 | assert.strictEqual(p.bandwidth(), b.bandwidth()); 26 | assert.strictEqual(p.step(), b.step()); 27 | }); 28 | 29 | it("point.padding(p) sets the band outer padding to p", () => { 30 | const p = scalePoint().domain(["foo", "bar"]).range([0, 960]).padding(0.5); 31 | const b = scaleBand().domain(["foo", "bar"]).range([0, 960]).paddingInner(1).paddingOuter(0.5); 32 | assert.deepStrictEqual(p.domain().map(p), b.domain().map(b)); 33 | assert.strictEqual(p.bandwidth(), b.bandwidth()); 34 | assert.strictEqual(p.step(), b.step()); 35 | }); 36 | 37 | it("point.copy() returns a copy", () => { 38 | const s = scalePoint(); 39 | assert.deepStrictEqual(s.domain(), []); 40 | assert.deepStrictEqual(s.range(), [0, 1]); 41 | assert.strictEqual(s.bandwidth(), 0); 42 | assert.strictEqual(s.step(), 1); 43 | assert.strictEqual(s.round(), false); 44 | assert.strictEqual(s.padding(), 0); 45 | assert.strictEqual(s.align(), 0.5); 46 | }); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-scale", 3 | "version": "4.0.2", 4 | "description": "Encodings that map abstract data to visual representation.", 5 | "homepage": "https://d3js.org/d3-scale/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/d3/d3-scale.git" 9 | }, 10 | "keywords": [ 11 | "d3", 12 | "d3-module", 13 | "scale", 14 | "visualization" 15 | ], 16 | "license": "ISC", 17 | "author": { 18 | "name": "Mike Bostock", 19 | "url": "https://bost.ocks.org/mike" 20 | }, 21 | "type": "module", 22 | "files": [ 23 | "dist/**/*.js", 24 | "src/**/*.js" 25 | ], 26 | "module": "src/index.js", 27 | "main": "src/index.js", 28 | "jsdelivr": "dist/d3-scale.min.js", 29 | "unpkg": "dist/d3-scale.min.js", 30 | "exports": { 31 | "umd": "./dist/d3-scale.min.js", 32 | "default": "./src/index.js" 33 | }, 34 | "sideEffects": false, 35 | "dependencies": { 36 | "d3-array": "2.10.0 - 3", 37 | "d3-format": "1 - 3", 38 | "d3-interpolate": "1.2.0 - 3", 39 | "d3-time": "2.1.1 - 3", 40 | "d3-time-format": "2 - 4" 41 | }, 42 | "devDependencies": { 43 | "d3-color": "1 - 3", 44 | "eslint": "7", 45 | "mocha": "9", 46 | "rollup": "2", 47 | "rollup-plugin-terser": "7" 48 | }, 49 | "scripts": { 50 | "test": "TZ=America/Los_Angeles mocha 'test/**/*-test.js' && eslint src test", 51 | "prepublishOnly": "rm -rf dist && yarn test && rollup -c", 52 | "postpublish": "git push && git push --tags && cd ../d3.github.com && git pull && cp ../${npm_package_name}/dist/${npm_package_name}.js ${npm_package_name}.v${npm_package_version%%.*}.js && cp ../${npm_package_name}/dist/${npm_package_name}.min.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git add ${npm_package_name}.v${npm_package_version%%.*}.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git commit -m \"${npm_package_name} ${npm_package_version}\" && git push && cd -" 53 | }, 54 | "engines": { 55 | "node": ">=12" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/tickFormat-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {tickFormat} from "../src/index.js"; 3 | 4 | it("tickFormat(start, stop, count) returns a format suitable for the ticks", () => { 5 | assert.strictEqual(tickFormat(0, 1, 10)(0.2), "0.2"); 6 | assert.strictEqual(tickFormat(0, 1, 20)(0.2), "0.20"); 7 | assert.strictEqual(tickFormat(-100, 100, 10)(-20), "−20"); 8 | }); 9 | 10 | it("tickFormat(start, stop, count, specifier) sets the appropriate fixed precision if not specified", () => { 11 | assert.strictEqual(tickFormat(0, 1, 10, "+f")(0.2), "+0.2"); 12 | assert.strictEqual(tickFormat(0, 1, 20, "+f")(0.2), "+0.20"); 13 | assert.strictEqual(tickFormat(0, 1, 10, "+%")(0.2), "+20%"); 14 | assert.strictEqual(tickFormat(0.19, 0.21, 10, "+%")(0.2), "+20.0%"); 15 | }); 16 | 17 | it("tickFormat(start, stop, count, specifier) sets the appropriate round precision if not specified", () => { 18 | assert.strictEqual(tickFormat(0, 9, 10, "")(2.10), "2"); 19 | assert.strictEqual(tickFormat(0, 9, 100, "")(2.01), "2"); 20 | assert.strictEqual(tickFormat(0, 9, 100, "")(2.11), "2.1"); 21 | assert.strictEqual(tickFormat(0, 9, 10, "e")(2.10), "2e+0"); 22 | assert.strictEqual(tickFormat(0, 9, 100, "e")(2.01), "2.0e+0"); 23 | assert.strictEqual(tickFormat(0, 9, 100, "e")(2.11), "2.1e+0"); 24 | assert.strictEqual(tickFormat(0, 9, 10, "g")(2.10), "2"); 25 | assert.strictEqual(tickFormat(0, 9, 100, "g")(2.01), "2.0"); 26 | assert.strictEqual(tickFormat(0, 9, 100, "g")(2.11), "2.1"); 27 | assert.strictEqual(tickFormat(0, 9, 10, "r")(2.10e6), "2000000"); 28 | assert.strictEqual(tickFormat(0, 9, 100, "r")(2.01e6), "2000000"); 29 | assert.strictEqual(tickFormat(0, 9, 100, "r")(2.11e6), "2100000"); 30 | assert.strictEqual(tickFormat(0, 0.9, 10, "p")(0.210), "20%"); 31 | assert.strictEqual(tickFormat(0.19, 0.21, 10, "p")(0.201), "20.1%"); 32 | }); 33 | 34 | it("tickFormat(start, stop, count, specifier) sets the appropriate prefix precision if not specified", () => { 35 | assert.strictEqual(tickFormat(0, 1e6, 10, "$s")(0.51e6), "$0.5M"); 36 | assert.strictEqual(tickFormat(0, 1e6, 100, "$s")(0.501e6), "$0.50M"); 37 | }); 38 | 39 | it("tickFormat(start, stop, count) uses the default precision when the domain is invalid", () => { 40 | const f = tickFormat(0, NaN, 10); 41 | assert.strictEqual(f + "", " >-,f"); 42 | assert.strictEqual(f(0.12), "0.120000"); 43 | }); 44 | -------------------------------------------------------------------------------- /src/time.js: -------------------------------------------------------------------------------- 1 | import {timeYear, timeMonth, timeWeek, timeDay, timeHour, timeMinute, timeSecond, timeTicks, timeTickInterval} from "d3-time"; 2 | import {timeFormat} from "d3-time-format"; 3 | import continuous, {copy} from "./continuous.js"; 4 | import {initRange} from "./init.js"; 5 | import nice from "./nice.js"; 6 | 7 | function date(t) { 8 | return new Date(t); 9 | } 10 | 11 | function number(t) { 12 | return t instanceof Date ? +t : +new Date(+t); 13 | } 14 | 15 | export function calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format) { 16 | var scale = continuous(), 17 | invert = scale.invert, 18 | domain = scale.domain; 19 | 20 | var formatMillisecond = format(".%L"), 21 | formatSecond = format(":%S"), 22 | formatMinute = format("%I:%M"), 23 | formatHour = format("%I %p"), 24 | formatDay = format("%a %d"), 25 | formatWeek = format("%b %d"), 26 | formatMonth = format("%B"), 27 | formatYear = format("%Y"); 28 | 29 | function tickFormat(date) { 30 | return (second(date) < date ? formatMillisecond 31 | : minute(date) < date ? formatSecond 32 | : hour(date) < date ? formatMinute 33 | : day(date) < date ? formatHour 34 | : month(date) < date ? (week(date) < date ? formatDay : formatWeek) 35 | : year(date) < date ? formatMonth 36 | : formatYear)(date); 37 | } 38 | 39 | scale.invert = function(y) { 40 | return new Date(invert(y)); 41 | }; 42 | 43 | scale.domain = function(_) { 44 | return arguments.length ? domain(Array.from(_, number)) : domain().map(date); 45 | }; 46 | 47 | scale.ticks = function(interval) { 48 | var d = domain(); 49 | return ticks(d[0], d[d.length - 1], interval == null ? 10 : interval); 50 | }; 51 | 52 | scale.tickFormat = function(count, specifier) { 53 | return specifier == null ? tickFormat : format(specifier); 54 | }; 55 | 56 | scale.nice = function(interval) { 57 | var d = domain(); 58 | if (!interval || typeof interval.range !== "function") interval = tickInterval(d[0], d[d.length - 1], interval == null ? 10 : interval); 59 | return interval ? domain(nice(d, interval)) : scale; 60 | }; 61 | 62 | scale.copy = function() { 63 | return copy(scale, calendar(ticks, tickInterval, year, month, week, day, hour, minute, second, format)); 64 | }; 65 | 66 | return scale; 67 | } 68 | 69 | export default function time() { 70 | return initRange.apply(calendar(timeTicks, timeTickInterval, timeYear, timeMonth, timeWeek, timeDay, timeHour, timeMinute, timeSecond, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); 71 | } 72 | -------------------------------------------------------------------------------- /test/threshold-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleThreshold} from "../src/index.js"; 3 | 4 | it("scaleThreshold() has the expected defaults", () => { 5 | const x = scaleThreshold(); 6 | assert.deepStrictEqual(x.domain(), [0.5]); 7 | assert.deepStrictEqual(x.range(), [0, 1]); 8 | assert.strictEqual(x(0.50), 1); 9 | assert.strictEqual(x(0.49), 0); 10 | }); 11 | 12 | it("threshold(x) maps a number to a discrete value in the range", () => { 13 | const x = scaleThreshold().domain([1/3, 2/3]).range(["a", "b", "c"]); 14 | assert.strictEqual(x(0), "a"); 15 | assert.strictEqual(x(0.2), "a"); 16 | assert.strictEqual(x(0.4), "b"); 17 | assert.strictEqual(x(0.6), "b"); 18 | assert.strictEqual(x(0.8), "c"); 19 | assert.strictEqual(x(1), "c"); 20 | }); 21 | 22 | it("threshold(x) returns undefined if the specified value x is not orderable", () => { 23 | const x = scaleThreshold().domain([1/3, 2/3]).range(["a", "b", "c"]); 24 | assert.strictEqual(x(), undefined); 25 | assert.strictEqual(x(undefined), undefined); 26 | assert.strictEqual(x(NaN), undefined); 27 | assert.strictEqual(x(null), undefined); 28 | }); 29 | 30 | it("threshold.domain(…) supports arbitrary orderable values", () => { 31 | const x = scaleThreshold().domain(["10", "2"]).range([0, 1, 2]); 32 | assert.strictEqual(x.domain()[0], "10"); 33 | assert.strictEqual(x.domain()[1], "2"); 34 | assert.strictEqual(x("0"), 0); 35 | assert.strictEqual(x("12"), 1); 36 | assert.strictEqual(x("3"), 2); 37 | }); 38 | 39 | it("threshold.domain(…) accepts an iterable", () => { 40 | const x = scaleThreshold().domain(new Set(["10", "2"])).range([0, 1, 2]); 41 | assert.deepStrictEqual(x.domain(), ["10", "2"]); 42 | }); 43 | 44 | it("threshold.range(…) supports arbitrary values", () => { 45 | const a = {}, b = {}, c = {}, x = scaleThreshold().domain([1/3, 2/3]).range([a, b, c]); 46 | assert.strictEqual(x(0), a); 47 | assert.strictEqual(x(0.2), a); 48 | assert.strictEqual(x(0.4), b); 49 | assert.strictEqual(x(0.6), b); 50 | assert.strictEqual(x(0.8), c); 51 | assert.strictEqual(x(1), c); 52 | }); 53 | 54 | it("threshold.range(…) accepts an iterable", () => { 55 | const x = scaleThreshold().domain(["10", "2"]).range(new Set([0, 1, 2])); 56 | assert.deepStrictEqual(x.range(), [0, 1, 2]); 57 | }); 58 | 59 | it("threshold.invertExtent(y) returns the domain extent for the specified range value", () => { 60 | const a = {}, b = {}, c = {}, x = scaleThreshold().domain([1/3, 2/3]).range([a, b, c]); 61 | assert.deepStrictEqual(x.invertExtent(a), [undefined, 1/3]); 62 | assert.deepStrictEqual(x.invertExtent(b), [1/3, 2/3]); 63 | assert.deepStrictEqual(x.invertExtent(c), [2/3, undefined]); 64 | assert.deepStrictEqual(x.invertExtent({}), [undefined, undefined]); 65 | }); 66 | -------------------------------------------------------------------------------- /src/band.js: -------------------------------------------------------------------------------- 1 | import {range as sequence} from "d3-array"; 2 | import {initRange} from "./init.js"; 3 | import ordinal from "./ordinal.js"; 4 | 5 | export default function band() { 6 | var scale = ordinal().unknown(undefined), 7 | domain = scale.domain, 8 | ordinalRange = scale.range, 9 | r0 = 0, 10 | r1 = 1, 11 | step, 12 | bandwidth, 13 | round = false, 14 | paddingInner = 0, 15 | paddingOuter = 0, 16 | align = 0.5; 17 | 18 | delete scale.unknown; 19 | 20 | function rescale() { 21 | var n = domain().length, 22 | reverse = r1 < r0, 23 | start = reverse ? r1 : r0, 24 | stop = reverse ? r0 : r1; 25 | step = (stop - start) / Math.max(1, n - paddingInner + paddingOuter * 2); 26 | if (round) step = Math.floor(step); 27 | start += (stop - start - step * (n - paddingInner)) * align; 28 | bandwidth = step * (1 - paddingInner); 29 | if (round) start = Math.round(start), bandwidth = Math.round(bandwidth); 30 | var values = sequence(n).map(function(i) { return start + step * i; }); 31 | return ordinalRange(reverse ? values.reverse() : values); 32 | } 33 | 34 | scale.domain = function(_) { 35 | return arguments.length ? (domain(_), rescale()) : domain(); 36 | }; 37 | 38 | scale.range = function(_) { 39 | return arguments.length ? ([r0, r1] = _, r0 = +r0, r1 = +r1, rescale()) : [r0, r1]; 40 | }; 41 | 42 | scale.rangeRound = function(_) { 43 | return [r0, r1] = _, r0 = +r0, r1 = +r1, round = true, rescale(); 44 | }; 45 | 46 | scale.bandwidth = function() { 47 | return bandwidth; 48 | }; 49 | 50 | scale.step = function() { 51 | return step; 52 | }; 53 | 54 | scale.round = function(_) { 55 | return arguments.length ? (round = !!_, rescale()) : round; 56 | }; 57 | 58 | scale.padding = function(_) { 59 | return arguments.length ? (paddingInner = Math.min(1, paddingOuter = +_), rescale()) : paddingInner; 60 | }; 61 | 62 | scale.paddingInner = function(_) { 63 | return arguments.length ? (paddingInner = Math.min(1, _), rescale()) : paddingInner; 64 | }; 65 | 66 | scale.paddingOuter = function(_) { 67 | return arguments.length ? (paddingOuter = +_, rescale()) : paddingOuter; 68 | }; 69 | 70 | scale.align = function(_) { 71 | return arguments.length ? (align = Math.max(0, Math.min(1, _)), rescale()) : align; 72 | }; 73 | 74 | scale.copy = function() { 75 | return band(domain(), [r0, r1]) 76 | .round(round) 77 | .paddingInner(paddingInner) 78 | .paddingOuter(paddingOuter) 79 | .align(align); 80 | }; 81 | 82 | return initRange.apply(rescale(), arguments); 83 | } 84 | 85 | function pointish(scale) { 86 | var copy = scale.copy; 87 | 88 | scale.padding = scale.paddingOuter; 89 | delete scale.paddingInner; 90 | delete scale.paddingOuter; 91 | 92 | scale.copy = function() { 93 | return pointish(copy()); 94 | }; 95 | 96 | return scale; 97 | } 98 | 99 | export function point() { 100 | return pointish(band.apply(null, arguments).paddingInner(1)); 101 | } 102 | -------------------------------------------------------------------------------- /src/sequential.js: -------------------------------------------------------------------------------- 1 | import {interpolate, interpolateRound} from "d3-interpolate"; 2 | import {identity} from "./continuous.js"; 3 | import {initInterpolator} from "./init.js"; 4 | import {linearish} from "./linear.js"; 5 | import {loggish} from "./log.js"; 6 | import {symlogish} from "./symlog.js"; 7 | import {powish} from "./pow.js"; 8 | 9 | function transformer() { 10 | var x0 = 0, 11 | x1 = 1, 12 | t0, 13 | t1, 14 | k10, 15 | transform, 16 | interpolator = identity, 17 | clamp = false, 18 | unknown; 19 | 20 | function scale(x) { 21 | return x == null || isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, clamp ? Math.max(0, Math.min(1, x)) : x)); 22 | } 23 | 24 | scale.domain = function(_) { 25 | return arguments.length ? ([x0, x1] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1]; 26 | }; 27 | 28 | scale.clamp = function(_) { 29 | return arguments.length ? (clamp = !!_, scale) : clamp; 30 | }; 31 | 32 | scale.interpolator = function(_) { 33 | return arguments.length ? (interpolator = _, scale) : interpolator; 34 | }; 35 | 36 | function range(interpolate) { 37 | return function(_) { 38 | var r0, r1; 39 | return arguments.length ? ([r0, r1] = _, interpolator = interpolate(r0, r1), scale) : [interpolator(0), interpolator(1)]; 40 | }; 41 | } 42 | 43 | scale.range = range(interpolate); 44 | 45 | scale.rangeRound = range(interpolateRound); 46 | 47 | scale.unknown = function(_) { 48 | return arguments.length ? (unknown = _, scale) : unknown; 49 | }; 50 | 51 | return function(t) { 52 | transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0); 53 | return scale; 54 | }; 55 | } 56 | 57 | export function copy(source, target) { 58 | return target 59 | .domain(source.domain()) 60 | .interpolator(source.interpolator()) 61 | .clamp(source.clamp()) 62 | .unknown(source.unknown()); 63 | } 64 | 65 | export default function sequential() { 66 | var scale = linearish(transformer()(identity)); 67 | 68 | scale.copy = function() { 69 | return copy(scale, sequential()); 70 | }; 71 | 72 | return initInterpolator.apply(scale, arguments); 73 | } 74 | 75 | export function sequentialLog() { 76 | var scale = loggish(transformer()).domain([1, 10]); 77 | 78 | scale.copy = function() { 79 | return copy(scale, sequentialLog()).base(scale.base()); 80 | }; 81 | 82 | return initInterpolator.apply(scale, arguments); 83 | } 84 | 85 | export function sequentialSymlog() { 86 | var scale = symlogish(transformer()); 87 | 88 | scale.copy = function() { 89 | return copy(scale, sequentialSymlog()).constant(scale.constant()); 90 | }; 91 | 92 | return initInterpolator.apply(scale, arguments); 93 | } 94 | 95 | export function sequentialPow() { 96 | var scale = powish(transformer()); 97 | 98 | scale.copy = function() { 99 | return copy(scale, sequentialPow()).exponent(scale.exponent()); 100 | }; 101 | 102 | return initInterpolator.apply(scale, arguments); 103 | } 104 | 105 | export function sequentialSqrt() { 106 | return sequentialPow.apply(null, arguments).exponent(0.5); 107 | } 108 | -------------------------------------------------------------------------------- /src/diverging.js: -------------------------------------------------------------------------------- 1 | import {interpolate, interpolateRound, piecewise} from "d3-interpolate"; 2 | import {identity} from "./continuous.js"; 3 | import {initInterpolator} from "./init.js"; 4 | import {linearish} from "./linear.js"; 5 | import {loggish} from "./log.js"; 6 | import {copy} from "./sequential.js"; 7 | import {symlogish} from "./symlog.js"; 8 | import {powish} from "./pow.js"; 9 | 10 | function transformer() { 11 | var x0 = 0, 12 | x1 = 0.5, 13 | x2 = 1, 14 | s = 1, 15 | t0, 16 | t1, 17 | t2, 18 | k10, 19 | k21, 20 | interpolator = identity, 21 | transform, 22 | clamp = false, 23 | unknown; 24 | 25 | function scale(x) { 26 | return isNaN(x = +x) ? unknown : (x = 0.5 + ((x = +transform(x)) - t1) * (s * x < s * t1 ? k10 : k21), interpolator(clamp ? Math.max(0, Math.min(1, x)) : x)); 27 | } 28 | 29 | scale.domain = function(_) { 30 | return arguments.length ? ([x0, x1, x2] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), t2 = transform(x2 = +x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1, scale) : [x0, x1, x2]; 31 | }; 32 | 33 | scale.clamp = function(_) { 34 | return arguments.length ? (clamp = !!_, scale) : clamp; 35 | }; 36 | 37 | scale.interpolator = function(_) { 38 | return arguments.length ? (interpolator = _, scale) : interpolator; 39 | }; 40 | 41 | function range(interpolate) { 42 | return function(_) { 43 | var r0, r1, r2; 44 | return arguments.length ? ([r0, r1, r2] = _, interpolator = piecewise(interpolate, [r0, r1, r2]), scale) : [interpolator(0), interpolator(0.5), interpolator(1)]; 45 | }; 46 | } 47 | 48 | scale.range = range(interpolate); 49 | 50 | scale.rangeRound = range(interpolateRound); 51 | 52 | scale.unknown = function(_) { 53 | return arguments.length ? (unknown = _, scale) : unknown; 54 | }; 55 | 56 | return function(t) { 57 | transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1; 58 | return scale; 59 | }; 60 | } 61 | 62 | export default function diverging() { 63 | var scale = linearish(transformer()(identity)); 64 | 65 | scale.copy = function() { 66 | return copy(scale, diverging()); 67 | }; 68 | 69 | return initInterpolator.apply(scale, arguments); 70 | } 71 | 72 | export function divergingLog() { 73 | var scale = loggish(transformer()).domain([0.1, 1, 10]); 74 | 75 | scale.copy = function() { 76 | return copy(scale, divergingLog()).base(scale.base()); 77 | }; 78 | 79 | return initInterpolator.apply(scale, arguments); 80 | } 81 | 82 | export function divergingSymlog() { 83 | var scale = symlogish(transformer()); 84 | 85 | scale.copy = function() { 86 | return copy(scale, divergingSymlog()).constant(scale.constant()); 87 | }; 88 | 89 | return initInterpolator.apply(scale, arguments); 90 | } 91 | 92 | export function divergingPow() { 93 | var scale = powish(transformer()); 94 | 95 | scale.copy = function() { 96 | return copy(scale, divergingPow()).exponent(scale.exponent()); 97 | }; 98 | 99 | return initInterpolator.apply(scale, arguments); 100 | } 101 | 102 | export function divergingSqrt() { 103 | return divergingPow.apply(null, arguments).exponent(0.5); 104 | } 105 | -------------------------------------------------------------------------------- /test/radial-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleRadial} from "../src/index.js"; 3 | 4 | it("scaleRadial() has the expected defaults", () => { 5 | const s = scaleRadial(); 6 | assert.deepStrictEqual(s.domain(), [0, 1]); 7 | assert.deepStrictEqual(s.range(), [0, 1]); 8 | assert.strictEqual(s.clamp(), false); 9 | assert.strictEqual(s.round(), false); 10 | }); 11 | 12 | it("scaleRadial(range) sets the range", () => { 13 | const s = scaleRadial([100, 200]); 14 | assert.deepStrictEqual(s.domain(), [0, 1]); 15 | assert.deepStrictEqual(s.range(), [100, 200]); 16 | assert.strictEqual(s(0.5), 158.11388300841898); 17 | }); 18 | 19 | it("scaleRadial(domain, range) sets the range", () => { 20 | const s = scaleRadial([1, 2], [10, 20]); 21 | assert.deepStrictEqual(s.domain(), [1, 2]); 22 | assert.deepStrictEqual(s.range(), [10, 20]); 23 | assert.strictEqual(s(1.5), 15.811388300841896); 24 | }); 25 | 26 | it("radial(x) maps a domain value x to a range value y", () => { 27 | assert.strictEqual(scaleRadial([1, 2])(0.5), 1.5811388300841898); 28 | }); 29 | 30 | it("radial(x) ignores extra range values if the domain is smaller than the range", () => { 31 | assert.strictEqual(scaleRadial().domain([-10, 0]).range([2, 3, 4]).clamp(true)(-5), 2.5495097567963922); 32 | assert.strictEqual(scaleRadial().domain([-10, 0]).range([2, 3, 4]).clamp(true)(50), 3); 33 | }); 34 | 35 | it("radial(x) ignores extra domain values if the range is smaller than the domain", () => { 36 | assert.strictEqual(scaleRadial().domain([-10, 0, 100]).range([2, 3]).clamp(true)(-5), 2.5495097567963922); 37 | assert.strictEqual(scaleRadial().domain([-10, 0, 100]).range([2, 3]).clamp(true)(50), 3); 38 | }); 39 | 40 | it("radial(x) maps an empty domain to the middle of the range", () => { 41 | assert.strictEqual(scaleRadial().domain([0, 0]).range([1, 2])(0), 1.5811388300841898); 42 | assert.strictEqual(scaleRadial().domain([0, 0]).range([2, 1])(1), 1.5811388300841898); 43 | }); 44 | 45 | it("radial(x) can map a bilinear domain with two values to the corresponding range", () => { 46 | const s = scaleRadial().domain([1, 2]); 47 | assert.deepStrictEqual(s.domain(), [1, 2]); 48 | assert.strictEqual(s(0.5), -0.7071067811865476); 49 | assert.strictEqual(s(1.0), 0.0); 50 | assert.strictEqual(s(1.5), 0.7071067811865476); 51 | assert.strictEqual(s(2.0), 1.0); 52 | assert.strictEqual(s(2.5), 1.224744871391589); 53 | assert.strictEqual(s.invert(-0.5), 0.75); 54 | assert.strictEqual(s.invert( 0.0), 1.0); 55 | assert.strictEqual(s.invert( 0.5), 1.25); 56 | assert.strictEqual(s.invert( 1.0), 2.0); 57 | assert.strictEqual(s.invert( 1.5), 3.25); 58 | }); 59 | 60 | it("radial(NaN) returns undefined", () => { 61 | const s = scaleRadial(); 62 | assert.strictEqual(s(NaN), undefined); 63 | assert.strictEqual(s(undefined), undefined); 64 | assert.strictEqual(s("foo"), undefined); 65 | assert.strictEqual(s({}), undefined); 66 | }); 67 | 68 | it("radial.unknown(unknown)(NaN) returns the specified unknown value", () => { 69 | assert.strictEqual(scaleRadial().unknown("foo")(NaN), "foo"); 70 | }); 71 | 72 | it("radial(x) can handle a negative range", () => { 73 | assert.strictEqual(scaleRadial([-1, -2])(0.5), -1.5811388300841898); 74 | }); 75 | 76 | it("radial(x) can clamp negative values", () => { 77 | assert.strictEqual(scaleRadial([-1, -2]).clamp(true)(-0.5), -1); 78 | assert.strictEqual(scaleRadial().clamp(true)(-0.5), 0); 79 | assert.strictEqual(scaleRadial([-0.25, 0], [1, 2]).clamp(true)(-0.5), 1); 80 | }); 81 | -------------------------------------------------------------------------------- /src/continuous.js: -------------------------------------------------------------------------------- 1 | import {bisect} from "d3-array"; 2 | import {interpolate as interpolateValue, interpolateNumber, interpolateRound} from "d3-interpolate"; 3 | import constant from "./constant.js"; 4 | import number from "./number.js"; 5 | 6 | var unit = [0, 1]; 7 | 8 | export function identity(x) { 9 | return x; 10 | } 11 | 12 | function normalize(a, b) { 13 | return (b -= (a = +a)) 14 | ? function(x) { return (x - a) / b; } 15 | : constant(isNaN(b) ? NaN : 0.5); 16 | } 17 | 18 | function clamper(a, b) { 19 | var t; 20 | if (a > b) t = a, a = b, b = t; 21 | return function(x) { return Math.max(a, Math.min(b, x)); }; 22 | } 23 | 24 | // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1]. 25 | // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b]. 26 | function bimap(domain, range, interpolate) { 27 | var d0 = domain[0], d1 = domain[1], r0 = range[0], r1 = range[1]; 28 | if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0); 29 | else d0 = normalize(d0, d1), r0 = interpolate(r0, r1); 30 | return function(x) { return r0(d0(x)); }; 31 | } 32 | 33 | function polymap(domain, range, interpolate) { 34 | var j = Math.min(domain.length, range.length) - 1, 35 | d = new Array(j), 36 | r = new Array(j), 37 | i = -1; 38 | 39 | // Reverse descending domains. 40 | if (domain[j] < domain[0]) { 41 | domain = domain.slice().reverse(); 42 | range = range.slice().reverse(); 43 | } 44 | 45 | while (++i < j) { 46 | d[i] = normalize(domain[i], domain[i + 1]); 47 | r[i] = interpolate(range[i], range[i + 1]); 48 | } 49 | 50 | return function(x) { 51 | var i = bisect(domain, x, 1, j) - 1; 52 | return r[i](d[i](x)); 53 | }; 54 | } 55 | 56 | export function copy(source, target) { 57 | return target 58 | .domain(source.domain()) 59 | .range(source.range()) 60 | .interpolate(source.interpolate()) 61 | .clamp(source.clamp()) 62 | .unknown(source.unknown()); 63 | } 64 | 65 | export function transformer() { 66 | var domain = unit, 67 | range = unit, 68 | interpolate = interpolateValue, 69 | transform, 70 | untransform, 71 | unknown, 72 | clamp = identity, 73 | piecewise, 74 | output, 75 | input; 76 | 77 | function rescale() { 78 | var n = Math.min(domain.length, range.length); 79 | if (clamp !== identity) clamp = clamper(domain[0], domain[n - 1]); 80 | piecewise = n > 2 ? polymap : bimap; 81 | output = input = null; 82 | return scale; 83 | } 84 | 85 | function scale(x) { 86 | return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x))); 87 | } 88 | 89 | scale.invert = function(y) { 90 | return clamp(untransform((input || (input = piecewise(range, domain.map(transform), interpolateNumber)))(y))); 91 | }; 92 | 93 | scale.domain = function(_) { 94 | return arguments.length ? (domain = Array.from(_, number), rescale()) : domain.slice(); 95 | }; 96 | 97 | scale.range = function(_) { 98 | return arguments.length ? (range = Array.from(_), rescale()) : range.slice(); 99 | }; 100 | 101 | scale.rangeRound = function(_) { 102 | return range = Array.from(_), interpolate = interpolateRound, rescale(); 103 | }; 104 | 105 | scale.clamp = function(_) { 106 | return arguments.length ? (clamp = _ ? true : identity, rescale()) : clamp !== identity; 107 | }; 108 | 109 | scale.interpolate = function(_) { 110 | return arguments.length ? (interpolate = _, rescale()) : interpolate; 111 | }; 112 | 113 | scale.unknown = function(_) { 114 | return arguments.length ? (unknown = _, scale) : unknown; 115 | }; 116 | 117 | return function(t, u) { 118 | transform = t, untransform = u; 119 | return rescale(); 120 | }; 121 | } 122 | 123 | export default function continuous() { 124 | return transformer()(identity, identity); 125 | } 126 | -------------------------------------------------------------------------------- /src/log.js: -------------------------------------------------------------------------------- 1 | import {ticks} from "d3-array"; 2 | import {format, formatSpecifier} from "d3-format"; 3 | import nice from "./nice.js"; 4 | import {copy, transformer} from "./continuous.js"; 5 | import {initRange} from "./init.js"; 6 | 7 | function transformLog(x) { 8 | return Math.log(x); 9 | } 10 | 11 | function transformExp(x) { 12 | return Math.exp(x); 13 | } 14 | 15 | function transformLogn(x) { 16 | return -Math.log(-x); 17 | } 18 | 19 | function transformExpn(x) { 20 | return -Math.exp(-x); 21 | } 22 | 23 | function pow10(x) { 24 | return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; 25 | } 26 | 27 | function powp(base) { 28 | return base === 10 ? pow10 29 | : base === Math.E ? Math.exp 30 | : x => Math.pow(base, x); 31 | } 32 | 33 | function logp(base) { 34 | return base === Math.E ? Math.log 35 | : base === 10 && Math.log10 36 | || base === 2 && Math.log2 37 | || (base = Math.log(base), x => Math.log(x) / base); 38 | } 39 | 40 | function reflect(f) { 41 | return (x, k) => -f(-x, k); 42 | } 43 | 44 | export function loggish(transform) { 45 | const scale = transform(transformLog, transformExp); 46 | const domain = scale.domain; 47 | let base = 10; 48 | let logs; 49 | let pows; 50 | 51 | function rescale() { 52 | logs = logp(base), pows = powp(base); 53 | if (domain()[0] < 0) { 54 | logs = reflect(logs), pows = reflect(pows); 55 | transform(transformLogn, transformExpn); 56 | } else { 57 | transform(transformLog, transformExp); 58 | } 59 | return scale; 60 | } 61 | 62 | scale.base = function(_) { 63 | return arguments.length ? (base = +_, rescale()) : base; 64 | }; 65 | 66 | scale.domain = function(_) { 67 | return arguments.length ? (domain(_), rescale()) : domain(); 68 | }; 69 | 70 | scale.ticks = count => { 71 | const d = domain(); 72 | let u = d[0]; 73 | let v = d[d.length - 1]; 74 | const r = v < u; 75 | 76 | if (r) ([u, v] = [v, u]); 77 | 78 | let i = logs(u); 79 | let j = logs(v); 80 | let k; 81 | let t; 82 | const n = count == null ? 10 : +count; 83 | let z = []; 84 | 85 | if (!(base % 1) && j - i < n) { 86 | i = Math.floor(i), j = Math.ceil(j); 87 | if (u > 0) for (; i <= j; ++i) { 88 | for (k = 1; k < base; ++k) { 89 | t = i < 0 ? k / pows(-i) : k * pows(i); 90 | if (t < u) continue; 91 | if (t > v) break; 92 | z.push(t); 93 | } 94 | } else for (; i <= j; ++i) { 95 | for (k = base - 1; k >= 1; --k) { 96 | t = i > 0 ? k / pows(-i) : k * pows(i); 97 | if (t < u) continue; 98 | if (t > v) break; 99 | z.push(t); 100 | } 101 | } 102 | if (z.length * 2 < n) z = ticks(u, v, n); 103 | } else { 104 | z = ticks(i, j, Math.min(j - i, n)).map(pows); 105 | } 106 | return r ? z.reverse() : z; 107 | }; 108 | 109 | scale.tickFormat = (count, specifier) => { 110 | if (count == null) count = 10; 111 | if (specifier == null) specifier = base === 10 ? "s" : ","; 112 | if (typeof specifier !== "function") { 113 | if (!(base % 1) && (specifier = formatSpecifier(specifier)).precision == null) specifier.trim = true; 114 | specifier = format(specifier); 115 | } 116 | if (count === Infinity) return specifier; 117 | const k = Math.max(1, base * count / scale.ticks().length); // TODO fast estimate? 118 | return d => { 119 | let i = d / pows(Math.round(logs(d))); 120 | if (i * base < base - 0.5) i *= base; 121 | return i <= k ? specifier(d) : ""; 122 | }; 123 | }; 124 | 125 | scale.nice = () => { 126 | return domain(nice(domain(), { 127 | floor: x => pows(Math.floor(logs(x))), 128 | ceil: x => pows(Math.ceil(logs(x))) 129 | })); 130 | }; 131 | 132 | return scale; 133 | } 134 | 135 | export default function log() { 136 | const scale = loggish(transformer()).domain([1, 10]); 137 | scale.copy = () => copy(scale, log()).base(scale.base()); 138 | initRange.apply(scale, arguments); 139 | return scale; 140 | } 141 | -------------------------------------------------------------------------------- /test/sequential-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleSequential} from "../src/index.js"; 3 | 4 | it("scaleSequential() has the expected defaults", () => { 5 | const s = scaleSequential(); 6 | assert.deepStrictEqual(s.domain(), [0, 1]); 7 | assert.strictEqual(s.interpolator()(0.42), 0.42); 8 | assert.strictEqual(s.clamp(), false); 9 | assert.strictEqual(s.unknown(), undefined); 10 | assert.strictEqual(s(-0.5), -0.5); 11 | assert.strictEqual(s( 0.0), 0.0); 12 | assert.strictEqual(s( 0.5), 0.5); 13 | assert.strictEqual(s( 1.0), 1.0); 14 | assert.strictEqual(s( 1.5), 1.5); 15 | }); 16 | 17 | it("sequential.clamp(true) enables clamping", () => { 18 | const s = scaleSequential().clamp(true); 19 | assert.strictEqual(s.clamp(), true); 20 | assert.strictEqual(s(-0.5), 0.0); 21 | assert.strictEqual(s( 0.0), 0.0); 22 | assert.strictEqual(s( 0.5), 0.5); 23 | assert.strictEqual(s( 1.0), 1.0); 24 | assert.strictEqual(s( 1.5), 1.0); 25 | }); 26 | 27 | it("sequential.unknown(value) sets the return value for undefined and NaN input", () => { 28 | const s = scaleSequential().unknown(-1); 29 | assert.strictEqual(s.unknown(), -1); 30 | assert.strictEqual(s(undefined), -1); 31 | assert.strictEqual(s(NaN), -1); 32 | assert.strictEqual(s("N/A"), -1); 33 | assert.strictEqual(s(0.4), 0.4); 34 | }); 35 | 36 | it("sequential.domain() coerces domain values to numbers", () => { 37 | const s = scaleSequential().domain(["-1.20", "2.40"]); 38 | assert.deepStrictEqual(s.domain(), [-1.2, 2.4]); 39 | assert.strictEqual(s(-1.2), 0.0); 40 | assert.strictEqual(s( 0.6), 0.5); 41 | assert.strictEqual(s( 2.4), 1.0); 42 | }); 43 | 44 | it("sequential.domain() accepts an iterable", () => { 45 | const s = scaleSequential().domain(new Set(["-1.20", "2.40"])); 46 | assert.deepStrictEqual(s.domain(), [-1.2, 2.4]); 47 | }); 48 | 49 | it("sequential.domain() handles a degenerate domain", () => { 50 | const s = scaleSequential().domain([2, 2]); 51 | assert.deepStrictEqual(s.domain(), [2, 2]); 52 | assert.strictEqual(s(-1.2), 0.5); 53 | assert.strictEqual(s( 0.6), 0.5); 54 | assert.strictEqual(s( 2.4), 0.5); 55 | }); 56 | 57 | it("sequential.domain() handles a non-numeric domain", () => { 58 | const s = scaleSequential().domain([NaN, 2]); 59 | assert.strictEqual(isNaN(s.domain()[0]), true); 60 | assert.strictEqual(s.domain()[1], 2); 61 | assert.strictEqual(isNaN(s(-1.2)), true); 62 | assert.strictEqual(isNaN(s( 0.6)), true); 63 | assert.strictEqual(isNaN(s( 2.4)), true); 64 | }); 65 | 66 | it("sequential.domain() only considers the first and second element of the domain", () => { 67 | const s = scaleSequential().domain([-1, 100, 200]); 68 | assert.deepStrictEqual(s.domain(), [-1, 100]); 69 | }); 70 | 71 | it("sequential.copy() returns an isolated copy of the scale", () => { 72 | const s1 = scaleSequential().domain([1, 3]).clamp(true); 73 | const s2 = s1.copy(); 74 | assert.deepStrictEqual(s2.domain(), [1, 3]); 75 | assert.strictEqual(s2.clamp(), true); 76 | s1.domain([-1, 2]); 77 | assert.deepStrictEqual(s2.domain(), [1, 3]); 78 | s1.clamp(false); 79 | assert.strictEqual(s2.clamp(), true); 80 | s2.domain([3, 4]); 81 | assert.deepStrictEqual(s1.domain(), [-1, 2]); 82 | s2.clamp(true); 83 | assert.deepStrictEqual(s1.clamp(), false); 84 | }); 85 | 86 | it("sequential.interpolator(interpolator) sets the interpolator", () => { 87 | const i0 = function(t) { return t; }; 88 | const i1 = function(t) { return t * 2; }; 89 | const s = scaleSequential(i0); 90 | assert.strictEqual(s.interpolator(), i0); 91 | assert.strictEqual(s.interpolator(i1), s); 92 | assert.strictEqual(s.interpolator(), i1); 93 | assert.strictEqual(s(-0.5), -1.0); 94 | assert.strictEqual(s( 0.0), 0.0); 95 | assert.strictEqual(s( 0.5), 1.0); 96 | }); 97 | 98 | it("sequential.range() returns the computed range", () => { 99 | const s = scaleSequential(function(t) { return t * 2 + 1; }); 100 | assert.deepStrictEqual(s.range(), [1, 3]); 101 | }); 102 | 103 | it("sequential.range(range) sets the interpolator", () => { 104 | const s = scaleSequential().range([1, 3]); 105 | assert.strictEqual(s.interpolator()(0.5), 2); 106 | assert.deepStrictEqual(s.range(), [1, 3]); 107 | }); 108 | 109 | it("sequential.range(range) ignores additional values", () => { 110 | const s = scaleSequential().range([1, 3, 10]); 111 | assert.strictEqual(s.interpolator()(0.5), 2); 112 | assert.deepStrictEqual(s.range(), [1, 3]); 113 | }); 114 | 115 | it("scaleSequential(range) sets the interpolator", () => { 116 | const s = scaleSequential([1, 3]); 117 | assert.strictEqual(s.interpolator()(0.5), 2); 118 | assert.deepStrictEqual(s.range(), [1, 3]); 119 | }); 120 | -------------------------------------------------------------------------------- /test/quantize-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {range} from "d3-array"; 3 | import {scaleQuantize} from "../src/index.js"; 4 | import {assertInDelta} from "./asserts.js"; 5 | 6 | it("scaleQuantize() has the expected defaults", () => { 7 | const s = scaleQuantize(); 8 | assert.deepStrictEqual(s.domain(), [0, 1]); 9 | assert.deepStrictEqual(s.range(), [0, 1]); 10 | assert.deepStrictEqual(s.thresholds(), [0.5]); 11 | assert.strictEqual(s(0.25), 0); 12 | assert.strictEqual(s(0.75), 1); 13 | }); 14 | 15 | it("quantize(value) maps a number to a discrete value in the range", () => { 16 | const s = scaleQuantize().range([0, 1, 2]); 17 | assert.deepStrictEqual(s.thresholds(), [1 / 3, 2 / 3]); 18 | assert.strictEqual(s(0.0), 0); 19 | assert.strictEqual(s(0.2), 0); 20 | assert.strictEqual(s(0.4), 1); 21 | assert.strictEqual(s(0.6), 1); 22 | assert.strictEqual(s(0.8), 2); 23 | assert.strictEqual(s(1.0), 2); 24 | }); 25 | 26 | it("quantize(value) clamps input values to the domain", () => { 27 | const a = {}; 28 | const b = {}; 29 | const c = {}; 30 | const s = scaleQuantize().range([a, b, c]); 31 | assert.strictEqual(s(-0.5), a); 32 | assert.strictEqual(s(+1.5), c); 33 | }); 34 | 35 | it("quantize.unknown(value) sets the return value for undefined, null, and NaN input", () => { 36 | const s = scaleQuantize().range([0, 1, 2]).unknown(-1); 37 | assert.strictEqual(s(undefined), -1); 38 | assert.strictEqual(s(null), -1); 39 | assert.strictEqual(s(NaN), -1); 40 | }); 41 | 42 | it("quantize.domain() coerces domain values to numbers", () => { 43 | const s = scaleQuantize().domain(["-1.20", "2.40"]); 44 | assert.deepStrictEqual(s.domain(), [-1.2, 2.4]); 45 | assert.strictEqual(s(-1.2), 0); 46 | assert.strictEqual(s( 0.5), 0); 47 | assert.strictEqual(s( 0.7), 1); 48 | assert.strictEqual(s( 2.4), 1); 49 | }); 50 | 51 | it("quantize.domain() accepts an iterable", () => { 52 | const s = scaleQuantize().domain(new Set([1, 2])); 53 | assert.deepStrictEqual(s.domain(), [1, 2]); 54 | }); 55 | 56 | it("quantize.domain() only considers the first and second element of the domain", () => { 57 | const s = scaleQuantize().domain([-1, 100, 200]); 58 | assert.deepStrictEqual(s.domain(), [-1, 100]); 59 | }); 60 | 61 | it("quantize.range() cardinality determines the degree of quantization", () => { 62 | const s = scaleQuantize(); 63 | assertInDelta(s.range(range(0, 1.001, 0.001))(1/3), 0.333, 1e-6); 64 | assertInDelta(s.range(range(0, 1.010, 0.010))(1/3), 0.330, 1e-6); 65 | assertInDelta(s.range(range(0, 1.100, 0.100))(1/3), 0.300, 1e-6); 66 | assertInDelta(s.range(range(0, 1.200, 0.200))(1/3), 0.400, 1e-6); 67 | assertInDelta(s.range(range(0, 1.250, 0.250))(1/3), 0.250, 1e-6); 68 | assertInDelta(s.range(range(0, 1.500, 0.500))(1/3), 0.500, 1e-6); 69 | assertInDelta(s.range(range(1))(1/3), 0, 1e-6); 70 | }); 71 | 72 | it("quantize.range() values are arbitrary", () => { 73 | const a = {}; 74 | const b = {}; 75 | const c = {}; 76 | const s = scaleQuantize().range([a, b, c]); 77 | assert.strictEqual(s(0.0), a); 78 | assert.strictEqual(s(0.2), a); 79 | assert.strictEqual(s(0.4), b); 80 | assert.strictEqual(s(0.6), b); 81 | assert.strictEqual(s(0.8), c); 82 | assert.strictEqual(s(1.0), c); 83 | }); 84 | 85 | it("quantize.invertExtent() maps a value in the range to a domain extent", () => { 86 | const s = scaleQuantize().range([0, 1, 2, 3]); 87 | assert.deepStrictEqual(s.invertExtent(0), [0.00, 0.25]); 88 | assert.deepStrictEqual(s.invertExtent(1), [0.25, 0.50]); 89 | assert.deepStrictEqual(s.invertExtent(2), [0.50, 0.75]); 90 | assert.deepStrictEqual(s.invertExtent(3), [0.75, 1.00]); 91 | }); 92 | 93 | it("quantize.invertExtent() allows arbitrary range values", () => { 94 | const a = {}; 95 | const b = {}; 96 | const s = scaleQuantize().range([a, b]); 97 | assert.deepStrictEqual(s.invertExtent(a), [0.0, 0.5]); 98 | assert.deepStrictEqual(s.invertExtent(b), [0.5, 1.0]); 99 | }); 100 | 101 | it("quantize.invertExtent() returns [NaN, NaN] when the given value is not in the range", () => { 102 | const s = scaleQuantize(); 103 | assert(s.invertExtent(-1).every(Number.isNaN)); 104 | assert(s.invertExtent(0.5).every(Number.isNaN)); 105 | assert(s.invertExtent(2).every(Number.isNaN)); 106 | assert(s.invertExtent("a").every(Number.isNaN)); 107 | }); 108 | 109 | it("quantize.invertExtent() returns the first match if duplicate values exist in the range", () => { 110 | const s = scaleQuantize().range([0, 1, 2, 0]); 111 | assert.deepStrictEqual(s.invertExtent(0), [0.00, 0.25]); 112 | assert.deepStrictEqual(s.invertExtent(1), [0.25, 0.50]); 113 | }); 114 | 115 | it("quantize.invertExtent(y) is exactly consistent with quantize(x)", () => { 116 | const s = scaleQuantize().domain([4.2, 6.2]).range(range(10)); 117 | s.range().forEach(function(y) { 118 | const e = s.invertExtent(y); 119 | assert.strictEqual(s(e[0]), y); 120 | assert.strictEqual(s(e[1]), y < 9 ? y + 1 : y); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/quantile-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleQuantile} from "../src/index.js"; 3 | 4 | it("scaleQuantile() has the expected default", () => { 5 | const s = scaleQuantile(); 6 | assert.deepStrictEqual(s.domain(), []); 7 | assert.deepStrictEqual(s.range(), []); 8 | assert.strictEqual(s.unknown(), undefined); 9 | }); 10 | 11 | it("quantile(x) uses the R-7 algorithm to compute quantiles", () => { 12 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]); 13 | assert.deepStrictEqual([3, 6, 6.9, 7, 7.1].map(s), [0, 0, 0, 0, 0]); 14 | assert.deepStrictEqual([8, 8.9].map(s), [1, 1]); 15 | assert.deepStrictEqual([9, 9.1, 10, 13].map(s), [2, 2, 2, 2]); 16 | assert.deepStrictEqual([14.9, 15, 15.1, 16, 20].map(s), [3, 3, 3, 3, 3]); 17 | s.domain([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]); 18 | assert.deepStrictEqual([3, 6, 6.9, 7, 7.1].map(s), [0, 0, 0, 0, 0]); 19 | assert.deepStrictEqual([8, 8.9].map(s), [1, 1]); 20 | assert.deepStrictEqual([9, 9.1, 10, 13].map(s), [2, 2, 2, 2]); 21 | assert.deepStrictEqual([14.9, 15, 15.1, 16, 20].map(s), [3, 3, 3, 3, 3]); 22 | }); 23 | 24 | it("quantile(x) returns undefined if the input value is NaN", () => { 25 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]); 26 | assert.strictEqual(s(NaN), undefined); 27 | }); 28 | 29 | it("quantile.domain() values are sorted in ascending order", () => { 30 | const s = scaleQuantile().domain([6, 3, 7, 8, 8, 13, 20, 15, 16, 10]); 31 | assert.deepStrictEqual(s.domain(), [3, 6, 7, 8, 8, 10, 13, 15, 16, 20]); 32 | }); 33 | 34 | it("quantile.domain() values are coerced to numbers", () => { 35 | const s = scaleQuantile().domain(["6", "13", "20"]); 36 | assert.deepStrictEqual(s.domain(), [6, 13, 20]); 37 | }); 38 | 39 | it("quantile.domain() accepts an iterable", () => { 40 | const s = scaleQuantile().domain(new Set([6, 13, 20])); 41 | assert.deepStrictEqual(s.domain(), [6, 13, 20]); 42 | }); 43 | 44 | it("quantile.domain() values are allowed to be zero", () => { 45 | const s = scaleQuantile().domain([1, 2, 0, 0, null]); 46 | assert.deepStrictEqual(s.domain(), [0, 0, 1, 2]); 47 | }); 48 | 49 | it("quantile.domain() non-numeric values are ignored", () => { 50 | const s = scaleQuantile().domain([6, 3, NaN, undefined, 7, 8, 8, 13, null, 20, 15, 16, 10, NaN]); 51 | assert.deepStrictEqual(s.domain(), [3, 6, 7, 8, 8, 10, 13, 15, 16, 20]); 52 | }); 53 | 54 | it("quantile.quantiles() returns the inner thresholds", () => { 55 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]); 56 | assert.deepStrictEqual(s.quantiles(), [7.25, 9, 14.5]); 57 | s.domain([3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]); 58 | assert.deepStrictEqual(s.quantiles(), [7.5, 9, 14]); 59 | }); 60 | 61 | it("quantile.range() cardinality determines the number of quantiles", () => { 62 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]); 63 | assert.deepStrictEqual(s.range([0, 1, 2, 3]).quantiles(), [7.25, 9, 14.5]); 64 | assert.deepStrictEqual(s.range([0, 1]).quantiles(), [9]); 65 | assert.deepStrictEqual(s.range([,,,,,]).quantiles(), [6.8, 8, 11.2, 15.2]); 66 | assert.deepStrictEqual(s.range([,,,,,,]).quantiles(), [6.5, 8, 9, 13, 15.5]); 67 | }); 68 | 69 | it("quantile.range() accepts an iterable", () => { 70 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range(new Set([0, 1, 2, 3])); 71 | assert.deepStrictEqual(s.range(), [0, 1, 2, 3]); 72 | }); 73 | 74 | it("quantile.range() values are arbitrary", () => { 75 | const a = {}; 76 | const b = {}; 77 | const c = {}; 78 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([a, b, c, a]); 79 | assert.deepStrictEqual([3, 6, 6.9, 7, 7.1].map(s), [a, a, a, a, a]); 80 | assert.deepStrictEqual([8, 8.9].map(s), [b, b]); 81 | assert.deepStrictEqual([9, 9.1, 10, 13].map(s), [c, c, c, c]); 82 | assert.deepStrictEqual([14.9, 15, 15.1, 16, 20].map(s), [a, a, a, a, a]); 83 | }); 84 | 85 | it("quantile.invertExtent() maps a value in the range to a domain extent", () => { 86 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]); 87 | assert.deepStrictEqual(s.invertExtent(0), [3, 7.25]); 88 | assert.deepStrictEqual(s.invertExtent(1), [7.25, 9]); 89 | assert.deepStrictEqual(s.invertExtent(2), [9, 14.5]); 90 | assert.deepStrictEqual(s.invertExtent(3), [14.5, 20]); 91 | }); 92 | 93 | it("quantile.invertExtent() allows arbitrary range values", () => { 94 | const a = {}; 95 | const b = {}; 96 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([a, b]); 97 | assert.deepStrictEqual(s.invertExtent(a), [3, 9]); 98 | assert.deepStrictEqual(s.invertExtent(b), [9, 20]); 99 | }); 100 | 101 | it("quantile.invertExtent() returns [NaN, NaN] when the given value is not in the range", () => { 102 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]); 103 | assert(s.invertExtent(-1).every(isNaN)); 104 | assert(s.invertExtent(0.5).every(isNaN)); 105 | assert(s.invertExtent(2).every(isNaN)); 106 | assert(s.invertExtent('a').every(isNaN)); 107 | }); 108 | 109 | it("quantile.invertExtent() returns the first match if duplicate values exist in the range", () => { 110 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 0]); 111 | assert.deepStrictEqual(s.invertExtent(0), [3, 7.25]); 112 | assert.deepStrictEqual(s.invertExtent(1), [7.25, 9]); 113 | assert.deepStrictEqual(s.invertExtent(2), [9, 14.5]); 114 | }); 115 | 116 | it("quantile.unknown(value) sets the return value for undefined, null, and NaN input", () => { 117 | const s = scaleQuantile().domain([3, 6, 7, 8, 8, 10, 13, 15, 16, 20]).range([0, 1, 2, 3]).unknown(-1); 118 | assert.strictEqual(s(undefined), -1); 119 | assert.strictEqual(s(null), -1); 120 | assert.strictEqual(s(NaN), -1); 121 | assert.strictEqual(s("N/A"), -1); 122 | assert.strictEqual(s(2), 0); 123 | assert.strictEqual(s(3), 0); 124 | assert.strictEqual(s(21), 3); 125 | }); 126 | -------------------------------------------------------------------------------- /test/diverging-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleDiverging, scaleDivergingLog} from "../src/index.js"; 3 | 4 | it("scaleDiverging() has the expected defaults", () => { 5 | const s = scaleDiverging(); 6 | assert.deepStrictEqual(s.domain(), [0, 0.5, 1]); 7 | assert.strictEqual(s.interpolator()(0.42), 0.42); 8 | assert.strictEqual(s.clamp(), false); 9 | assert.strictEqual(s(-0.5), -0.5); 10 | assert.strictEqual(s( 0.0), 0.0); 11 | assert.strictEqual(s( 0.5), 0.5); 12 | assert.strictEqual(s( 1.0), 1.0); 13 | assert.strictEqual(s( 1.5), 1.5); 14 | }); 15 | 16 | it("diverging.clamp(true) enables clamping", () => { 17 | const s = scaleDiverging().clamp(true); 18 | assert.strictEqual(s.clamp(), true); 19 | assert.strictEqual(s(-0.5), 0.0); 20 | assert.strictEqual(s( 0.0), 0.0); 21 | assert.strictEqual(s( 0.5), 0.5); 22 | assert.strictEqual(s( 1.0), 1.0); 23 | assert.strictEqual(s( 1.5), 1.0); 24 | }); 25 | 26 | it("diverging.domain() coerces domain values to numbers", () => { 27 | const s = scaleDiverging().domain(["-1.20", " 0", "2.40"]); 28 | assert.deepStrictEqual(s.domain(), [-1.2, 0, 2.4]); 29 | assert.strictEqual(s(-1.2), 0.000); 30 | assert.strictEqual(s( 0.6), 0.625); 31 | assert.strictEqual(s( 2.4), 1.000); 32 | }); 33 | 34 | it("diverging.domain() accepts an iterable", () => { 35 | const s = scaleDiverging().domain(new Set([-1.2, 0, 2.4])); 36 | assert.deepStrictEqual(s.domain(), [-1.2, 0, 2.4]); 37 | }); 38 | 39 | it("diverging.domain() handles a degenerate domain", () => { 40 | const s = scaleDiverging().domain([2, 2, 3]); 41 | assert.deepStrictEqual(s.domain(), [2, 2, 3]); 42 | assert.strictEqual(s(-1.2), 0.5); 43 | assert.strictEqual(s( 0.6), 0.5); 44 | assert.strictEqual(s( 2.4), 0.7); 45 | assert.deepStrictEqual(s.domain([1, 2, 2]).domain(), [1, 2, 2]); 46 | assert.strictEqual(s(-1.0), -1); 47 | assert.strictEqual(s( 0.5), -0.25); 48 | assert.strictEqual(s( 2.4), 0.5); 49 | assert.deepStrictEqual(s.domain([2, 2, 2]).domain(), [2, 2, 2]); 50 | assert.strictEqual(s(-1.0), 0.5); 51 | assert.strictEqual(s( 0.5), 0.5); 52 | assert.strictEqual(s( 2.4), 0.5); 53 | }); 54 | 55 | it("diverging.domain() handles a descending domain", () => { 56 | const s = scaleDiverging().domain([4, 2, 1]); 57 | assert.deepStrictEqual(s.domain(), [4, 2, 1]); 58 | assert.strictEqual(s(1.2), 0.9); 59 | assert.strictEqual(s(2.0), 0.5); 60 | assert.strictEqual(s(3.0), 0.25); 61 | }); 62 | 63 | it("divergingLog.domain() handles a descending domain", () => { 64 | const s = scaleDivergingLog().domain([3, 2, 1]); 65 | assert.deepStrictEqual(s.domain(), [3, 2, 1]); 66 | assert.strictEqual(s(1.2), 1 - 0.1315172029168969); 67 | assert.strictEqual(s(2.0), 1 - 0.5000000000000000); 68 | assert.strictEqual(s(2.8), 1 - 0.9149213210862197); 69 | }); 70 | 71 | it("divergingLog.domain() handles a descending negative domain", () => { 72 | const s = scaleDivergingLog().domain([-1, -2, -3]); 73 | assert.deepStrictEqual(s.domain(), [-1, -2, -3]); 74 | assert.strictEqual(s(-1.2), 0.1315172029168969); 75 | assert.strictEqual(s(-2.0), 0.5000000000000000); 76 | assert.strictEqual(s(-2.8), 0.9149213210862197); 77 | }); 78 | 79 | it("diverging.domain() handles a non-numeric domain", () => { 80 | const s = scaleDiverging().domain([NaN, 2, 3]); 81 | assert.strictEqual(isNaN(s.domain()[0]), true); 82 | assert.strictEqual(isNaN(s(-1.2)), true); 83 | assert.strictEqual(isNaN(s( 0.6)), true); 84 | assert.strictEqual(s( 2.4), 0.7); 85 | assert.strictEqual(isNaN(s.domain([1, NaN, 2]).domain()[1]), true); 86 | assert.strictEqual(isNaN(s(-1.0)), true); 87 | assert.strictEqual(isNaN(s( 0.5)), true); 88 | assert.strictEqual(isNaN(s( 2.4)), true); 89 | assert.strictEqual(isNaN(s.domain([0, 1, NaN]).domain()[2]), true); 90 | assert.strictEqual(s(-1.0), -0.5); 91 | assert.strictEqual(s( 0.5), 0.25); 92 | assert.strictEqual(isNaN(s( 2.4)), true); 93 | }); 94 | 95 | it("diverging.domain() only considers the first three elements of the domain", () => { 96 | const s = scaleDiverging().domain([-1, 100, 200, 3]); 97 | assert.deepStrictEqual(s.domain(), [-1, 100, 200]); 98 | }); 99 | 100 | it("diverging.copy() returns an isolated copy of the scale", () => { 101 | const s1 = scaleDiverging().domain([1, 2, 3]).clamp(true); 102 | const s2 = s1.copy(); 103 | assert.deepStrictEqual(s2.domain(), [1, 2, 3]); 104 | assert.strictEqual(s2.clamp(), true); 105 | s1.domain([-1, 1, 2]); 106 | assert.deepStrictEqual(s2.domain(), [1, 2, 3]); 107 | s1.clamp(false); 108 | assert.strictEqual(s2.clamp(), true); 109 | s2.domain([3, 4, 5]); 110 | assert.deepStrictEqual(s1.domain(), [-1, 1, 2]); 111 | s2.clamp(true); 112 | assert.deepStrictEqual(s1.clamp(), false); 113 | }); 114 | 115 | it("diverging.range() returns the computed range", () => { 116 | const s = scaleDiverging(function(t) { return t * 2 + 1; }); 117 | assert.deepStrictEqual(s.range(), [1, 2, 3]); 118 | }); 119 | 120 | it("diverging.interpolator(interpolator) sets the interpolator", () => { 121 | const i0 = function(t) { return t; }; 122 | const i1 = function(t) { return t * 2; }; 123 | const s = scaleDiverging(i0); 124 | assert.strictEqual(s.interpolator(), i0); 125 | assert.strictEqual(s.interpolator(i1), s); 126 | assert.strictEqual(s.interpolator(), i1); 127 | assert.strictEqual(s(-0.5), -1.0); 128 | assert.strictEqual(s( 0.0), 0.0); 129 | assert.strictEqual(s( 0.5), 1.0); 130 | }); 131 | 132 | it("diverging.range(range) sets the interpolator", () => { 133 | const s = scaleDiverging().range([1, 3, 10]); 134 | assert.strictEqual(s.interpolator()(0.5), 3); 135 | assert.deepStrictEqual(s.range(), [1, 3, 10]); 136 | }); 137 | 138 | it("diverging.range(range) ignores additional values", () => { 139 | const s = scaleDiverging().range([1, 3, 10, 20]); 140 | assert.strictEqual(s.interpolator()(0.5), 3); 141 | assert.deepStrictEqual(s.range(), [1, 3, 10]); 142 | }); 143 | 144 | it("scaleDiverging(range) sets the interpolator", () => { 145 | const s = scaleDiverging([1, 3, 10]); 146 | assert.strictEqual(s.interpolator()(0.5), 3); 147 | assert.deepStrictEqual(s.range(), [1, 3, 10]); 148 | }); 149 | -------------------------------------------------------------------------------- /test/symlog-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleSymlog} from "../src/index.js"; 3 | import {assertInDelta} from "./asserts.js"; 4 | 5 | it("scaleSymlog() has the expected defaults", () => { 6 | const s = scaleSymlog(); 7 | assert.deepStrictEqual(s.domain(), [0, 1]); 8 | assert.deepStrictEqual(s.range(), [0, 1]); 9 | assert.strictEqual(s.clamp(), false); 10 | assert.strictEqual(s.constant(), 1); 11 | }); 12 | 13 | it("symlog(x) maps a domain value x to a range value y", () => { 14 | const s = scaleSymlog().domain([-100, 100]); 15 | assert.strictEqual(s(-100), 0); 16 | assert.strictEqual(s(100), 1); 17 | assert.strictEqual(s(0), 0.5); 18 | }); 19 | 20 | it("symlog.invert(y) maps a range value y to a domain value x", () => { 21 | const s = scaleSymlog().domain([-100, 100]); 22 | assertInDelta(s.invert(1), 100); 23 | }); 24 | 25 | it("symlog.invert(y) coerces range values to numbers", () => { 26 | const s = scaleSymlog().range(["-3", "3"]); 27 | assert.deepStrictEqual(s.invert(3), 1); 28 | }); 29 | 30 | it("symlog.invert(y) returns NaN if the range is not coercible to number", () => { 31 | assert(isNaN(scaleSymlog().range(["#000", "#fff"]).invert("#999"))); 32 | assert(isNaN(scaleSymlog().range([0, "#fff"]).invert("#999"))); 33 | }); 34 | 35 | it("symlog.constant(constant) sets the constant to the specified value", () => { 36 | const s = scaleSymlog().constant(5); 37 | assert.strictEqual(s.constant(), 5); 38 | }); 39 | 40 | it("symlog.constant(constant) changing the constant does not change the domain or range", () => { 41 | const s = scaleSymlog().constant(2); 42 | assert.deepStrictEqual(s.domain(), [0, 1]); 43 | assert.deepStrictEqual(s.range(), [0, 1]); 44 | }); 45 | 46 | it("symlog.domain(domain) accepts an array of numbers", () => { 47 | assert.deepStrictEqual(scaleSymlog().domain([]).domain(), []); 48 | assert.deepStrictEqual(scaleSymlog().domain([1, 0]).domain(), [1, 0]); 49 | assert.deepStrictEqual(scaleSymlog().domain([1, 2, 3]).domain(), [1, 2, 3]); 50 | }); 51 | 52 | it("symlog.domain(domain) coerces domain values to numbers", () => { 53 | assert.deepStrictEqual(scaleSymlog().domain([new Date(Date.UTC(1990, 0, 1)), new Date(Date.UTC(1991, 0, 1))]).domain(), [631152000000, 662688000000]); 54 | assert.deepStrictEqual(scaleSymlog().domain(["0.0", "1.0"]).domain(), [0, 1]); 55 | assert.deepStrictEqual(scaleSymlog().domain([new Number(0), new Number(1)]).domain(), [0, 1]); 56 | }); 57 | 58 | it("symlog.domain(domain) makes a copy of domain values", () => { 59 | const d = [1, 2], s = scaleSymlog().domain(d); 60 | assert.deepStrictEqual(s.domain(), [1, 2]); 61 | d.push(3); 62 | assert.deepStrictEqual(s.domain(), [1, 2]); 63 | assert.deepStrictEqual(d, [1, 2, 3]); 64 | }); 65 | 66 | it("symlog.domain() returns a copy of domain values", () => { 67 | const s = scaleSymlog(), d = s.domain(); 68 | assert.deepStrictEqual(d, [0, 1]); 69 | d.push(3); 70 | assert.deepStrictEqual(s.domain(), [0, 1]); 71 | }); 72 | 73 | it("symlog.range(range) does not coerce range to numbers", () => { 74 | const s = scaleSymlog().range(["0px", "2px"]); 75 | assert.deepStrictEqual(s.range(), ["0px", "2px"]); 76 | assert.strictEqual(s(1), "2px"); 77 | }); 78 | 79 | it("symlog.range(range) can accept range values as arrays or objects", () => { 80 | assert.deepStrictEqual(scaleSymlog().range([{color: "red"}, {color: "blue"}])(1), {color: "rgb(0, 0, 255)"}); 81 | assert.deepStrictEqual(scaleSymlog().range([["red"], ["blue"]])(0), ["rgb(255, 0, 0)"]); 82 | }); 83 | 84 | it("symlog.range(range) makes a copy of range values", () => { 85 | const r = [1, 2], s = scaleSymlog().range(r); 86 | assert.deepStrictEqual(s.range(), [1, 2]); 87 | r.push(3); 88 | assert.deepStrictEqual(s.range(), [1, 2]); 89 | assert.deepStrictEqual(r, [1, 2, 3]); 90 | }); 91 | 92 | it("symlog.range() returns a copy of range values", () => { 93 | const s = scaleSymlog(), r = s.range(); 94 | assert.deepStrictEqual(r, [0, 1]); 95 | r.push(3); 96 | assert.deepStrictEqual(s.range(), [0, 1]); 97 | }); 98 | 99 | it("symlog.clamp() is false by default", () => { 100 | assert.strictEqual(scaleSymlog().clamp(), false); 101 | assert.strictEqual(scaleSymlog().range([10, 20])(3), 30); 102 | assert.strictEqual(scaleSymlog().range([10, 20])(-1), 0); 103 | assert.strictEqual(scaleSymlog().range([10, 20]).invert(30), 3); 104 | assert.strictEqual(scaleSymlog().range([10, 20]).invert(0), -1); 105 | }); 106 | 107 | it("symlog.clamp(true) restricts output values to the range", () => { 108 | assert.strictEqual(scaleSymlog().clamp(true).range([10, 20])(2), 20); 109 | assert.strictEqual(scaleSymlog().clamp(true).range([10, 20])(-1), 10); 110 | }); 111 | 112 | it("symlog.clamp(true) restricts input values to the domain", () => { 113 | assert.strictEqual(scaleSymlog().clamp(true).range([10, 20]).invert(30), 1); 114 | assert.strictEqual(scaleSymlog().clamp(true).range([10, 20]).invert(0), 0); 115 | }); 116 | 117 | it("symlog.clamp(clamp) coerces the specified clamp value to a boolean", () => { 118 | assert.strictEqual(scaleSymlog().clamp("true").clamp(), true); 119 | assert.strictEqual(scaleSymlog().clamp(1).clamp(), true); 120 | assert.strictEqual(scaleSymlog().clamp("").clamp(), false); 121 | assert.strictEqual(scaleSymlog().clamp(0).clamp(), false); 122 | }); 123 | 124 | it("symlog.copy() returns a copy with changes to the domain are isolated", () => { 125 | const x = scaleSymlog(), y = x.copy(); 126 | x.domain([1, 2]); 127 | assert.deepStrictEqual(y.domain(), [0, 1]); 128 | assert.strictEqual(x(1), 0); 129 | assert.strictEqual(y(1), 1); 130 | y.domain([2, 3]); 131 | assert.strictEqual(x(2), 1); 132 | assert.strictEqual(y(2), 0); 133 | assert.deepStrictEqual(x.domain(), [1, 2]); 134 | assert.deepStrictEqual(y.domain(), [2, 3]); 135 | const y2 = x.domain([1, 1.9]).copy(); 136 | x.nice(5); 137 | assert.deepStrictEqual(x.domain(), [1, 2]); 138 | assert.deepStrictEqual(y2.domain(), [1, 1.9]); 139 | }); 140 | 141 | it("symlog.copy() returns a copy with changes to the range are isolated", () => { 142 | const x = scaleSymlog(), y = x.copy(); 143 | x.range([1, 2]); 144 | assert.strictEqual(x.invert(1), 0); 145 | assert.strictEqual(y.invert(1), 1); 146 | assert.deepStrictEqual(y.range(), [0, 1]); 147 | y.range([2, 3]); 148 | assert.strictEqual(x.invert(2), 1); 149 | assert.strictEqual(y.invert(2), 0); 150 | assert.deepStrictEqual(x.range(), [1, 2]); 151 | assert.deepStrictEqual(y.range(), [2, 3]); 152 | }); 153 | 154 | it("symlog.copy() returns a copy with changes to clamping are isolated", () => { 155 | const x = scaleSymlog().clamp(true), y = x.copy(); 156 | x.clamp(false); 157 | assert.strictEqual(x(3), 2); 158 | assert.strictEqual(y(2), 1); 159 | assert.strictEqual(y.clamp(), true); 160 | y.clamp(false); 161 | assert.strictEqual(x(3), 2); 162 | assert.strictEqual(y(3), 2); 163 | assert.strictEqual(x.clamp(), false); 164 | }); 165 | 166 | it("symlog().clamp(true).invert(x) cannot return a value outside the domain", () => { 167 | const x = scaleSymlog().domain([1, 20]).clamp(true); 168 | assert.strictEqual(x.invert(0), 1); 169 | assert.strictEqual(x.invert(1), 20); 170 | }); 171 | -------------------------------------------------------------------------------- /test/identity-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleIdentity} from "../src/index.js"; 3 | 4 | it("scaleIdentity() has the expected defaults", () => { 5 | const s = scaleIdentity(); 6 | assert.deepStrictEqual(s.domain(), [0, 1]); 7 | assert.deepStrictEqual(s.range(), [0, 1]); 8 | }); 9 | 10 | it("scaleIdentity(range) sets the domain and range", () => { 11 | const s = scaleIdentity([1, 2]); 12 | assert.deepStrictEqual(s.domain(), [1, 2]); 13 | assert.deepStrictEqual(s.range(), [1, 2]); 14 | }); 15 | 16 | it("identity(x) is the identity function", () => { 17 | const s = scaleIdentity().domain([1, 2]); 18 | assert.strictEqual(s(0.5), 0.5); 19 | assert.strictEqual(s(1), 1); 20 | assert.strictEqual(s(1.5), 1.5); 21 | assert.strictEqual(s(2), 2); 22 | assert.strictEqual(s(2.5), 2.5); 23 | }); 24 | 25 | it("identity(x) coerces input to a number", () => { 26 | const s = scaleIdentity().domain([1, 2]); 27 | assert.strictEqual(s("2"), 2); 28 | }); 29 | 30 | it("identity(undefined) returns unknown", () => { 31 | const s = scaleIdentity().unknown(-1); 32 | assert.strictEqual(s(undefined), -1); 33 | assert.strictEqual(s(null), -1); 34 | assert.strictEqual(s(NaN), -1); 35 | assert.strictEqual(s("N/A"), -1); 36 | assert.strictEqual(s(0.4), 0.4); 37 | }); 38 | 39 | it("identity.invert(y) is the identity function", () => { 40 | const s = scaleIdentity().domain([1, 2]); 41 | assert.strictEqual(s.invert(0.5), 0.5); 42 | assert.strictEqual(s.invert(1), 1); 43 | assert.strictEqual(s.invert(1.5), 1.5); 44 | assert.strictEqual(s.invert(2), 2); 45 | assert.strictEqual(s.invert(2.5), 2.5); 46 | }); 47 | 48 | it("identity.invert(y) coerces range value to numbers", () => { 49 | const s = scaleIdentity().range(["0", "2"]); 50 | assert.strictEqual(s.invert("1"), 1); 51 | s.range([new Date(1990, 0, 1), new Date(1991, 0, 1)]); 52 | assert.strictEqual(s.invert(new Date(1990, 6, 2, 13)), +new Date(1990, 6, 2, 13)); 53 | s.range(["#000", "#fff"]); 54 | assert(isNaN(s.invert("#999"))); 55 | }); 56 | 57 | it("identity.invert(y) coerces input to a number", () => { 58 | const s = scaleIdentity().domain([1, 2]); 59 | assert.strictEqual(s.invert("2"), 2); 60 | }); 61 | 62 | it("identity.domain() is an alias for range()", () => { 63 | const s = scaleIdentity(); 64 | assert.strictEqual(s.domain, s.range); 65 | assert.deepStrictEqual(s.domain(), s.range()); 66 | s.domain([-10, 0, 100]); 67 | assert.deepStrictEqual(s.range(), [-10, 0, 100]); 68 | s.range([-10, 0, 100]); 69 | assert.deepStrictEqual(s.domain(), [-10, 0, 100]); 70 | }); 71 | 72 | it("identity.domain() defaults to [0, 1]", () => { 73 | const s = scaleIdentity(); 74 | assert.deepStrictEqual(s.domain(), [0, 1]); 75 | assert.deepStrictEqual(s.range(), [0, 1]); 76 | assert.strictEqual(s(0.5), 0.5); 77 | }); 78 | 79 | it("identity.domain() coerces values to numbers", () => { 80 | const s = scaleIdentity().domain([new Date(1990, 0, 1), new Date(1991, 0, 1)]); 81 | assert.strictEqual(typeof s.domain()[0], "number"); 82 | assert.strictEqual(typeof s.domain()[1], "number"); 83 | assert.strictEqual(s.domain()[0], +new Date(1990, 0, 1)); 84 | assert.strictEqual(s.domain()[1], +new Date(1991, 0, 1)); 85 | assert.strictEqual(typeof s(new Date(1989, 9, 20)), "number"); 86 | assert.strictEqual(s(new Date(1989, 9, 20)), +new Date(1989, 9, 20)); 87 | s.domain(["0", "1"]); 88 | assert.strictEqual(typeof s.domain()[0], "number"); 89 | assert.strictEqual(typeof s.domain()[1], "number"); 90 | assert.strictEqual(s(0.5), 0.5); 91 | s.domain([new Number(0), new Number(1)]); 92 | assert.strictEqual(typeof s.domain()[0], "number"); 93 | assert.strictEqual(typeof s.domain()[1], "number"); 94 | assert.strictEqual(s(0.5), 0.5); 95 | s.range([new Date(1990, 0, 1), new Date(1991, 0, 1)]); 96 | assert.strictEqual(typeof s.range()[0], "number"); 97 | assert.strictEqual(typeof s.range()[1], "number"); 98 | assert.strictEqual(s.range()[0], +new Date(1990, 0, 1)); 99 | assert.strictEqual(s.range()[1], +new Date(1991, 0, 1)); 100 | assert.strictEqual(typeof s(new Date(1989, 9, 20)), "number"); 101 | assert.strictEqual(s(new Date(1989, 9, 20)), +new Date(1989, 9, 20)); 102 | s.range(["0", "1"]); 103 | assert.strictEqual(typeof s.range()[0], "number"); 104 | assert.strictEqual(typeof s.range()[1], "number"); 105 | assert.strictEqual(s(0.5), 0.5); 106 | s.range([new Number(0), new Number(1)]); 107 | assert.strictEqual(typeof s.range()[0], "number"); 108 | assert.strictEqual(typeof s.range()[1], "number"); 109 | assert.strictEqual(s(0.5), 0.5); 110 | }); 111 | 112 | it("identity.domain() accepts an iterable", () => { 113 | const s = scaleIdentity().domain(new Set([1, 2])); 114 | assert.deepStrictEqual(s.domain(), [1, 2]); 115 | assert.deepStrictEqual(s.range(), [1, 2]); 116 | }); 117 | 118 | it("identity.domain() can specify a polyidentity domain and range", () => { 119 | const s = scaleIdentity().domain([-10, 0, 100]); 120 | assert.deepStrictEqual(s.domain(), [-10, 0, 100]); 121 | assert.strictEqual(s(-5), -5); 122 | assert.strictEqual(s(50), 50); 123 | assert.strictEqual(s(75), 75); 124 | s.range([-10, 0, 100]); 125 | assert.deepStrictEqual(s.range(), [-10, 0, 100]); 126 | assert.strictEqual(s(-5), -5); 127 | assert.strictEqual(s(50), 50); 128 | assert.strictEqual(s(75), 75); 129 | }); 130 | 131 | it("identity.domain() does not affect the identity function", () => { 132 | const s = scaleIdentity().domain([Infinity, NaN]); 133 | assert.strictEqual(s(42), 42); 134 | assert.strictEqual(s.invert(-42), -42); 135 | }); 136 | 137 | it("identity.ticks(count) generates ticks of varying degree", () => { 138 | const s = scaleIdentity(); 139 | assert.deepStrictEqual(s.ticks(1).map(s.tickFormat(1)), ["0", "1"]); 140 | assert.deepStrictEqual(s.ticks(2).map(s.tickFormat(2)), ["0.0", "0.5", "1.0"]); 141 | assert.deepStrictEqual(s.ticks(5).map(s.tickFormat(5)), ["0.0", "0.2", "0.4", "0.6", "0.8", "1.0"]); 142 | assert.deepStrictEqual(s.ticks(10).map(s.tickFormat(10)), ["0.0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1.0"]); 143 | s.domain([1, 0]); 144 | assert.deepStrictEqual(s.ticks(1).map(s.tickFormat(1)), ["0", "1"].reverse()); 145 | assert.deepStrictEqual(s.ticks(2).map(s.tickFormat(2)), ["0.0", "0.5", "1.0"].reverse()); 146 | assert.deepStrictEqual(s.ticks(5).map(s.tickFormat(5)), ["0.0", "0.2", "0.4", "0.6", "0.8", "1.0"].reverse()); 147 | assert.deepStrictEqual(s.ticks(10).map(s.tickFormat(10)), ["0.0", "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7", "0.8", "0.9", "1.0"].reverse()); 148 | }); 149 | 150 | it("identity.tickFormat(count) formats ticks with the appropriate precision", () => { 151 | const s = scaleIdentity().domain([0.123456789, 1.23456789]); 152 | assert.strictEqual(s.tickFormat(1)(s.ticks(1)[0]), "1"); 153 | assert.strictEqual(s.tickFormat(2)(s.ticks(2)[0]), "0.5"); 154 | assert.strictEqual(s.tickFormat(4)(s.ticks(4)[0]), "0.2"); 155 | assert.strictEqual(s.tickFormat(8)(s.ticks(8)[0]), "0.2"); 156 | assert.strictEqual(s.tickFormat(16)(s.ticks(16)[0]), "0.15"); 157 | assert.strictEqual(s.tickFormat(32)(s.ticks(32)[0]), "0.15"); 158 | assert.strictEqual(s.tickFormat(64)(s.ticks(64)[0]), "0.14"); 159 | assert.strictEqual(s.tickFormat(128)(s.ticks(128)[0]), "0.13"); 160 | assert.strictEqual(s.tickFormat(256)(s.ticks(256)[0]), "0.125"); 161 | }); 162 | 163 | it("identity.copy() isolates changes to the domain or range", () => { 164 | const s1 = scaleIdentity(); 165 | const s2 = s1.copy(); 166 | const s3 = s1.copy(); 167 | s1.domain([1, 2]); 168 | assert.deepStrictEqual(s2.domain(), [0, 1]); 169 | s2.domain([2, 3]); 170 | assert.deepStrictEqual(s1.domain(), [1, 2]); 171 | assert.deepStrictEqual(s2.domain(), [2, 3]); 172 | const s4 = s3.copy(); 173 | s3.range([1, 2]); 174 | assert.deepStrictEqual(s4.range(), [0, 1]); 175 | s4.range([2, 3]); 176 | assert.deepStrictEqual(s3.range(), [1, 2]); 177 | assert.deepStrictEqual(s4.range(), [2, 3]); 178 | }); 179 | -------------------------------------------------------------------------------- /test/ordinal-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleImplicit, scaleOrdinal} from "../src/index.js"; 3 | 4 | it("scaleOrdinal() has the expected defaults", () => { 5 | const s = scaleOrdinal(); 6 | assert.deepStrictEqual(s.domain(), []); 7 | assert.deepStrictEqual(s.range(), []); 8 | assert.strictEqual(s(0), undefined); 9 | assert.strictEqual(s.unknown(), scaleImplicit); 10 | assert.deepStrictEqual(s.domain(), [0]); 11 | }); 12 | 13 | it("ordinal(x) maps a unique name x in the domain to the corresponding value y in the range", () => { 14 | const s = scaleOrdinal().domain([0, 1]).range(["foo", "bar"]).unknown(undefined); 15 | assert.strictEqual(s(0), "foo"); 16 | assert.strictEqual(s(1), "bar"); 17 | s.range(["a", "b", "c"]); 18 | assert.strictEqual(s(0), "a"); 19 | assert.strictEqual(s("0"), undefined); 20 | assert.strictEqual(s([0]), undefined); 21 | assert.strictEqual(s(1), "b"); 22 | assert.strictEqual(s(new Number(1)), "b"); 23 | assert.strictEqual(s(2), undefined); 24 | }); 25 | 26 | it("ordinal(x) implicitly extends the domain when a range is explicitly specified", () => { 27 | const s = scaleOrdinal().range(["foo", "bar"]); 28 | assert.deepStrictEqual(s.domain(), []); 29 | assert.strictEqual(s(0), "foo"); 30 | assert.deepStrictEqual(s.domain(), [0]); 31 | assert.strictEqual(s(1), "bar"); 32 | assert.deepStrictEqual(s.domain(), [0, 1]); 33 | assert.strictEqual(s(0), "foo"); 34 | assert.deepStrictEqual(s.domain(), [0, 1]); 35 | }); 36 | 37 | it("ordinal.domain(x) makes a copy of the domain", () => { 38 | const domain = ["red", "green"]; 39 | const s = scaleOrdinal().domain(domain); 40 | domain.push("blue"); 41 | assert.deepStrictEqual(s.domain(), ["red", "green"]); 42 | }); 43 | 44 | it("ordinal.domain() returns a copy of the domain", () => { 45 | const s = scaleOrdinal().domain(["red", "green"]); 46 | const domain = s.domain(); 47 | s("blue"); 48 | assert.deepStrictEqual(domain, ["red", "green"]); 49 | }); 50 | 51 | it("ordinal.domain() accepts an iterable", () => { 52 | const s = scaleOrdinal().domain(new Set(["red", "green"])); 53 | assert.deepStrictEqual(s.domain(), ["red", "green"]); 54 | }); 55 | 56 | it("ordinal.domain() replaces previous domain values", () => { 57 | const s = scaleOrdinal().range(["foo", "bar"]); 58 | assert.strictEqual(s(1), "foo"); 59 | assert.strictEqual(s(0), "bar"); 60 | assert.deepStrictEqual(s.domain(), [1, 0]); 61 | s.domain(["0", "1"]); 62 | assert.strictEqual(s("0"), "foo"); // it changed! 63 | assert.strictEqual(s("1"), "bar"); 64 | assert.deepStrictEqual(s.domain(), ["0", "1"]); 65 | }); 66 | 67 | it("ordinal.domain() uniqueness is based on primitive coercion", () => { 68 | const s = scaleOrdinal().domain(["foo"]).range([42, 43, 44]); 69 | assert.strictEqual(s(new String("foo")), 42); 70 | assert.strictEqual(s({valueOf: function() { return "foo"; }}), 42); 71 | assert.strictEqual(s({valueOf: function() { return "bar"; }}), 43); 72 | }); 73 | 74 | it("ordinal.domain() does not coerce domain values to strings", () => { 75 | const s = scaleOrdinal().domain([0, 1]); 76 | assert.deepStrictEqual(s.domain(), [0, 1]); 77 | assert.strictEqual(typeof s.domain()[0], "number"); 78 | assert.strictEqual(typeof s.domain()[1], "number"); 79 | }); 80 | 81 | it("ordinal.domain() does not barf on object built-ins", () => { 82 | const s = scaleOrdinal().domain(["__proto__", "hasOwnProperty"]).range([42, 43]); 83 | assert.strictEqual(s("__proto__"), 42); 84 | assert.strictEqual(s("hasOwnProperty"), 43); 85 | assert.deepStrictEqual(s.domain(), ["__proto__", "hasOwnProperty"]); 86 | }); 87 | 88 | it("ordinal() accepts dates", () => { 89 | const s = scaleOrdinal(); 90 | s(new Date(1970, 2, 1)); 91 | s(new Date(2001, 4, 13)); 92 | s(new Date(1970, 2, 1)); 93 | s(new Date(2001, 4, 13)); 94 | assert.deepStrictEqual(s.domain(), [new Date(1970, 2, 1), new Date(2001, 4, 13)]); 95 | }); 96 | 97 | it("ordinal.domain() accepts dates", () => { 98 | const s = scaleOrdinal().domain([ 99 | new Date(1970, 2, 1), 100 | new Date(2001, 4, 13), 101 | new Date(1970, 2, 1), 102 | new Date(2001, 4, 13) 103 | ]); 104 | s(new Date(1970, 2, 1)); 105 | s(new Date(1999, 11, 31)); 106 | assert.deepStrictEqual(s.domain(), [new Date(1970, 2, 1), new Date(2001, 4, 13), new Date(1999, 11, 31)]); 107 | }); 108 | 109 | it("ordinal.domain() does not barf on object built-ins", () => { 110 | const s = scaleOrdinal().domain(["__proto__", "hasOwnProperty"]).range([42, 43]); 111 | assert.strictEqual(s("__proto__"), 42); 112 | assert.strictEqual(s("hasOwnProperty"), 43); 113 | assert.deepStrictEqual(s.domain(), ["__proto__", "hasOwnProperty"]); 114 | }); 115 | 116 | it("ordinal.domain() is ordered by appearance", () => { 117 | const s = scaleOrdinal(); 118 | s("foo"); 119 | s("bar"); 120 | s("baz"); 121 | assert.deepStrictEqual(s.domain(), ["foo", "bar", "baz"]); 122 | s.domain(["baz", "bar"]); 123 | s("foo"); 124 | assert.deepStrictEqual(s.domain(), ["baz", "bar", "foo"]); 125 | s.domain(["baz", "foo"]); 126 | assert.deepStrictEqual(s.domain(), ["baz", "foo"]); 127 | s.domain([]); 128 | s("foo"); 129 | s("bar"); 130 | assert.deepStrictEqual(s.domain(), ["foo", "bar"]); 131 | }); 132 | 133 | it("ordinal.range(x) makes a copy of the range", () => { 134 | const range = ["red", "green"]; 135 | const s = scaleOrdinal().range(range); 136 | range.push("blue"); 137 | assert.deepStrictEqual(s.range(), ["red", "green"]); 138 | }); 139 | 140 | it("ordinal.range() accepts an iterable", () => { 141 | const s = scaleOrdinal().range(new Set(["red", "green"])); 142 | assert.deepStrictEqual(s.range(), ["red", "green"]); 143 | }); 144 | 145 | it("ordinal.range() returns a copy of the range", () => { 146 | const s = scaleOrdinal().range(["red", "green"]); 147 | const range = s.range(); 148 | assert.deepStrictEqual(range, ["red", "green"]); 149 | range.push("blue"); 150 | assert.deepStrictEqual(s.range(), ["red", "green"]); 151 | }); 152 | 153 | it("ordinal.range(values) does not discard implicit domain associations", () => { 154 | const s = scaleOrdinal(); 155 | assert.strictEqual(s(0), undefined); 156 | assert.strictEqual(s(1), undefined); 157 | s.range(["foo", "bar"]); 158 | assert.strictEqual(s(1), "bar"); 159 | assert.strictEqual(s(0), "foo"); 160 | }); 161 | 162 | it("ordinal(value) recycles values when exhausted", () => { 163 | const s = scaleOrdinal().range(["a", "b", "c"]); 164 | assert.strictEqual(s(0), "a"); 165 | assert.strictEqual(s(1), "b"); 166 | assert.strictEqual(s(2), "c"); 167 | assert.strictEqual(s(3), "a"); 168 | assert.strictEqual(s(4), "b"); 169 | assert.strictEqual(s(5), "c"); 170 | assert.strictEqual(s(2), "c"); 171 | assert.strictEqual(s(1), "b"); 172 | assert.strictEqual(s(0), "a"); 173 | }); 174 | 175 | it("ordinal.unknown(x) sets the output value for unknown inputs", () => { 176 | const s = scaleOrdinal().domain(["foo", "bar"]).unknown("gray").range(["red", "blue"]); 177 | assert.strictEqual(s("foo"), "red"); 178 | assert.strictEqual(s("bar"), "blue"); 179 | assert.strictEqual(s("baz"), "gray"); 180 | assert.strictEqual(s("quux"), "gray"); 181 | }); 182 | 183 | it("ordinal.unknown(x) prevents implicit domain extension if x is not implicit", () => { 184 | const s = scaleOrdinal().domain(["foo", "bar"]).unknown(undefined).range(["red", "blue"]); 185 | assert.strictEqual(s("baz"), undefined); 186 | assert.deepStrictEqual(s.domain(), ["foo", "bar"]); 187 | }); 188 | 189 | it("ordinal.copy() copies all fields", () => { 190 | const s1 = scaleOrdinal().domain([1, 2]).range(["red", "green"]).unknown("gray"); 191 | const s2 = s1.copy(); 192 | assert.deepStrictEqual(s2.domain(), s1.domain()); 193 | assert.deepStrictEqual(s2.range(), s1.range()); 194 | assert.deepStrictEqual(s2.unknown(), s1.unknown()); 195 | }); 196 | 197 | it("ordinal.copy() changes to the domain are isolated", () => { 198 | const s1 = scaleOrdinal().range(["foo", "bar"]); 199 | const s2 = s1.copy(); 200 | s1.domain([1, 2]); 201 | assert.deepStrictEqual(s2.domain(), []); 202 | assert.strictEqual(s1(1), "foo"); 203 | assert.strictEqual(s2(1), "foo"); 204 | s2.domain([2, 3]); 205 | assert.strictEqual(s1(2), "bar"); 206 | assert.strictEqual(s2(2), "foo"); 207 | assert.deepStrictEqual(s1.domain(), [1, 2]); 208 | assert.deepStrictEqual(s2.domain(), [2, 3]); 209 | }); 210 | 211 | it("ordinal.copy() changes to the range are isolated", () => { 212 | const s1 = scaleOrdinal().range(["foo", "bar"]); 213 | const s2 = s1.copy(); 214 | s1.range(["bar", "foo"]); 215 | assert.strictEqual(s1(1), "bar"); 216 | assert.strictEqual(s2(1), "foo"); 217 | assert.deepStrictEqual(s2.range(), ["foo", "bar"]); 218 | s2.range(["foo", "baz"]); 219 | assert.strictEqual(s1(2), "foo"); 220 | assert.strictEqual(s2(2), "baz"); 221 | assert.deepStrictEqual(s1.range(), ["bar", "foo"]); 222 | assert.deepStrictEqual(s2.range(), ["foo", "baz"]); 223 | }); 224 | -------------------------------------------------------------------------------- /test/band-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleBand} from "../src/index.js"; 3 | 4 | it("scaleBand() has the expected defaults", () => { 5 | const s = scaleBand(); 6 | assert.deepStrictEqual(s.domain(), []); 7 | assert.deepStrictEqual(s.range(), [0, 1]); 8 | assert.strictEqual(s.bandwidth(), 1); 9 | assert.strictEqual(s.step(), 1); 10 | assert.strictEqual(s.round(), false); 11 | assert.strictEqual(s.paddingInner(), 0); 12 | assert.strictEqual(s.paddingOuter(), 0); 13 | assert.strictEqual(s.align(), 0.5); 14 | }); 15 | 16 | it("band(value) computes discrete bands in a continuous range", () => { 17 | const s = scaleBand([0, 960]); 18 | assert.strictEqual(s("foo"), undefined); 19 | s.domain(["foo", "bar"]); 20 | assert.strictEqual(s("foo"), 0); 21 | assert.strictEqual(s("bar"), 480); 22 | s.domain(["a", "b", "c"]).range([0, 120]); 23 | assert.deepStrictEqual(s.domain().map(s), [0, 40, 80]); 24 | assert.strictEqual(s.bandwidth(), 40); 25 | s.padding(0.2); 26 | assert.deepStrictEqual(s.domain().map(s), [7.5, 45, 82.5]); 27 | assert.strictEqual(s.bandwidth(), 30); 28 | }); 29 | 30 | it("band(value) returns undefined for values outside the domain", () => { 31 | const s = scaleBand(["a", "b", "c"], [0, 1]); 32 | assert.strictEqual(s("d"), undefined); 33 | assert.strictEqual(s("e"), undefined); 34 | assert.strictEqual(s("f"), undefined); 35 | }); 36 | 37 | it("band(value) does not implicitly add values to the domain", () => { 38 | const s = scaleBand(["a", "b", "c"], [0, 1]); 39 | s("d"); 40 | s("e"); 41 | assert.deepStrictEqual(s.domain(), ["a", "b", "c"]); 42 | }); 43 | 44 | it("band.step() returns the distance between the starts of adjacent bands", () => { 45 | const s = scaleBand([0, 960]); 46 | assert.strictEqual(s.domain(["foo"]).step(), 960); 47 | assert.strictEqual(s.domain(["foo", "bar"]).step(), 480); 48 | assert.strictEqual(s.domain(["foo", "bar", "baz"]).step(), 320); 49 | s.padding(0.5); 50 | assert.strictEqual(s.domain(["foo"]).step(), 640); 51 | assert.strictEqual(s.domain(["foo", "bar"]).step(), 384); 52 | }); 53 | 54 | it("band.bandwidth() returns the width of the band", () => { 55 | const s = scaleBand([0, 960]); 56 | assert.strictEqual(s.domain([]).bandwidth(), 960); 57 | assert.strictEqual(s.domain(["foo"]).bandwidth(), 960); 58 | assert.strictEqual(s.domain(["foo", "bar"]).bandwidth(), 480); 59 | assert.strictEqual(s.domain(["foo", "bar", "baz"]).bandwidth(), 320); 60 | s.padding(0.5); 61 | assert.strictEqual(s.domain([]).bandwidth(), 480); 62 | assert.strictEqual(s.domain(["foo"]).bandwidth(), 320); 63 | assert.strictEqual(s.domain(["foo", "bar"]).bandwidth(), 192); 64 | }); 65 | 66 | it("band.domain([]) computes reasonable band and step values", () => { 67 | const s = scaleBand([0, 960]).domain([]); 68 | assert.strictEqual(s.step(), 960); 69 | assert.strictEqual(s.bandwidth(), 960); 70 | s.padding(0.5); 71 | assert.strictEqual(s.step(), 960); 72 | assert.strictEqual(s.bandwidth(), 480); 73 | s.padding(1); 74 | assert.strictEqual(s.step(), 960); 75 | assert.strictEqual(s.bandwidth(), 0); 76 | }); 77 | 78 | it("band.domain([value]) computes a reasonable singleton band, even with padding", () => { 79 | const s = scaleBand([0, 960]).domain(["foo"]); 80 | assert.strictEqual(s("foo"), 0); 81 | assert.strictEqual(s.step(), 960); 82 | assert.strictEqual(s.bandwidth(), 960); 83 | s.padding(0.5); 84 | assert.strictEqual(s("foo"), 320); 85 | assert.strictEqual(s.step(), 640); 86 | assert.strictEqual(s.bandwidth(), 320); 87 | s.padding(1); 88 | assert.strictEqual(s("foo"), 480); 89 | assert.strictEqual(s.step(), 480); 90 | assert.strictEqual(s.bandwidth(), 0); 91 | }); 92 | 93 | it("band.domain(values) recomputes the bands", () => { 94 | const s = scaleBand().domain(["a", "b", "c"]).rangeRound([0, 100]); 95 | assert.deepStrictEqual(s.domain().map(s), [1, 34, 67]); 96 | assert.strictEqual(s.bandwidth(), 33); 97 | s.domain(["a", "b", "c", "d"]); 98 | assert.deepStrictEqual(s.domain().map(s), [0, 25, 50, 75]); 99 | assert.strictEqual(s.bandwidth(), 25); 100 | }); 101 | 102 | it("band.domain(domain) accepts an iterable", () => { 103 | assert.deepStrictEqual(scaleBand().domain(new Set(["a", "b", "c"])).domain(), ["a", "b", "c"]); 104 | }); 105 | 106 | it("band.domain(values) makes a copy of the specified domain values", () => { 107 | const domain = ["red", "green"]; 108 | const s = scaleBand().domain(domain); 109 | domain.push("blue"); 110 | assert.deepStrictEqual(s.domain(), ["red", "green"]); 111 | }); 112 | 113 | it("band.domain() returns a copy of the domain", () => { 114 | const s = scaleBand().domain(["red", "green"]); 115 | const domain = s.domain(); 116 | assert.deepStrictEqual(domain, ["red", "green"]); 117 | domain.push("blue"); 118 | assert.deepStrictEqual(s.domain(), ["red", "green"]); 119 | }); 120 | 121 | it("band.range(values) can be descending", () => { 122 | const s = scaleBand().domain(["a", "b", "c"]).range([120, 0]); 123 | assert.deepStrictEqual(s.domain().map(s), [80, 40, 0]); 124 | assert.strictEqual(s.bandwidth(), 40); 125 | s.padding(0.2); 126 | assert.deepStrictEqual(s.domain().map(s), [82.5, 45, 7.5]); 127 | assert.strictEqual(s.bandwidth(), 30); 128 | }); 129 | 130 | it("band.range(values) makes a copy of the specified range values", () => { 131 | const range = [1, 2]; 132 | const s = scaleBand().range(range); 133 | range.push("blue"); 134 | assert.deepStrictEqual(s.range(), [1, 2]); 135 | }); 136 | 137 | it("band.range() returns a copy of the range", () => { 138 | const s = scaleBand().range([1, 2]); 139 | const range = s.range(); 140 | assert.deepStrictEqual(range, [1, 2]); 141 | range.push("blue"); 142 | assert.deepStrictEqual(s.range(), [1, 2]); 143 | }); 144 | 145 | it("band.range(values) accepts an iterable", () => { 146 | const s = scaleBand().range(new Set([1, 2])); 147 | assert.deepStrictEqual(s.range(), [1, 2]); 148 | }); 149 | 150 | it("band.rangeRound(values) accepts an iterable", () => { 151 | const s = scaleBand().rangeRound(new Set([1, 2])); 152 | assert.deepStrictEqual(s.range(), [1, 2]); 153 | }); 154 | 155 | it("band.range(values) coerces values to numbers", () => { 156 | const s = scaleBand().range(["1.0", "2.0"]); 157 | assert.deepStrictEqual(s.range(), [1, 2]); 158 | }); 159 | 160 | it("band.rangeRound(values) coerces values to numbers", () => { 161 | const s = scaleBand().rangeRound(["1.0", "2.0"]); 162 | assert.deepStrictEqual(s.range(), [1, 2]); 163 | }); 164 | 165 | it("band.paddingInner(p) specifies the inner padding p", () => { 166 | const s = scaleBand().domain(["a", "b", "c"]).range([120, 0]).paddingInner(0.1).round(true); 167 | assert.deepStrictEqual(s.domain().map(s), [83, 42, 1]); 168 | assert.strictEqual(s.bandwidth(), 37); 169 | s.paddingInner(0.2); 170 | assert.deepStrictEqual(s.domain().map(s), [85, 43, 1]); 171 | assert.strictEqual(s.bandwidth(), 34); 172 | }); 173 | 174 | it("band.paddingInner(p) coerces p to a number <= 1", () => { 175 | const s = scaleBand(); 176 | assert.strictEqual(s.paddingInner("1.0").paddingInner(), 1); 177 | assert.strictEqual(s.paddingInner("-1.0").paddingInner(), -1); 178 | assert.strictEqual(s.paddingInner("2.0").paddingInner(), 1); 179 | assert(Number.isNaN(s.paddingInner(NaN).paddingInner())); 180 | }); 181 | 182 | it("band.paddingOuter(p) specifies the outer padding p", () => { 183 | const s = scaleBand().domain(["a", "b", "c"]).range([120, 0]).paddingInner(0.2).paddingOuter(0.1); 184 | assert.deepStrictEqual(s.domain().map(s), [84, 44, 4]); 185 | assert.strictEqual(s.bandwidth(), 32); 186 | s.paddingOuter(1); 187 | assert.deepStrictEqual(s.domain().map(s), [75, 50, 25]); 188 | assert.strictEqual(s.bandwidth(), 20); 189 | }); 190 | 191 | it("band.paddingOuter(p) coerces p to a number", () => { 192 | const s = scaleBand(); 193 | assert.strictEqual(s.paddingOuter("1.0").paddingOuter(), 1); 194 | assert.strictEqual(s.paddingOuter("-1.0").paddingOuter(), -1); 195 | assert.strictEqual(s.paddingOuter("2.0").paddingOuter(), 2); 196 | assert(Number.isNaN(s.paddingOuter(NaN).paddingOuter())); 197 | }); 198 | 199 | it("band.rangeRound(values) is an alias for band.range(values).round(true)", () => { 200 | const s = scaleBand().domain(["a", "b", "c"]).rangeRound([0, 100]); 201 | assert.deepStrictEqual(s.range(), [0, 100]); 202 | assert.strictEqual(s.round(), true); 203 | }); 204 | 205 | it("band.round(true) computes discrete rounded bands in a continuous range", () => { 206 | const s = scaleBand().domain(["a", "b", "c"]).range([0, 100]).round(true); 207 | assert.deepStrictEqual(s.domain().map(s), [1, 34, 67]); 208 | assert.strictEqual(s.bandwidth(), 33); 209 | s.padding(0.2); 210 | assert.deepStrictEqual(s.domain().map(s), [7, 38, 69]); 211 | assert.strictEqual(s.bandwidth(), 25); 212 | }); 213 | 214 | it("band.copy() copies all fields", () => { 215 | const s1 = scaleBand().domain(["red", "green"]).range([1, 2]).round(true).paddingInner(0.1).paddingOuter(0.2); 216 | const s2 = s1.copy(); 217 | assert.deepStrictEqual(s2.domain(), s1.domain()); 218 | assert.deepStrictEqual(s2.range(), s1.range()); 219 | assert.strictEqual(s2.round(), s1.round()); 220 | assert.strictEqual(s2.paddingInner(), s1.paddingInner()); 221 | assert.strictEqual(s2.paddingOuter(), s1.paddingOuter()); 222 | }); 223 | 224 | it("band.copy() isolates changes to the domain", () => { 225 | const s1 = scaleBand().domain(["foo", "bar"]).range([0, 2]); 226 | const s2 = s1.copy(); 227 | s1.domain(["red", "blue"]); 228 | assert.deepStrictEqual(s2.domain(), ["foo", "bar"]); 229 | assert.deepStrictEqual(s1.domain().map(s1), [0, 1]); 230 | assert.deepStrictEqual(s2.domain().map(s2), [0, 1]); 231 | s2.domain(["red", "blue"]); 232 | assert.deepStrictEqual(s1.domain(), ["red", "blue"]); 233 | assert.deepStrictEqual(s1.domain().map(s1), [0, 1]); 234 | assert.deepStrictEqual(s2.domain().map(s2), [0, 1]); 235 | }); 236 | 237 | it("band.copy() isolates changes to the range", () => { 238 | const s1 = scaleBand().domain(["foo", "bar"]).range([0, 2]); 239 | const s2 = s1.copy(); 240 | s1.range([3, 5]); 241 | assert.deepStrictEqual(s2.range(), [0, 2]); 242 | assert.deepStrictEqual(s1.domain().map(s1), [3, 4]); 243 | assert.deepStrictEqual(s2.domain().map(s2), [0, 1]); 244 | s2.range([5, 7]); 245 | assert.deepStrictEqual(s1.range(), [3, 5]); 246 | assert.deepStrictEqual(s1.domain().map(s1), [3, 4]); 247 | assert.deepStrictEqual(s2.domain().map(s2), [5, 6]); 248 | }); 249 | 250 | // TODO align tests for padding & round 251 | -------------------------------------------------------------------------------- /test/utcTime-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {interpolateHsl} from "d3-interpolate"; 3 | import {utcDay, utcMinute, utcMonth, utcWeek, utcYear} from "d3-time"; 4 | import {scaleUtc} from "../src/index.js"; 5 | import {utc} from "./date.js"; 6 | 7 | it("scaleUtc.nice() is an alias for scaleUtc.nice(10)", () => { 8 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 17), utc(2009, 0, 1, 23, 42)]); 9 | assert.deepStrictEqual(x.nice().domain(), [utc(2009, 0, 1), utc(2009, 0, 2)]); 10 | }); 11 | 12 | it("scaleUtc.nice() can nice sub-second domains", () => { 13 | const x = scaleUtc().domain([utc(2013, 0, 1, 12, 0, 0, 0), utc(2013, 0, 1, 12, 0, 0, 128)]); 14 | assert.deepStrictEqual(x.nice().domain(), [utc(2013, 0, 1, 12, 0, 0, 0), utc(2013, 0, 1, 12, 0, 0, 130)]); 15 | }); 16 | 17 | it("scaleUtc.nice() can nice multi-year domains", () => { 18 | const x = scaleUtc().domain([utc(2001, 0, 1), utc(2138, 0, 1)]); 19 | assert.deepStrictEqual(x.nice().domain(), [utc(2000, 0, 1), utc(2140, 0, 1)]); 20 | }); 21 | 22 | it("scaleUtc.nice() can nice empty domains", () => { 23 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 12), utc(2009, 0, 1, 0, 12)]); 24 | assert.deepStrictEqual(x.nice().domain(), [utc(2009, 0, 1, 0, 12), utc(2009, 0, 1, 0, 12)]); 25 | }); 26 | 27 | it("scaleUtc.nice(count) nices using the specified tick count", () => { 28 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 17), utc(2009, 0, 1, 23, 42)]); 29 | assert.deepStrictEqual(x.nice(100).domain(), [utc(2009, 0, 1, 0, 15), utc(2009, 0, 1, 23, 45)]); 30 | assert.deepStrictEqual(x.nice(10).domain(), [utc(2009, 0, 1), utc(2009, 0, 2)]); 31 | }); 32 | 33 | it("scaleUtc.nice(interval) nices using the specified time interval", () => { 34 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 12), utc(2009, 0, 1, 23, 48)]); 35 | assert.deepStrictEqual(x.nice(utcDay).domain(), [utc(2009, 0, 1), utc(2009, 0, 2)]); 36 | assert.deepStrictEqual(x.nice(utcWeek).domain(), [utc(2008, 11, 28), utc(2009, 0, 4)]); 37 | assert.deepStrictEqual(x.nice(utcMonth).domain(), [utc(2008, 11, 1), utc(2009, 1, 1)]); 38 | assert.deepStrictEqual(x.nice(utcYear).domain(), [utc(2008, 0, 1), utc(2010, 0, 1)]); 39 | }); 40 | 41 | it("scaleUtc.nice(interval) can nice empty domains", () => { 42 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 12), utc(2009, 0, 1, 0, 12)]); 43 | assert.deepStrictEqual(x.nice(utcDay).domain(), [utc(2009, 0, 1), utc(2009, 0, 2)]); 44 | }); 45 | 46 | it("scaleUtc.nice(interval) can nice a polylinear domain, only affecting its extent", () => { 47 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 12), utc(2009, 0, 1, 23, 48), utc(2009, 0, 2, 23, 48)]).nice(utcDay); 48 | assert.deepStrictEqual(x.domain(), [utc(2009, 0, 1), utc(2009, 0, 1, 23, 48), utc(2009, 0, 3)]); 49 | }); 50 | 51 | it("scaleUtc.nice(interval.every(step)) nices using the specified time interval and step", () => { 52 | const x = scaleUtc().domain([utc(2009, 0, 1, 0, 12), utc(2009, 0, 1, 23, 48)]); 53 | assert.deepStrictEqual(x.nice(utcDay.every(3)).domain(), [utc(2009, 0, 1), utc(2009, 0, 4)]); 54 | assert.deepStrictEqual(x.nice(utcWeek.every(2)).domain(), [utc(2008, 11, 21), utc(2009, 0, 4)]); 55 | assert.deepStrictEqual(x.nice(utcMonth.every(3)).domain(), [utc(2008, 9, 1), utc(2009, 3, 1)]); 56 | assert.deepStrictEqual(x.nice(utcYear.every(10)).domain(), [utc(2000, 0, 1), utc(2010, 0, 1)]); 57 | }); 58 | 59 | it("scaleUtc.copy() isolates changes to the domain", () => { 60 | const x = scaleUtc().domain([utc(2009, 0, 1), utc(2010, 0, 1)]), y = x.copy(); 61 | x.domain([utc(2010, 0, 1), utc(2011, 0, 1)]); 62 | assert.deepStrictEqual(y.domain(), [utc(2009, 0, 1), utc(2010, 0, 1)]); 63 | assert.strictEqual(x(utc(2010, 0, 1)), 0); 64 | assert.strictEqual(y(utc(2010, 0, 1)), 1); 65 | y.domain([utc(2011, 0, 1), utc(2012, 0, 1)]); 66 | assert.strictEqual(x(utc(2011, 0, 1)), 1); 67 | assert.strictEqual(y(utc(2011, 0, 1)), 0); 68 | assert.deepStrictEqual(x.domain(), [utc(2010, 0, 1), utc(2011, 0, 1)]); 69 | assert.deepStrictEqual(y.domain(), [utc(2011, 0, 1), utc(2012, 0, 1)]); 70 | }); 71 | 72 | it("scaleUtc.copy() isolates changes to the range", () => { 73 | const x = scaleUtc().domain([utc(2009, 0, 1), utc(2010, 0, 1)]), y = x.copy(); 74 | x.range([1, 2]); 75 | assert.deepStrictEqual(x.invert(1), utc(2009, 0, 1)); 76 | assert.deepStrictEqual(y.invert(1), utc(2010, 0, 1)); 77 | assert.deepStrictEqual(y.range(), [0, 1]); 78 | y.range([2, 3]); 79 | assert.deepStrictEqual(x.invert(2), utc(2010, 0, 1)); 80 | assert.deepStrictEqual(y.invert(2), utc(2009, 0, 1)); 81 | assert.deepStrictEqual(x.range(), [1, 2]); 82 | assert.deepStrictEqual(y.range(), [2, 3]); 83 | }); 84 | 85 | it("scaleUtc.copy() isolates changes to the interpolator", () => { 86 | const x = scaleUtc().domain([utc(2009, 0, 1), utc(2010, 0, 1)]).range(["red", "blue"]); 87 | const i = x.interpolate(); 88 | const y = x.copy(); 89 | x.interpolate(interpolateHsl); 90 | assert.strictEqual(x(utc(2009, 6, 1)), "rgb(255, 0, 253)"); 91 | assert.strictEqual(y(utc(2009, 6, 1)), "rgb(129, 0, 126)"); 92 | assert.strictEqual(y.interpolate(), i); 93 | }); 94 | 95 | it("scaleUtc.copy() isolates changes to clamping", () => { 96 | const x = scaleUtc().domain([utc(2009, 0, 1), utc(2010, 0, 1)]).clamp(true), y = x.copy(); 97 | x.clamp(false); 98 | assert.strictEqual(x(utc(2011, 0, 1)), 2); 99 | assert.strictEqual(y(utc(2011, 0, 1)), 1); 100 | assert.strictEqual(y.clamp(), true); 101 | y.clamp(false); 102 | assert.strictEqual(x(utc(2011, 0, 1)), 2); 103 | assert.strictEqual(y(utc(2011, 0, 1)), 2); 104 | assert.strictEqual(x.clamp(), false); 105 | }); 106 | 107 | it("scaleUtc.ticks(interval) observes the specified tick interval", () => { 108 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 1, 0), utc(2011, 0, 1, 12, 4, 4)]); 109 | assert.deepStrictEqual(x.ticks(utcMinute), [ 110 | utc(2011, 0, 1, 12, 1), 111 | utc(2011, 0, 1, 12, 2), 112 | utc(2011, 0, 1, 12, 3), 113 | utc(2011, 0, 1, 12, 4) 114 | ]); 115 | }); 116 | 117 | it("scaleUtc.ticks(interval) observes the specified named tick interval", () => { 118 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 1, 0), utc(2011, 0, 1, 12, 4, 4)]); 119 | assert.deepStrictEqual(x.ticks(utcMinute), [ 120 | utc(2011, 0, 1, 12, 1), 121 | utc(2011, 0, 1, 12, 2), 122 | utc(2011, 0, 1, 12, 3), 123 | utc(2011, 0, 1, 12, 4) 124 | ]); 125 | }); 126 | 127 | it("scaleUtc.ticks(interval.every(step)) observes the specified tick interval and step", () => { 128 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 0), utc(2011, 0, 1, 12, 33, 4)]); 129 | assert.deepStrictEqual(x.ticks(utcMinute.every(10)), [ 130 | utc(2011, 0, 1, 12, 0), 131 | utc(2011, 0, 1, 12, 10), 132 | utc(2011, 0, 1, 12, 20), 133 | utc(2011, 0, 1, 12, 30) 134 | ]); 135 | }); 136 | 137 | it("scaleUtc.ticks(count) can generate sub-second ticks", () => { 138 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 0), utc(2011, 0, 1, 12, 0, 1)]); 139 | assert.deepStrictEqual(x.ticks(4), [ 140 | utc(2011, 0, 1, 12, 0, 0, 0), 141 | utc(2011, 0, 1, 12, 0, 0, 200), 142 | utc(2011, 0, 1, 12, 0, 0, 400), 143 | utc(2011, 0, 1, 12, 0, 0, 600), 144 | utc(2011, 0, 1, 12, 0, 0, 800), 145 | utc(2011, 0, 1, 12, 0, 1, 0) 146 | ]); 147 | }); 148 | 149 | it("scaleUtc.ticks(count) can generate 1-second ticks", () => { 150 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 0), utc(2011, 0, 1, 12, 0, 4)]); 151 | assert.deepStrictEqual(x.ticks(4), [ 152 | utc(2011, 0, 1, 12, 0, 0), 153 | utc(2011, 0, 1, 12, 0, 1), 154 | utc(2011, 0, 1, 12, 0, 2), 155 | utc(2011, 0, 1, 12, 0, 3), 156 | utc(2011, 0, 1, 12, 0, 4) 157 | ]); 158 | }); 159 | 160 | it("scaleUtc.ticks(count) can generate 5-second ticks", () => { 161 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 0), utc(2011, 0, 1, 12, 0, 20)]); 162 | assert.deepStrictEqual(x.ticks(4), [ 163 | utc(2011, 0, 1, 12, 0, 0), 164 | utc(2011, 0, 1, 12, 0, 5), 165 | utc(2011, 0, 1, 12, 0, 10), 166 | utc(2011, 0, 1, 12, 0, 15), 167 | utc(2011, 0, 1, 12, 0, 20) 168 | ]); 169 | }); 170 | 171 | it("scaleUtc.ticks(count) can generate 15-second ticks", () => { 172 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 0), utc(2011, 0, 1, 12, 0, 50)]); 173 | assert.deepStrictEqual(x.ticks(4), [ 174 | utc(2011, 0, 1, 12, 0, 0), 175 | utc(2011, 0, 1, 12, 0, 15), 176 | utc(2011, 0, 1, 12, 0, 30), 177 | utc(2011, 0, 1, 12, 0, 45) 178 | ]); 179 | }); 180 | 181 | it("scaleUtc.ticks(count) can generate 30-second ticks", () => { 182 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 0), utc(2011, 0, 1, 12, 1, 50)]); 183 | assert.deepStrictEqual(x.ticks(4), [ 184 | utc(2011, 0, 1, 12, 0, 0), 185 | utc(2011, 0, 1, 12, 0, 30), 186 | utc(2011, 0, 1, 12, 1, 0), 187 | utc(2011, 0, 1, 12, 1, 30) 188 | ]); 189 | }); 190 | 191 | it("scaleUtc.ticks(count) can generate 1-minute ticks", () => { 192 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 0, 27), utc(2011, 0, 1, 12, 4, 12)]); 193 | assert.deepStrictEqual(x.ticks(4), [ 194 | utc(2011, 0, 1, 12, 1), 195 | utc(2011, 0, 1, 12, 2), 196 | utc(2011, 0, 1, 12, 3), 197 | utc(2011, 0, 1, 12, 4) 198 | ]); 199 | }); 200 | 201 | it("scaleUtc.ticks(count) can generate 5-minute ticks", () => { 202 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 3, 27), utc(2011, 0, 1, 12, 21, 12)]); 203 | assert.deepStrictEqual(x.ticks(4), [ 204 | utc(2011, 0, 1, 12, 5), 205 | utc(2011, 0, 1, 12, 10), 206 | utc(2011, 0, 1, 12, 15), 207 | utc(2011, 0, 1, 12, 20) 208 | ]); 209 | }); 210 | 211 | it("scaleUtc.ticks(count) can generate 15-minute ticks", () => { 212 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 8, 27), utc(2011, 0, 1, 13, 4, 12)]); 213 | assert.deepStrictEqual(x.ticks(4), [ 214 | utc(2011, 0, 1, 12, 15), 215 | utc(2011, 0, 1, 12, 30), 216 | utc(2011, 0, 1, 12, 45), 217 | utc(2011, 0, 1, 13, 0) 218 | ]); 219 | }); 220 | 221 | it("scaleUtc.ticks(count) can generate 30-minute ticks", () => { 222 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 28, 27), utc(2011, 0, 1, 14, 4, 12)]); 223 | assert.deepStrictEqual(x.ticks(4), [ 224 | utc(2011, 0, 1, 12, 30), 225 | utc(2011, 0, 1, 13, 0), 226 | utc(2011, 0, 1, 13, 30), 227 | utc(2011, 0, 1, 14, 0) 228 | ]); 229 | }); 230 | 231 | it("scaleUtc.ticks(count) can generate 1-hour ticks", () => { 232 | const x = scaleUtc().domain([utc(2011, 0, 1, 12, 28, 27), utc(2011, 0, 1, 16, 34, 12)]); 233 | assert.deepStrictEqual(x.ticks(4), [ 234 | utc(2011, 0, 1, 13, 0), 235 | utc(2011, 0, 1, 14, 0), 236 | utc(2011, 0, 1, 15, 0), 237 | utc(2011, 0, 1, 16, 0) 238 | ]); 239 | }); 240 | 241 | it("scaleUtc.ticks(count) can generate 3-hour ticks", () => { 242 | const x = scaleUtc().domain([utc(2011, 0, 1, 14, 28, 27), utc(2011, 0, 2, 1, 34, 12)]); 243 | assert.deepStrictEqual(x.ticks(4), [ 244 | utc(2011, 0, 1, 15, 0), 245 | utc(2011, 0, 1, 18, 0), 246 | utc(2011, 0, 1, 21, 0), 247 | utc(2011, 0, 2, 0, 0) 248 | ]); 249 | }); 250 | 251 | it("scaleUtc.ticks(count) can generate 6-hour ticks", () => { 252 | const x = scaleUtc().domain([utc(2011, 0, 1, 16, 28, 27), utc(2011, 0, 2, 14, 34, 12)]); 253 | assert.deepStrictEqual(x.ticks(4), [ 254 | utc(2011, 0, 1, 18, 0), 255 | utc(2011, 0, 2, 0, 0), 256 | utc(2011, 0, 2, 6, 0), 257 | utc(2011, 0, 2, 12, 0) 258 | ]); 259 | }); 260 | 261 | it("scaleUtc.ticks(count) can generate 12-hour ticks", () => { 262 | const x = scaleUtc().domain([utc(2011, 0, 1, 16, 28, 27), utc(2011, 0, 3, 21, 34, 12)]); 263 | assert.deepStrictEqual(x.ticks(4), [ 264 | utc(2011, 0, 2, 0, 0), 265 | utc(2011, 0, 2, 12, 0), 266 | utc(2011, 0, 3, 0, 0), 267 | utc(2011, 0, 3, 12, 0) 268 | ]); 269 | }); 270 | 271 | it("scaleUtc.ticks(count) can generate 1-day ticks", () => { 272 | const x = scaleUtc().domain([utc(2011, 0, 1, 16, 28, 27), utc(2011, 0, 5, 21, 34, 12)]); 273 | assert.deepStrictEqual(x.ticks(4), [ 274 | utc(2011, 0, 2, 0, 0), 275 | utc(2011, 0, 3, 0, 0), 276 | utc(2011, 0, 4, 0, 0), 277 | utc(2011, 0, 5, 0, 0) 278 | ]); 279 | }); 280 | 281 | it("scaleUtc.ticks(count) can generate 2-day ticks", () => { 282 | const x = scaleUtc().domain([utc(2011, 0, 2, 16, 28, 27), utc(2011, 0, 9, 21, 34, 12)]); 283 | assert.deepStrictEqual(x.ticks(4), [ 284 | utc(2011, 0, 3, 0, 0), 285 | utc(2011, 0, 5, 0, 0), 286 | utc(2011, 0, 7, 0, 0), 287 | utc(2011, 0, 9, 0, 0) 288 | ]); 289 | }); 290 | 291 | it("scaleUtc.ticks(count) can generate 1-week ticks", () => { 292 | const x = scaleUtc().domain([utc(2011, 0, 1, 16, 28, 27), utc(2011, 0, 23, 21, 34, 12)]); 293 | assert.deepStrictEqual(x.ticks(4), [ 294 | utc(2011, 0, 2, 0, 0), 295 | utc(2011, 0, 9, 0, 0), 296 | utc(2011, 0, 16, 0, 0), 297 | utc(2011, 0, 23, 0, 0) 298 | ]); 299 | }); 300 | 301 | it("scaleUtc.ticks(count) can generate 1-month ticks", () => { 302 | const x = scaleUtc().domain([utc(2011, 0, 18), utc(2011, 4, 2)]); 303 | assert.deepStrictEqual(x.ticks(4), [ 304 | utc(2011, 1, 1, 0, 0), 305 | utc(2011, 2, 1, 0, 0), 306 | utc(2011, 3, 1, 0, 0), 307 | utc(2011, 4, 1, 0, 0) 308 | ]); 309 | }); 310 | 311 | it("scaleUtc.ticks(count) can generate 3-month ticks", () => { 312 | const x = scaleUtc().domain([utc(2010, 11, 18), utc(2011, 10, 2)]); 313 | assert.deepStrictEqual(x.ticks(4), [ 314 | utc(2011, 0, 1, 0, 0), 315 | utc(2011, 3, 1, 0, 0), 316 | utc(2011, 6, 1, 0, 0), 317 | utc(2011, 9, 1, 0, 0) 318 | ]); 319 | }); 320 | 321 | it("scaleUtc.ticks(count) can generate 1-year ticks", () => { 322 | const x = scaleUtc().domain([utc(2010, 11, 18), utc(2014, 2, 2)]); 323 | assert.deepStrictEqual(x.ticks(4), [ 324 | utc(2011, 0, 1, 0, 0), 325 | utc(2012, 0, 1, 0, 0), 326 | utc(2013, 0, 1, 0, 0), 327 | utc(2014, 0, 1, 0, 0) 328 | ]); 329 | }); 330 | 331 | it("scaleUtc.ticks(count) can generate multi-year ticks", () => { 332 | const x = scaleUtc().domain([utc(0, 11, 18), utc(2014, 2, 2)]); 333 | assert.deepStrictEqual(x.ticks(6), [ 334 | utc( 500, 0, 1, 0, 0), 335 | utc(1000, 0, 1, 0, 0), 336 | utc(1500, 0, 1, 0, 0), 337 | utc(2000, 0, 1, 0, 0) 338 | ]); 339 | }); 340 | 341 | it("scaleUtc.ticks(count) returns one tick for an empty domain", () => { 342 | const x = scaleUtc().domain([utc(2014, 2, 2), utc(2014, 2, 2)]); 343 | assert.deepStrictEqual(x.ticks(6), [utc(2014, 2, 2)]); 344 | }); 345 | 346 | it("scaleUtc.tickFormat()(date) formats year on New Year's", () => { 347 | const f = scaleUtc().tickFormat(); 348 | assert.strictEqual(f(utc(2011, 0, 1)), "2011"); 349 | assert.strictEqual(f(utc(2012, 0, 1)), "2012"); 350 | assert.strictEqual(f(utc(2013, 0, 1)), "2013"); 351 | }); 352 | 353 | it("scaleUtc.tickFormat()(date) formats month on the 1st of each month", () => { 354 | const f = scaleUtc().tickFormat(); 355 | assert.strictEqual(f(utc(2011, 1, 1)), "February"); 356 | assert.strictEqual(f(utc(2011, 2, 1)), "March"); 357 | assert.strictEqual(f(utc(2011, 3, 1)), "April"); 358 | }); 359 | 360 | it("scaleUtc.tickFormat()(date) formats week on Sunday midnight", () => { 361 | const f = scaleUtc().tickFormat(); 362 | assert.strictEqual(f(utc(2011, 1, 6)), "Feb 06"); 363 | assert.strictEqual(f(utc(2011, 1, 13)), "Feb 13"); 364 | assert.strictEqual(f(utc(2011, 1, 20)), "Feb 20"); 365 | }); 366 | 367 | it("scaleUtc.tickFormat()(date) formats date on midnight", () => { 368 | const f = scaleUtc().tickFormat(); 369 | assert.strictEqual(f(utc(2011, 1, 2)), "Wed 02"); 370 | assert.strictEqual(f(utc(2011, 1, 3)), "Thu 03"); 371 | assert.strictEqual(f(utc(2011, 1, 4)), "Fri 04"); 372 | }); 373 | 374 | it("scaleUtc.tickFormat()(date) formats hour on minute zero", () => { 375 | const f = scaleUtc().tickFormat(); 376 | assert.strictEqual(f(utc(2011, 1, 2, 11)), "11 AM"); 377 | assert.strictEqual(f(utc(2011, 1, 2, 12)), "12 PM"); 378 | assert.strictEqual(f(utc(2011, 1, 2, 13)), "01 PM"); 379 | }); 380 | 381 | it("scaleUtc.tickFormat()(date) formats minute on second zero", () => { 382 | const f = scaleUtc().tickFormat(); 383 | assert.strictEqual(f(utc(2011, 1, 2, 11, 59)), "11:59"); 384 | assert.strictEqual(f(utc(2011, 1, 2, 12, 1)), "12:01"); 385 | assert.strictEqual(f(utc(2011, 1, 2, 12, 2)), "12:02"); 386 | }); 387 | 388 | it("scaleUtc.tickFormat()(date) otherwise, formats second", () => { 389 | const f = scaleUtc().tickFormat(); 390 | assert.strictEqual(f(utc(2011, 1, 2, 12, 1, 9)), ":09"); 391 | assert.strictEqual(f(utc(2011, 1, 2, 12, 1, 10)), ":10"); 392 | assert.strictEqual(f(utc(2011, 1, 2, 12, 1, 11)), ":11"); 393 | }); 394 | 395 | it("scaleUtc.tickFormat(count, specifier) returns a time format for the specified specifier", () => { 396 | const f = scaleUtc().tickFormat(10, "%c"); 397 | assert.strictEqual(f(utc(2011, 1, 2, 12)), "2/2/2011, 12:00:00 PM"); 398 | }); 399 | -------------------------------------------------------------------------------- /test/time-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {interpolateHsl} from "d3-interpolate"; 3 | import {timeDay, timeMinute, timeMonth, timeWeek, timeYear} from "d3-time"; 4 | import {scaleTime} from "../src/index.js"; 5 | import {local} from "./date.js"; 6 | 7 | it("time.domain([-1e50, 1e50]) is equivalent to time.domain([NaN, NaN])", () => { 8 | const x = scaleTime().domain([-1e50, 1e50]); 9 | assert.strictEqual(isNaN(x.domain()[0]), true); // Note: also coerced on retrieval, so insufficient test! 10 | assert.strictEqual(isNaN(x.domain()[1]), true); 11 | assert.deepStrictEqual(x.ticks(10), []); 12 | }); 13 | 14 | it("time.domain(domain) accepts an iterable", () => { 15 | const x = scaleTime().domain(new Set([local(2009), local(2010)])); 16 | assert.deepStrictEqual(x.domain(), [local(2009), local(2010)]); 17 | }); 18 | 19 | it("time.nice() is an alias for time.nice(10)", () => { 20 | const x = scaleTime().domain([local(2009, 0, 1, 0, 17), local(2009, 0, 1, 23, 42)]); 21 | assert.deepStrictEqual(x.nice().domain(), [local(2009, 0, 1), local(2009, 0, 2)]); 22 | }); 23 | 24 | it("time.nice() can nice sub-second domains", () => { 25 | const x = scaleTime().domain([local(2013, 0, 1, 12, 0, 0, 0), local(2013, 0, 1, 12, 0, 0, 128)]); 26 | assert.deepStrictEqual(x.nice().domain(), [local(2013, 0, 1, 12, 0, 0, 0), local(2013, 0, 1, 12, 0, 0, 130)]); 27 | }); 28 | 29 | it("time.nice() can nice multi-year domains", () => { 30 | const x = scaleTime().domain([local(2001, 0, 1), local(2138, 0, 1)]); 31 | assert.deepStrictEqual(x.nice().domain(), [local(2000, 0, 1), local(2140, 0, 1)]); 32 | }); 33 | 34 | it("time.nice() can nice empty domains", () => { 35 | const x = scaleTime().domain([local(2009, 0, 1, 0, 12), local(2009, 0, 1, 0, 12)]); 36 | assert.deepStrictEqual(x.nice().domain(), [local(2009, 0, 1, 0, 12), local(2009, 0, 1, 0, 12)]); 37 | }); 38 | 39 | it("time.nice(count) nices using the specified tick count", () => { 40 | const x = scaleTime().domain([local(2009, 0, 1, 0, 17), local(2009, 0, 1, 23, 42)]); 41 | assert.deepStrictEqual(x.nice(100).domain(), [local(2009, 0, 1, 0, 15), local(2009, 0, 1, 23, 45)]); 42 | assert.deepStrictEqual(x.nice(10).domain(), [local(2009, 0, 1), local(2009, 0, 2)]); 43 | }); 44 | 45 | it("time.nice(interval) nices using the specified time interval", () => { 46 | const x = scaleTime().domain([local(2009, 0, 1, 0, 12), local(2009, 0, 1, 23, 48)]); 47 | assert.deepStrictEqual(x.nice(timeDay).domain(), [local(2009, 0, 1), local(2009, 0, 2)]); 48 | assert.deepStrictEqual(x.nice(timeWeek).domain(), [local(2008, 11, 28), local(2009, 0, 4)]); 49 | assert.deepStrictEqual(x.nice(timeMonth).domain(), [local(2008, 11, 1), local(2009, 1, 1)]); 50 | assert.deepStrictEqual(x.nice(timeYear).domain(), [local(2008, 0, 1), local(2010, 0, 1)]); 51 | }); 52 | 53 | it("time.nice(interval) can nice empty domains", () => { 54 | const x = scaleTime().domain([local(2009, 0, 1, 0, 12), local(2009, 0, 1, 0, 12)]); 55 | assert.deepStrictEqual(x.nice(timeDay).domain(), [local(2009, 0, 1), local(2009, 0, 2)]); 56 | }); 57 | 58 | it("time.nice(interval) can nice a polylinear domain, only affecting its extent", () => { 59 | const x = scaleTime().domain([local(2009, 0, 1, 0, 12), local(2009, 0, 1, 23, 48), local(2009, 0, 2, 23, 48)]).nice(timeDay); 60 | assert.deepStrictEqual(x.domain(), [local(2009, 0, 1), local(2009, 0, 1, 23, 48), local(2009, 0, 3)]); 61 | }); 62 | 63 | it("time.nice(interval.every(step)) nices using the specified time interval and step", () => { 64 | const x = scaleTime().domain([local(2009, 0, 1, 0, 12), local(2009, 0, 1, 23, 48)]); 65 | assert.deepStrictEqual(x.nice(timeDay.every(3)).domain(), [local(2009, 0, 1), local(2009, 0, 4)]); 66 | assert.deepStrictEqual(x.nice(timeWeek.every(2)).domain(), [local(2008, 11, 21), local(2009, 0, 4)]); 67 | assert.deepStrictEqual(x.nice(timeMonth.every(3)).domain(), [local(2008, 9, 1), local(2009, 3, 1)]); 68 | assert.deepStrictEqual(x.nice(timeYear.every(10)).domain(), [local(2000, 0, 1), local(2010, 0, 1)]); 69 | }); 70 | 71 | it("time.copy() isolates changes to the domain", () => { 72 | const x = scaleTime().domain([local(2009, 0, 1), local(2010, 0, 1)]), y = x.copy(); 73 | x.domain([local(2010, 0, 1), local(2011, 0, 1)]); 74 | assert.deepStrictEqual(y.domain(), [local(2009, 0, 1), local(2010, 0, 1)]); 75 | assert.strictEqual(x(local(2010, 0, 1)), 0); 76 | assert.strictEqual(y(local(2010, 0, 1)), 1); 77 | y.domain([local(2011, 0, 1), local(2012, 0, 1)]); 78 | assert.strictEqual(x(local(2011, 0, 1)), 1); 79 | assert.strictEqual(y(local(2011, 0, 1)), 0); 80 | assert.deepStrictEqual(x.domain(), [local(2010, 0, 1), local(2011, 0, 1)]); 81 | assert.deepStrictEqual(y.domain(), [local(2011, 0, 1), local(2012, 0, 1)]); 82 | }); 83 | 84 | it("time.copy() isolates changes to the range", () => { 85 | const x = scaleTime().domain([local(2009, 0, 1), local(2010, 0, 1)]), y = x.copy(); 86 | x.range([1, 2]); 87 | assert.deepStrictEqual(x.invert(1), local(2009, 0, 1)); 88 | assert.deepStrictEqual(y.invert(1), local(2010, 0, 1)); 89 | assert.deepStrictEqual(y.range(), [0, 1]); 90 | y.range([2, 3]); 91 | assert.deepStrictEqual(x.invert(2), local(2010, 0, 1)); 92 | assert.deepStrictEqual(y.invert(2), local(2009, 0, 1)); 93 | assert.deepStrictEqual(x.range(), [1, 2]); 94 | assert.deepStrictEqual(y.range(), [2, 3]); 95 | }); 96 | 97 | it("time.copy() isolates changes to the interpolator", () => { 98 | const x = scaleTime().domain([local(2009, 0, 1), local(2010, 0, 1)]).range(["red", "blue"]), 99 | i = x.interpolate(), 100 | y = x.copy(); 101 | x.interpolate(interpolateHsl); 102 | assert.strictEqual(x(local(2009, 6, 1)), "rgb(255, 0, 253)"); 103 | assert.strictEqual(y(local(2009, 6, 1)), "rgb(129, 0, 126)"); 104 | assert.strictEqual(y.interpolate(), i); 105 | }); 106 | 107 | it("time.copy() isolates changes to clamping", () => { 108 | const x = scaleTime().domain([local(2009, 0, 1), local(2010, 0, 1)]).clamp(true), y = x.copy(); 109 | x.clamp(false); 110 | assert.strictEqual(x(local(2011, 0, 1)), 2); 111 | assert.strictEqual(y(local(2011, 0, 1)), 1); 112 | assert.strictEqual(y.clamp(), true); 113 | y.clamp(false); 114 | assert.strictEqual(x(local(2011, 0, 1)), 2); 115 | assert.strictEqual(y(local(2011, 0, 1)), 2); 116 | assert.strictEqual(x.clamp(), false); 117 | }); 118 | 119 | it("time.clamp(true).invert(value) never returns a value outside the domain", () => { 120 | const x = scaleTime().clamp(true); 121 | assert(x.invert(0) instanceof Date); 122 | assert(x.invert(0) !== x.invert(0)); // returns a distinct copy 123 | assert.strictEqual(+x.invert(-1), +x.domain()[0]); 124 | assert.strictEqual(+x.invert(0), +x.domain()[0]); 125 | assert.strictEqual(+x.invert(1), +x.domain()[1]); 126 | assert.strictEqual(+x.invert(2), +x.domain()[1]); 127 | }); 128 | 129 | it("time.ticks(interval) observes the specified tick interval", () => { 130 | const x = scaleTime().domain([local(2011, 0, 1, 12, 1, 0), local(2011, 0, 1, 12, 4, 4)]); 131 | assert.deepStrictEqual(x.ticks(timeMinute), [ 132 | local(2011, 0, 1, 12, 1), 133 | local(2011, 0, 1, 12, 2), 134 | local(2011, 0, 1, 12, 3), 135 | local(2011, 0, 1, 12, 4) 136 | ]); 137 | }); 138 | 139 | it("time.ticks(interval.every(step)) observes the specified tick interval and step", () => { 140 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 0), local(2011, 0, 1, 12, 33, 4)]); 141 | assert.deepStrictEqual(x.ticks(timeMinute.every(10)), [ 142 | local(2011, 0, 1, 12, 0), 143 | local(2011, 0, 1, 12, 10), 144 | local(2011, 0, 1, 12, 20), 145 | local(2011, 0, 1, 12, 30) 146 | ]); 147 | }); 148 | 149 | it("time.ticks(count) can generate sub-second ticks", () => { 150 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 0), local(2011, 0, 1, 12, 0, 1)]); 151 | assert.deepStrictEqual(x.ticks(4), [ 152 | local(2011, 0, 1, 12, 0, 0, 0), 153 | local(2011, 0, 1, 12, 0, 0, 200), 154 | local(2011, 0, 1, 12, 0, 0, 400), 155 | local(2011, 0, 1, 12, 0, 0, 600), 156 | local(2011, 0, 1, 12, 0, 0, 800), 157 | local(2011, 0, 1, 12, 0, 1, 0) 158 | ]); 159 | }); 160 | 161 | it("time.ticks(count) can generate 1-second ticks", () => { 162 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 0), local(2011, 0, 1, 12, 0, 4)]); 163 | assert.deepStrictEqual(x.ticks(4), [ 164 | local(2011, 0, 1, 12, 0, 0), 165 | local(2011, 0, 1, 12, 0, 1), 166 | local(2011, 0, 1, 12, 0, 2), 167 | local(2011, 0, 1, 12, 0, 3), 168 | local(2011, 0, 1, 12, 0, 4) 169 | ]); 170 | }); 171 | 172 | it("time.ticks(count) can generate 5-second ticks", () => { 173 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 0), local(2011, 0, 1, 12, 0, 20)]); 174 | assert.deepStrictEqual(x.ticks(4), [ 175 | local(2011, 0, 1, 12, 0, 0), 176 | local(2011, 0, 1, 12, 0, 5), 177 | local(2011, 0, 1, 12, 0, 10), 178 | local(2011, 0, 1, 12, 0, 15), 179 | local(2011, 0, 1, 12, 0, 20) 180 | ]); 181 | }); 182 | 183 | it("time.ticks(count) can generate 15-second ticks", () => { 184 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 0), local(2011, 0, 1, 12, 0, 50)]); 185 | assert.deepStrictEqual(x.ticks(4), [ 186 | local(2011, 0, 1, 12, 0, 0), 187 | local(2011, 0, 1, 12, 0, 15), 188 | local(2011, 0, 1, 12, 0, 30), 189 | local(2011, 0, 1, 12, 0, 45) 190 | ]); 191 | }); 192 | 193 | it("time.ticks(count) can generate 30-second ticks", () => { 194 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 0), local(2011, 0, 1, 12, 1, 50)]); 195 | assert.deepStrictEqual(x.ticks(4), [ 196 | local(2011, 0, 1, 12, 0, 0), 197 | local(2011, 0, 1, 12, 0, 30), 198 | local(2011, 0, 1, 12, 1, 0), 199 | local(2011, 0, 1, 12, 1, 30) 200 | ]); 201 | }); 202 | 203 | it("time.ticks(count) can generate 1-minute ticks", () => { 204 | const x = scaleTime().domain([local(2011, 0, 1, 12, 0, 27), local(2011, 0, 1, 12, 4, 12)]); 205 | assert.deepStrictEqual(x.ticks(4), [ 206 | local(2011, 0, 1, 12, 1), 207 | local(2011, 0, 1, 12, 2), 208 | local(2011, 0, 1, 12, 3), 209 | local(2011, 0, 1, 12, 4) 210 | ]); 211 | }); 212 | 213 | it("time.ticks(count) can generate 5-minute ticks", () => { 214 | const x = scaleTime().domain([local(2011, 0, 1, 12, 3, 27), local(2011, 0, 1, 12, 21, 12)]); 215 | assert.deepStrictEqual(x.ticks(4), [ 216 | local(2011, 0, 1, 12, 5), 217 | local(2011, 0, 1, 12, 10), 218 | local(2011, 0, 1, 12, 15), 219 | local(2011, 0, 1, 12, 20) 220 | ]); 221 | }); 222 | 223 | it("time.ticks(count) can generate 15-minute ticks", () => { 224 | const x = scaleTime().domain([local(2011, 0, 1, 12, 8, 27), local(2011, 0, 1, 13, 4, 12)]); 225 | assert.deepStrictEqual(x.ticks(4), [ 226 | local(2011, 0, 1, 12, 15), 227 | local(2011, 0, 1, 12, 30), 228 | local(2011, 0, 1, 12, 45), 229 | local(2011, 0, 1, 13, 0) 230 | ]); 231 | }); 232 | 233 | it("time.ticks(count) can generate 30-minute ticks", () => { 234 | const x = scaleTime().domain([local(2011, 0, 1, 12, 28, 27), local(2011, 0, 1, 14, 4, 12)]); 235 | assert.deepStrictEqual(x.ticks(4), [ 236 | local(2011, 0, 1, 12, 30), 237 | local(2011, 0, 1, 13, 0), 238 | local(2011, 0, 1, 13, 30), 239 | local(2011, 0, 1, 14, 0) 240 | ]); 241 | }); 242 | 243 | it("time.ticks(count) can generate 1-hour ticks", () => { 244 | const x = scaleTime().domain([local(2011, 0, 1, 12, 28, 27), local(2011, 0, 1, 16, 34, 12)]); 245 | assert.deepStrictEqual(x.ticks(4), [ 246 | local(2011, 0, 1, 13, 0), 247 | local(2011, 0, 1, 14, 0), 248 | local(2011, 0, 1, 15, 0), 249 | local(2011, 0, 1, 16, 0) 250 | ]); 251 | }); 252 | 253 | it("time.ticks(count) can generate 3-hour ticks", () => { 254 | const x = scaleTime().domain([local(2011, 0, 1, 14, 28, 27), local(2011, 0, 2, 1, 34, 12)]); 255 | assert.deepStrictEqual(x.ticks(4), [ 256 | local(2011, 0, 1, 15, 0), 257 | local(2011, 0, 1, 18, 0), 258 | local(2011, 0, 1, 21, 0), 259 | local(2011, 0, 2, 0, 0) 260 | ]); 261 | }); 262 | 263 | it("time.ticks(count) can generate 6-hour ticks", () => { 264 | const x = scaleTime().domain([local(2011, 0, 1, 16, 28, 27), local(2011, 0, 2, 14, 34, 12)]); 265 | assert.deepStrictEqual(x.ticks(4), [ 266 | local(2011, 0, 1, 18, 0), 267 | local(2011, 0, 2, 0, 0), 268 | local(2011, 0, 2, 6, 0), 269 | local(2011, 0, 2, 12, 0) 270 | ]); 271 | }); 272 | 273 | it("time.ticks(count) can generate 12-hour ticks", () => { 274 | const x = scaleTime().domain([local(2011, 0, 1, 16, 28, 27), local(2011, 0, 3, 21, 34, 12)]); 275 | assert.deepStrictEqual(x.ticks(4), [ 276 | local(2011, 0, 2, 0, 0), 277 | local(2011, 0, 2, 12, 0), 278 | local(2011, 0, 3, 0, 0), 279 | local(2011, 0, 3, 12, 0) 280 | ]); 281 | }); 282 | 283 | it("time.ticks(count) can generate 1-day ticks", () => { 284 | const x = scaleTime().domain([local(2011, 0, 1, 16, 28, 27), local(2011, 0, 5, 21, 34, 12)]); 285 | assert.deepStrictEqual(x.ticks(4), [ 286 | local(2011, 0, 2, 0, 0), 287 | local(2011, 0, 3, 0, 0), 288 | local(2011, 0, 4, 0, 0), 289 | local(2011, 0, 5, 0, 0) 290 | ]); 291 | }); 292 | 293 | it("time.ticks(count) can generate 2-day ticks", () => { 294 | const x = scaleTime().domain([local(2011, 0, 2, 16, 28, 27), local(2011, 0, 9, 21, 34, 12)]); 295 | assert.deepStrictEqual(x.ticks(4), [ 296 | local(2011, 0, 3, 0, 0), 297 | local(2011, 0, 5, 0, 0), 298 | local(2011, 0, 7, 0, 0), 299 | local(2011, 0, 9, 0, 0) 300 | ]); 301 | }); 302 | 303 | it("time.ticks(count) can generate 1-week ticks", () => { 304 | const x = scaleTime().domain([local(2011, 0, 1, 16, 28, 27), local(2011, 0, 23, 21, 34, 12)]); 305 | assert.deepStrictEqual(x.ticks(4), [ 306 | local(2011, 0, 2, 0, 0), 307 | local(2011, 0, 9, 0, 0), 308 | local(2011, 0, 16, 0, 0), 309 | local(2011, 0, 23, 0, 0) 310 | ]); 311 | }); 312 | 313 | it("time.ticks(count) can generate 1-month ticks", () => { 314 | const x = scaleTime().domain([local(2011, 0, 18), local(2011, 4, 2)]); 315 | assert.deepStrictEqual(x.ticks(4), [ 316 | local(2011, 1, 1, 0, 0), 317 | local(2011, 2, 1, 0, 0), 318 | local(2011, 3, 1, 0, 0), 319 | local(2011, 4, 1, 0, 0) 320 | ]); 321 | }); 322 | 323 | it("time.ticks(count) can generate 3-month ticks", () => { 324 | const x = scaleTime().domain([local(2010, 11, 18), local(2011, 10, 2)]); 325 | assert.deepStrictEqual(x.ticks(4), [ 326 | local(2011, 0, 1, 0, 0), 327 | local(2011, 3, 1, 0, 0), 328 | local(2011, 6, 1, 0, 0), 329 | local(2011, 9, 1, 0, 0) 330 | ]); 331 | }); 332 | 333 | it("time.ticks(count) can generate 1-year ticks", () => { 334 | const x = scaleTime().domain([local(2010, 11, 18), local(2014, 2, 2)]); 335 | assert.deepStrictEqual(x.ticks(4), [ 336 | local(2011, 0, 1, 0, 0), 337 | local(2012, 0, 1, 0, 0), 338 | local(2013, 0, 1, 0, 0), 339 | local(2014, 0, 1, 0, 0) 340 | ]); 341 | }); 342 | 343 | it("time.ticks(count) can generate multi-year ticks", () => { 344 | const x = scaleTime().domain([local(0, 11, 18), local(2014, 2, 2)]); 345 | assert.deepStrictEqual(x.ticks(6), [ 346 | local( 500, 0, 1, 0, 0), 347 | local(1000, 0, 1, 0, 0), 348 | local(1500, 0, 1, 0, 0), 349 | local(2000, 0, 1, 0, 0) 350 | ]); 351 | }); 352 | 353 | it("time.ticks(count) returns one tick for an empty domain", () => { 354 | const x = scaleTime().domain([local(2014, 2, 2), local(2014, 2, 2)]); 355 | assert.deepStrictEqual(x.ticks(6), [local(2014, 2, 2)]); 356 | }); 357 | 358 | it("time.ticks() returns descending ticks for a descending domain", () => { 359 | const x = scaleTime(); 360 | assert.deepStrictEqual(x.domain([local(2014, 2, 2), local(2010, 11, 18)]).ticks(4), [local(2014, 0, 1, 0, 0), local(2013, 0, 1, 0, 0), local(2012, 0, 1, 0, 0), local(2011, 0, 1, 0, 0)]); 361 | assert.deepStrictEqual(x.domain([local(2011, 10, 2), local(2010, 11, 18)]).ticks(4), [local(2011, 9, 1, 0, 0), local(2011, 6, 1, 0, 0), local(2011, 3, 1, 0, 0), local(2011, 0, 1, 0, 0)]); 362 | }); 363 | 364 | it("time.tickFormat()(date) formats year on New Year's", () => { 365 | const f = scaleTime().tickFormat(); 366 | assert.strictEqual(f(local(2011, 0, 1)), "2011"); 367 | assert.strictEqual(f(local(2012, 0, 1)), "2012"); 368 | assert.strictEqual(f(local(2013, 0, 1)), "2013"); 369 | }); 370 | 371 | it("time.tickFormat()(date) formats month on the 1st of each month", () => { 372 | const f = scaleTime().tickFormat(); 373 | assert.strictEqual(f(local(2011, 1, 1)), "February"); 374 | assert.strictEqual(f(local(2011, 2, 1)), "March"); 375 | assert.strictEqual(f(local(2011, 3, 1)), "April"); 376 | }); 377 | 378 | it("time.tickFormat()(date) formats week on Sunday midnight", () => { 379 | const f = scaleTime().tickFormat(); 380 | assert.strictEqual(f(local(2011, 1, 6)), "Feb 06"); 381 | assert.strictEqual(f(local(2011, 1, 13)), "Feb 13"); 382 | assert.strictEqual(f(local(2011, 1, 20)), "Feb 20"); 383 | }); 384 | 385 | it("time.tickFormat()(date) formats date on midnight", () => { 386 | const f = scaleTime().tickFormat(); 387 | assert.strictEqual(f(local(2011, 1, 2)), "Wed 02"); 388 | assert.strictEqual(f(local(2011, 1, 3)), "Thu 03"); 389 | assert.strictEqual(f(local(2011, 1, 4)), "Fri 04"); 390 | }); 391 | 392 | it("time.tickFormat()(date) formats hour on minute zero", () => { 393 | const f = scaleTime().tickFormat(); 394 | assert.strictEqual(f(local(2011, 1, 2, 11)), "11 AM"); 395 | assert.strictEqual(f(local(2011, 1, 2, 12)), "12 PM"); 396 | assert.strictEqual(f(local(2011, 1, 2, 13)), "01 PM"); 397 | }); 398 | 399 | it("time.tickFormat()(date) formats minute on second zero", () => { 400 | const f = scaleTime().tickFormat(); 401 | assert.strictEqual(f(local(2011, 1, 2, 11, 59)), "11:59"); 402 | assert.strictEqual(f(local(2011, 1, 2, 12, 1)), "12:01"); 403 | assert.strictEqual(f(local(2011, 1, 2, 12, 2)), "12:02"); 404 | }); 405 | 406 | it("time.tickFormat()(date) otherwise, formats second", () => { 407 | const f = scaleTime().tickFormat(); 408 | assert.strictEqual(f(local(2011, 1, 2, 12, 1, 9)), ":09"); 409 | assert.strictEqual(f(local(2011, 1, 2, 12, 1, 10)), ":10"); 410 | assert.strictEqual(f(local(2011, 1, 2, 12, 1, 11)), ":11"); 411 | }); 412 | 413 | it("time.tickFormat(count, specifier) returns a time format for the specified specifier", () => { 414 | const f = scaleTime().tickFormat(10, "%c"); 415 | assert.strictEqual(f(local(2011, 1, 2, 12)), "2/2/2011, 12:00:00 PM"); 416 | }); 417 | -------------------------------------------------------------------------------- /test/log-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {hsl, rgb} from "d3-color"; 3 | import {interpolate, interpolateHsl} from "d3-interpolate"; 4 | import {format} from "d3-format"; 5 | import {scaleLog} from "../src/index.js"; 6 | import {assertInDelta} from "./asserts.js"; 7 | 8 | it("scaleLog() has the expected defaults", () => { 9 | const x = scaleLog(); 10 | assert.deepStrictEqual(x.domain(), [1, 10]); 11 | assert.deepStrictEqual(x.range(), [0, 1]); 12 | assert.strictEqual(x.clamp(), false); 13 | assert.strictEqual(x.base(), 10); 14 | assert.strictEqual(x.interpolate(), interpolate); 15 | assert.deepStrictEqual(x.interpolate()({array: ["red"]}, {array: ["blue"]})(0.5), {array: ["rgb(128, 0, 128)"]}); 16 | assertInDelta(x(5), 0.69897); 17 | assertInDelta(x.invert(0.69897), 5); 18 | assertInDelta(x(3.162278), 0.5); 19 | assertInDelta(x.invert(0.5), 3.162278); 20 | }); 21 | 22 | it("log.domain(…) coerces values to numbers", () => { 23 | const x = scaleLog().domain([new Date(1990, 0, 1), new Date(1991, 0, 1)]); 24 | assert.strictEqual(typeof x.domain()[0], "number"); 25 | assert.strictEqual(typeof x.domain()[1], "number"); 26 | assertInDelta(x(new Date(1989, 9, 20)), -0.2061048); 27 | assertInDelta(x(new Date(1990, 0, 1)), 0.0000000); 28 | assertInDelta(x(new Date(1990, 2, 15)), 0.2039385); 29 | assertInDelta(x(new Date(1990, 4, 27)), 0.4057544); 30 | assertInDelta(x(new Date(1991, 0, 1)), 1.0000000); 31 | assertInDelta(x(new Date(1991, 2, 15)), 1.1942797); 32 | x.domain(["1", "10"]); 33 | assert.strictEqual(typeof x.domain()[0], "number"); 34 | assert.strictEqual(typeof x.domain()[1], "number"); 35 | assertInDelta(x(5), 0.69897); 36 | x.domain([new Number(1), new Number(10)]); 37 | assert.strictEqual(typeof x.domain()[0], "number"); 38 | assert.strictEqual(typeof x.domain()[1], "number"); 39 | assertInDelta(x(5), 0.69897); 40 | }); 41 | 42 | it("log.domain(…) can take negative values", () => { 43 | const x = scaleLog().domain([-100, -1]); 44 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(Infinity)), [ 45 | "−100", 46 | "−90", "−80", "−70", "−60", "−50", "−40", "−30", "−20", "−10", 47 | "−9", "−8", "−7", "−6", "−5", "−4", "−3", "−2", "−1" 48 | ]); 49 | assertInDelta(x(-50), 0.150515); 50 | }); 51 | 52 | it("log.domain(…).range(…) can take more than two values", () => { 53 | const x = scaleLog().domain([0.1, 1, 100]).range(["red", "white", "green"]); 54 | assert.strictEqual(x(0.5), "rgb(255, 178, 178)"); 55 | assert.strictEqual(x(50), "rgb(38, 147, 38)"); 56 | assert.strictEqual(x(75), "rgb(16, 136, 16)"); 57 | }); 58 | 59 | it("log.domain(…) preserves specified domain exactly, with no floating point error", () => { 60 | const x = scaleLog().domain([0.1, 1000]); 61 | assert.deepStrictEqual(x.domain(), [0.1, 1000]); 62 | }); 63 | 64 | it("log.ticks(…) returns exact ticks, with no floating point error", () => { 65 | assert.deepStrictEqual(scaleLog().domain([0.15, 0.68]).ticks(), [0.2, 0.3, 0.4, 0.5, 0.6]); 66 | assert.deepStrictEqual(scaleLog().domain([0.68, 0.15]).ticks(), [0.6, 0.5, 0.4, 0.3, 0.2]); 67 | assert.deepStrictEqual(scaleLog().domain([-0.15, -0.68]).ticks(), [-0.2, -0.3, -0.4, -0.5, -0.6]); 68 | assert.deepStrictEqual(scaleLog().domain([-0.68, -0.15]).ticks(), [-0.6, -0.5, -0.4, -0.3, -0.2]); 69 | }); 70 | 71 | it("log.range(…) does not coerce values to numbers", () => { 72 | const x = scaleLog().range(["0", "2"]); 73 | assert.strictEqual(typeof x.range()[0], "string"); 74 | assert.strictEqual(typeof x.range()[1], "string"); 75 | }); 76 | 77 | it("log.range(…) can take colors", () => { 78 | const x = scaleLog().range(["red", "blue"]); 79 | assert.strictEqual(x(5), "rgb(77, 0, 178)"); 80 | x.range(["#ff0000", "#0000ff"]); 81 | assert.strictEqual(x(5), "rgb(77, 0, 178)"); 82 | x.range(["#f00", "#00f"]); 83 | assert.strictEqual(x(5), "rgb(77, 0, 178)"); 84 | x.range([rgb(255, 0, 0), hsl(240, 1, 0.5)]); 85 | assert.strictEqual(x(5), "rgb(77, 0, 178)"); 86 | x.range(["hsl(0,100%,50%)", "hsl(240,100%,50%)"]); 87 | assert.strictEqual(x(5), "rgb(77, 0, 178)"); 88 | }); 89 | 90 | it("log.range(…) can take arrays or objects", () => { 91 | const x = scaleLog().range([{color: "red"}, {color: "blue"}]); 92 | assert.deepStrictEqual(x(5), {color: "rgb(77, 0, 178)"}); 93 | x.range([["red"], ["blue"]]); 94 | assert.deepStrictEqual(x(5), ["rgb(77, 0, 178)"]); 95 | }); 96 | 97 | it("log.interpolate(f) sets the interpolator", () => { 98 | const x = scaleLog().range(["red", "blue"]); 99 | assert.strictEqual(x.interpolate(), interpolate); 100 | assert.strictEqual(x(5), "rgb(77, 0, 178)"); 101 | x.interpolate(interpolateHsl); 102 | assert.strictEqual(x(5), "rgb(154, 0, 255)"); 103 | }); 104 | 105 | it("log(x) does not clamp by default", () => { 106 | const x = scaleLog(); 107 | assert.strictEqual(x.clamp(), false); 108 | assertInDelta(x(0.5), -0.3010299); 109 | assertInDelta(x(15), 1.1760913); 110 | }); 111 | 112 | it("log.clamp(true)(x) clamps to the domain", () => { 113 | const x = scaleLog().clamp(true); 114 | assertInDelta(x(-1), 0); 115 | assertInDelta(x(5), 0.69897); 116 | assertInDelta(x(15), 1); 117 | x.domain([10, 1]); 118 | assertInDelta(x(-1), 1); 119 | assertInDelta(x(5), 0.30103); 120 | assertInDelta(x(15), 0); 121 | }); 122 | 123 | it("log.clamp(true).invert(y) clamps to the range", () => { 124 | const x = scaleLog().clamp(true); 125 | assertInDelta(x.invert(-0.1), 1); 126 | assertInDelta(x.invert(0.69897), 5); 127 | assertInDelta(x.invert(1.5), 10); 128 | x.domain([10, 1]); 129 | assertInDelta(x.invert(-0.1), 10); 130 | assertInDelta(x.invert(0.30103), 5); 131 | assertInDelta(x.invert(1.5), 1); 132 | }); 133 | 134 | it("log(x) maps a number x to a number y", () => { 135 | const x = scaleLog().domain([1, 2]); 136 | assertInDelta(x(0.5), -1.0000000); 137 | assertInDelta(x(1.0), 0.0000000); 138 | assertInDelta(x(1.5), 0.5849625); 139 | assertInDelta(x(2.0), 1.0000000); 140 | assertInDelta(x(2.5), 1.3219281); 141 | }); 142 | 143 | it("log.invert(y) maps a number y to a number x", () => { 144 | const x = scaleLog().domain([1, 2]); 145 | assertInDelta(x.invert(-1.0000000), 0.5); 146 | assertInDelta(x.invert( 0.0000000), 1.0); 147 | assertInDelta(x.invert( 0.5849625), 1.5); 148 | assertInDelta(x.invert( 1.0000000), 2.0); 149 | assertInDelta(x.invert( 1.3219281), 2.5); 150 | }); 151 | 152 | it("log.invert(y) coerces y to number", () => { 153 | const x = scaleLog().range(["0", "2"]); 154 | assertInDelta(x.invert("1"), 3.1622777); 155 | x.range([new Date(1990, 0, 1), new Date(1991, 0, 1)]); 156 | assertInDelta(x.invert(new Date(1990, 6, 2, 13)), 3.1622777); 157 | x.range(["#000", "#fff"]); 158 | assert(Number.isNaN(x.invert("#999"))); 159 | }); 160 | 161 | it("log.base(b) sets the log base, changing the ticks", () => { 162 | const x = scaleLog().domain([1, 32]); 163 | assert.deepStrictEqual(x.base(2).ticks().map(x.tickFormat()), ["1", "2", "4", "8", "16", "32"]); 164 | assert.deepStrictEqual(x.base(Math.E).ticks().map(x.tickFormat()), ["1", "2.71828182846", "7.38905609893", "20.0855369232"]); 165 | }); 166 | 167 | it("log.nice() nices the domain, extending it to powers of ten", () => { 168 | const x = scaleLog().domain([1.1, 10.9]).nice(); 169 | assert.deepStrictEqual(x.domain(), [1, 100]); 170 | x.domain([10.9, 1.1]).nice(); 171 | assert.deepStrictEqual(x.domain(), [100, 1]); 172 | x.domain([0.7, 11.001]).nice(); 173 | assert.deepStrictEqual(x.domain(), [0.1, 100]); 174 | x.domain([123.1, 6.7]).nice(); 175 | assert.deepStrictEqual(x.domain(), [1000, 1]); 176 | x.domain([0.01, 0.49]).nice(); 177 | assert.deepStrictEqual(x.domain(), [0.01, 1]); 178 | x.domain([1.5, 50]).nice(); 179 | assert.deepStrictEqual(x.domain(), [1, 100]); 180 | assertInDelta(x(1), 0); 181 | assertInDelta(x(100), 1); 182 | }); 183 | 184 | it("log.nice() works on degenerate domains", () => { 185 | const x = scaleLog().domain([0, 0]).nice(); 186 | assert.deepStrictEqual(x.domain(), [0, 0]); 187 | x.domain([0.5, 0.5]).nice(); 188 | assert.deepStrictEqual(x.domain(), [0.1, 1]); 189 | }); 190 | 191 | it("log.nice() on a polylog domain only affects the extent", () => { 192 | const x = scaleLog().domain([1.1, 1.5, 10.9]).nice(); 193 | assert.deepStrictEqual(x.domain(), [1, 1.5, 100]); 194 | x.domain([-123.1, -1.5, -0.5]).nice(); 195 | assert.deepStrictEqual(x.domain(), [-1000, -1.5, -0.1]); 196 | }); 197 | 198 | it("log.copy() isolates changes to the domain", () => { 199 | const x = scaleLog(), y = x.copy(); 200 | x.domain([10, 100]); 201 | assert.deepStrictEqual(y.domain(), [1, 10]); 202 | assertInDelta(x(10), 0); 203 | assertInDelta(y(1), 0); 204 | y.domain([100, 1000]); 205 | assertInDelta(x(100), 1); 206 | assertInDelta(y(100), 0); 207 | assert.deepStrictEqual(x.domain(), [10, 100]); 208 | assert.deepStrictEqual(y.domain(), [100, 1000]); 209 | }); 210 | 211 | it("log.copy() isolates changes to the domain via nice", () => { 212 | const x = scaleLog().domain([1.5, 50]), y = x.copy().nice(); 213 | assert.deepStrictEqual(x.domain(), [1.5, 50]); 214 | assertInDelta(x(1.5), 0); 215 | assertInDelta(x(50), 1); 216 | assertInDelta(x.invert(0), 1.5); 217 | assertInDelta(x.invert(1), 50); 218 | assert.deepStrictEqual(y.domain(), [1, 100]); 219 | assertInDelta(y(1), 0); 220 | assertInDelta(y(100), 1); 221 | assertInDelta(y.invert(0), 1); 222 | assertInDelta(y.invert(1), 100); 223 | }); 224 | 225 | it("log.copy() isolates changes to the range", () => { 226 | const x = scaleLog(), y = x.copy(); 227 | x.range([1, 2]); 228 | assertInDelta(x.invert(1), 1); 229 | assertInDelta(y.invert(1), 10); 230 | assert.deepStrictEqual(y.range(), [0, 1]); 231 | y.range([2, 3]); 232 | assertInDelta(x.invert(2), 10); 233 | assertInDelta(y.invert(2), 1); 234 | assert.deepStrictEqual(x.range(), [1, 2]); 235 | assert.deepStrictEqual(y.range(), [2, 3]); 236 | }); 237 | 238 | it("log.copy() isolates changes to the interpolator", () => { 239 | const x = scaleLog().range(["red", "blue"]), y = x.copy(); 240 | x.interpolate(interpolateHsl); 241 | assert.strictEqual(x(5), "rgb(154, 0, 255)"); 242 | assert.strictEqual(y(5), "rgb(77, 0, 178)"); 243 | assert.strictEqual(y.interpolate(), interpolate); 244 | }); 245 | 246 | it("log.copy() isolates changes to clamping", () => { 247 | const x = scaleLog().clamp(true), y = x.copy(); 248 | x.clamp(false); 249 | assertInDelta(x(0.5), -0.30103); 250 | assertInDelta(y(0.5), 0); 251 | assert.strictEqual(y.clamp(), true); 252 | y.clamp(false); 253 | assertInDelta(x(20), 1.30103); 254 | assertInDelta(y(20), 1.30103); 255 | assert.strictEqual(x.clamp(), false); 256 | }); 257 | 258 | it("log.ticks() generates the expected power-of-ten for ascending ticks", () => { 259 | const s = scaleLog(); 260 | assert.deepStrictEqual(s.domain([1e-1, 1e1]).ticks().map(round), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); 261 | assert.deepStrictEqual(s.domain([1e-1, 1e0]).ticks().map(round), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]); 262 | assert.deepStrictEqual(s.domain([-1e0, -1e-1]).ticks().map(round), [-1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1]); 263 | }); 264 | 265 | 266 | it("log.ticks() generates the expected power-of-ten ticks for descending domains", () => { 267 | const s = scaleLog(); 268 | assert.deepStrictEqual(s.domain([-1e-1, -1e1]).ticks().map(round), [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1].reverse()); 269 | assert.deepStrictEqual(s.domain([-1e-1, -1e0]).ticks().map(round), [-1, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1].reverse()); 270 | assert.deepStrictEqual(s.domain([1e0, 1e-1]).ticks().map(round), [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1].reverse()); 271 | }); 272 | 273 | it("log.ticks() generates the expected power-of-ten ticks for small domains", () => { 274 | const s = scaleLog(); 275 | assert.deepStrictEqual(s.domain([1, 5]).ticks(), [1, 2, 3, 4, 5]); 276 | assert.deepStrictEqual(s.domain([5, 1]).ticks(), [5, 4, 3, 2, 1]); 277 | assert.deepStrictEqual(s.domain([-1, -5]).ticks(), [-1, -2, -3, -4, -5]); 278 | assert.deepStrictEqual(s.domain([-5, -1]).ticks(), [-5, -4, -3, -2, -1]); 279 | assert.deepStrictEqual(s.domain([286.9252014, 329.4978332]).ticks(1), [300]); 280 | assert.deepStrictEqual(s.domain([286.9252014, 329.4978332]).ticks(2), [300]); 281 | assert.deepStrictEqual(s.domain([286.9252014, 329.4978332]).ticks(3), [300, 320]); 282 | assert.deepStrictEqual(s.domain([286.9252014, 329.4978332]).ticks(4), [290, 300, 310, 320]); 283 | assert.deepStrictEqual(s.domain([286.9252014, 329.4978332]).ticks(), [290, 295, 300, 305, 310, 315, 320, 325]); 284 | }); 285 | 286 | it("log.ticks() generates linear ticks when the domain extent is small", () => { 287 | const s = scaleLog(); 288 | assert.deepStrictEqual(s.domain([41, 42]).ticks(), [41, 41.1, 41.2, 41.3, 41.4, 41.5, 41.6, 41.7, 41.8, 41.9, 42]); 289 | assert.deepStrictEqual(s.domain([42, 41]).ticks(), [42, 41.9, 41.8, 41.7, 41.6, 41.5, 41.4, 41.3, 41.2, 41.1, 41]); 290 | assert.deepStrictEqual(s.domain([1600, 1400]).ticks(), [1600, 1580, 1560, 1540, 1520, 1500, 1480, 1460, 1440, 1420, 1400]); 291 | }); 292 | 293 | it("log.base(base).ticks() generates the expected power-of-base ticks", () => { 294 | const s = scaleLog().base(Math.E); 295 | assert.deepStrictEqual(s.domain([0.1, 100]).ticks().map(round), [0.135335283237, 0.367879441171, 1, 2.718281828459, 7.389056098931, 20.085536923188, 54.598150033144]); 296 | }); 297 | 298 | it("log.tickFormat() is equivalent to log.tickFormat(10)", () => { 299 | const s = scaleLog(); 300 | assert.deepStrictEqual(s.domain([1e-1, 1e1]).ticks().map(s.tickFormat()), ["100m", "200m", "300m", "400m", "500m", "", "", "", "", "1", "2", "3", "4", "5", "", "", "", "", "10"]); 301 | }); 302 | 303 | it("log.tickFormat(count) returns a filtered \"s\" format", () => { 304 | const s = scaleLog(), t = s.domain([1e-1, 1e1]).ticks(); 305 | assert.deepStrictEqual(t.map(s.tickFormat(10)), ["100m", "200m", "300m", "400m", "500m", "", "", "", "", "1", "2", "3", "4", "5", "", "", "", "", "10"]); 306 | assert.deepStrictEqual(t.map(s.tickFormat(5)), ["100m", "200m", "", "", "", "", "", "", "", "1", "2", "", "", "", "", "", "", "", "10"]); 307 | assert.deepStrictEqual(t.map(s.tickFormat(1)), ["100m", "", "", "", "", "", "", "", "", "1", "", "", "", "", "", "", "", "", "10"]); 308 | assert.deepStrictEqual(t.map(s.tickFormat(0)), ["100m", "", "", "", "", "", "", "", "", "1", "", "", "", "", "", "", "", "", "10"]); 309 | }); 310 | 311 | it("log.tickFormat(count, format) returns the specified format, filtered", () => { 312 | const s = scaleLog(), t = s.domain([1e-1, 1e1]).ticks(); 313 | assert.deepStrictEqual(t.map(s.tickFormat(10, "+")), ["+0.1", "+0.2", "+0.3", "+0.4", "+0.5", "", "", "", "", "+1", "+2", "+3", "+4", "+5", "", "", "", "", "+10"]); 314 | }); 315 | 316 | it("log.base(base).tickFormat() returns the \",\" format", () => { 317 | const s = scaleLog().base(Math.E); 318 | assert.deepStrictEqual(s.domain([1e-1, 1e1]).ticks().map(s.tickFormat()), ["0.135335283237", "0.367879441171", "1", "2.71828182846", "7.38905609893"]); 319 | }); 320 | 321 | it("log.base(base).tickFormat(count) returns a filtered \",\" format", () => { 322 | const s = scaleLog().base(16), t = s.domain([1e-1, 1e1]).ticks(); 323 | assert.deepStrictEqual(t.map(s.tickFormat(10)), ["0.125", "0.1875", "0.25", "0.3125", "0.375", "", "", "", "", "", "", "", "", "", "1", "2", "3", "4", "5", "6", "", "", "", ""]); 324 | assert.deepStrictEqual(t.map(s.tickFormat(5)), ["0.125", "0.1875", "", "", "", "", "", "", "", "", "", "", "", "", "1", "2", "3", "", "", "", "", "", "", ""]); 325 | assert.deepStrictEqual(t.map(s.tickFormat(1)), ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "1", "", "", "", "", "", "", "", "", ""]); 326 | }); 327 | 328 | it("log.ticks() generates log ticks", () => { 329 | const x = scaleLog(); 330 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(Infinity)), [ 331 | "1", "2", "3", "4", "5", "6", "7", "8", "9", 332 | "10" 333 | ]); 334 | x.domain([100, 1]); 335 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(Infinity)), [ 336 | "100", 337 | "90", "80", "70", "60", "50", "40", "30", "20", "10", 338 | "9", "8", "7", "6", "5", "4", "3", "2", "1" 339 | ]); 340 | x.domain([0.49999, 0.006029505943610648]); 341 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(Infinity)), [ 342 | "400m", "300m", "200m", "100m", 343 | "90m", "80m", "70m", "60m", "50m", "40m", "30m", "20m", "10m", 344 | "9m", "8m", "7m" 345 | ]); 346 | x.domain([0.95, 1.05e8]); 347 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(8)).filter(String), [ 348 | "1", "10", "100", "1k", "10k", "100k", "1M", "10M", "100M" 349 | ]); 350 | }); 351 | 352 | it("log.tickFormat(count) filters ticks to about count", () => { 353 | const x = scaleLog(); 354 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(5)), [ 355 | "1", "2", "3", "4", "5", "", "", "", "", 356 | "10" 357 | ]); 358 | x.domain([100, 1]); 359 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(10)), [ 360 | "100", 361 | "", "", "", "", "50", "40", "30", "20", "10", 362 | "", "", "", "", "5", "4", "3", "2", "1" 363 | ]); 364 | }); 365 | 366 | it("log.ticks(count) filters powers-of-ten ticks for huge domains", () => { 367 | const x = scaleLog().domain([1e10, 1]); 368 | assert.deepStrictEqual(x.ticks().map(x.tickFormat()), ["10G", "1G", "100M", "10M", "1M", "100k", "10k", "1k", "100", "10", "1"]); 369 | x.domain([1e-29, 1e-1]); 370 | assert.deepStrictEqual(x.ticks().map(x.tickFormat()), ["0.0001y", "0.01y", "1y", "100y", "10z", "1a", "100a", "10f", "1p", "100p", "10n", "1µ", "100µ", "10m"]); 371 | }); 372 | 373 | it("log.ticks() generates ticks that cover the domain", () => { 374 | const x = scaleLog().domain([0.01, 10000]); 375 | assert.deepStrictEqual(x.ticks(20).map(x.tickFormat(20)), [ 376 | "10m", "20m", "30m", "", "", "", "", "", "", 377 | "100m", "200m", "300m", "", "", "", "", "", "", 378 | "1", "2", "3", "", "", "", "", "", "", 379 | "10", "20", "30", "", "", "", "", "", "", 380 | "100", "200", "300", "", "", "", "", "", "", 381 | "1k", "2k", "3k", "", "", "", "", "", "", 382 | "10k" 383 | ]); 384 | }); 385 | 386 | it("log.ticks() generates ticks that cover the niced domain", () => { 387 | const x = scaleLog().domain([0.0124123, 1230.4]).nice(); 388 | assert.deepStrictEqual(x.ticks(20).map(x.tickFormat(20)), [ 389 | "10m", "20m", "30m", "", "", "", "", "", "", 390 | "100m", "200m", "300m", "", "", "", "", "", "", 391 | "1", "2", "3", "", "", "", "", "", "", 392 | "10", "20", "30", "", "", "", "", "", "", 393 | "100", "200", "300", "", "", "", "", "", "", 394 | "1k", "2k", "3k", "", "", "", "", "", "", 395 | "10k" 396 | ]); 397 | }); 398 | 399 | it("log.tickFormat(count, format) returns a filtered format", () => { 400 | const x = scaleLog().domain([1000.1, 1]); 401 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(10, format("+,d"))), [ 402 | "+1,000", 403 | "", "", "", "", "", "", "+300", "+200", "+100", 404 | "", "", "", "", "", "", "+30", "+20", "+10", 405 | "", "", "", "", "", "", "+3", "+2", "+1" 406 | ]); 407 | }); 408 | 409 | it("log.tickFormat(count, specifier) returns a filtered format", () => { 410 | const x = scaleLog().domain([1000.1, 1]); 411 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(10, "s")), [ 412 | "1k", 413 | "", "", "", "", "", "", "300", "200", "100", 414 | "", "", "", "", "", "", "30", "20", "10", 415 | "", "", "", "", "", "", "3", "2", "1" 416 | ]); 417 | }); 418 | 419 | it("log.tickFormat(count, specifier) trims trailing zeroes by default", () => { 420 | const x = scaleLog().domain([100.1, 0.02]); 421 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(10, "f")), [ 422 | "100", 423 | "", "", "", "", "", "", "", "20", "10", 424 | "", "", "", "", "", "", "", "2", "1", 425 | "", "", "", "", "", "", "", "0.2", "0.1", 426 | "", "", "", "", "", "", "", "0.02" 427 | ]); 428 | }); 429 | 430 | it("log.tickFormat(count, specifier) with base two trims trailing zeroes by default", () => { 431 | const x = scaleLog().base(2).domain([100.1, 0.02]); 432 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(10, "f")), [ 433 | "64", "32", "16", "8", "4", "2", "1", "0.5", "0.25", "0.125", "0.0625", "0.03125" 434 | ]); 435 | }); 436 | 437 | it("log.tickFormat(count, specifier) preserves trailing zeroes if needed", () => { 438 | const x = scaleLog().domain([100.1, 0.02]); 439 | assert.deepStrictEqual(x.ticks().map(x.tickFormat(10, ".1f")), [ 440 | "100.0", 441 | "", "", "", "", "", "", "", "20.0", "10.0", 442 | "", "", "", "", "", "", "", "2.0", "1.0", 443 | "", "", "", "", "", "", "", "0.2", "0.1", 444 | "", "", "", "", "", "", "", "0.0" 445 | ]); 446 | }); 447 | 448 | it("log.ticks() returns the empty array when the domain is degenerate", () => { 449 | const x = scaleLog(); 450 | assert.deepStrictEqual(x.domain([0, 1]).ticks(), []); 451 | assert.deepStrictEqual(x.domain([1, 0]).ticks(), []); 452 | assert.deepStrictEqual(x.domain([0, -1]).ticks(), []); 453 | assert.deepStrictEqual(x.domain([-1, 0]).ticks(), []); 454 | assert.deepStrictEqual(x.domain([-1, 1]).ticks(), []); 455 | assert.deepStrictEqual(x.domain([0, 0]).ticks(), []); 456 | }); 457 | 458 | function round(x) { 459 | return Math.round(x * 1e12) / 1e12; 460 | } 461 | -------------------------------------------------------------------------------- /test/pow-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scalePow, scaleSqrt} from "../src/index.js"; 3 | import {roundEpsilon} from "./roundEpsilon.js"; 4 | import {assertInDelta} from "./asserts.js"; 5 | 6 | it("scalePow() has the expected defaults", () => { 7 | const s = scalePow(); 8 | assert.deepStrictEqual(s.domain(), [0, 1]); 9 | assert.deepStrictEqual(s.range(), [0, 1]); 10 | assert.strictEqual(s.clamp(), false); 11 | assert.strictEqual(s.exponent(), 1); 12 | assert.deepStrictEqual(s.interpolate()({array: ["red"]}, {array: ["blue"]})(0.5), {array: ["rgb(128, 0, 128)"]}); 13 | }); 14 | 15 | it("pow(x) maps a domain value x to a range value y", () => { 16 | assert.strictEqual(scalePow().exponent(0.5)(0.5), Math.SQRT1_2); 17 | }); 18 | 19 | it("pow(x) ignores extra range values if the domain is smaller than the range", () => { 20 | assert.strictEqual(scalePow().domain([-10, 0]).range(["red", "white", "green"]).clamp(true)(-5), "rgb(255, 128, 128)"); 21 | assert.strictEqual(scalePow().domain([-10, 0]).range(["red", "white", "green"]).clamp(true)(50), "rgb(255, 255, 255)"); 22 | }); 23 | 24 | it("pow(x) ignores extra domain values if the range is smaller than the domain", () => { 25 | assert.strictEqual(scalePow().domain([-10, 0, 100]).range(["red", "white"]).clamp(true)(-5), "rgb(255, 128, 128)"); 26 | assert.strictEqual(scalePow().domain([-10, 0, 100]).range(["red", "white"]).clamp(true)(50), "rgb(255, 255, 255)"); 27 | }); 28 | 29 | it("pow(x) maps an empty domain to the middle of the range", () => { 30 | assert.strictEqual(scalePow().domain([0, 0]).range([1, 2])(0), 1.5); 31 | assert.strictEqual(scalePow().domain([0, 0]).range([2, 1])(1), 1.5); 32 | }); 33 | 34 | it("pow(x) can map a bipow domain with two values to the corresponding range", () => { 35 | const s = scalePow().domain([1, 2]); 36 | assert.deepStrictEqual(s.domain(), [1, 2]); 37 | assert.strictEqual(s(0.5), -0.5); 38 | assert.strictEqual(s(1.0), 0.0); 39 | assert.strictEqual(s(1.5), 0.5); 40 | assert.strictEqual(s(2.0), 1.0); 41 | assert.strictEqual(s(2.5), 1.5); 42 | assert.strictEqual(s.invert(-0.5), 0.5); 43 | assert.strictEqual(s.invert( 0.0), 1.0); 44 | assert.strictEqual(s.invert( 0.5), 1.5); 45 | assert.strictEqual(s.invert( 1.0), 2.0); 46 | assert.strictEqual(s.invert( 1.5), 2.5); 47 | }); 48 | 49 | it("pow(x) can map a polypow domain with more than two values to the corresponding range", () => { 50 | const s = scalePow().domain([-10, 0, 100]).range(["red", "white", "green"]); 51 | assert.deepStrictEqual(s.domain(), [-10, 0, 100]); 52 | assert.strictEqual(s(-5), "rgb(255, 128, 128)"); 53 | assert.strictEqual(s(50), "rgb(128, 192, 128)"); 54 | assert.strictEqual(s(75), "rgb(64, 160, 64)"); 55 | s.domain([4, 2, 1]).range([1, 2, 4]); 56 | assert.strictEqual(s(1.5), 3); 57 | assert.strictEqual(s(3), 1.5); 58 | assert.strictEqual(s.invert(1.5), 3); 59 | assert.strictEqual(s.invert(3), 1.5); 60 | s.domain([1, 2, 4]).range([4, 2, 1]); 61 | assert.strictEqual(s(1.5), 3); 62 | assert.strictEqual(s(3), 1.5); 63 | assert.strictEqual(s.invert(1.5), 3); 64 | assert.strictEqual(s.invert(3), 1.5); 65 | }); 66 | 67 | it("pow.invert(y) maps a range value y to a domain value x", () => { 68 | assert.strictEqual(scalePow().range([1, 2]).invert(1.5), 0.5); 69 | }); 70 | 71 | it("pow.invert(y) maps an empty range to the middle of the domain", () => { 72 | assert.strictEqual(scalePow().domain([1, 2]).range([0, 0]).invert(0), 1.5); 73 | assert.strictEqual(scalePow().domain([2, 1]).range([0, 0]).invert(1), 1.5); 74 | }); 75 | 76 | it("pow.invert(y) coerces range values to numbers", () => { 77 | assert.strictEqual(scalePow().range(["0", "2"]).invert("1"), 0.5); 78 | assert.strictEqual(scalePow().range([new Date(1990, 0, 1), new Date(1991, 0, 1)]).invert(new Date(1990, 6, 2, 13)), 0.5); 79 | }); 80 | 81 | it("pow.invert(y) returns NaN if the range is not coercible to number", () => { 82 | assert(isNaN(scalePow().range(["#000", "#fff"]).invert("#999"))); 83 | assert(isNaN(scalePow().range([0, "#fff"]).invert("#999"))); 84 | }); 85 | 86 | it("pow.exponent(exponent) sets the exponent to the specified value", () => { 87 | const x = scalePow().exponent(0.5).domain([1, 2]); 88 | assertInDelta(x(1), 0, 1e-6); 89 | assertInDelta(x(1.5), 0.5425821, 1e-6); 90 | assertInDelta(x(2), 1, 1e-6); 91 | assert.strictEqual(x.exponent(), 0.5); 92 | x.exponent(2).domain([1, 2]); 93 | assertInDelta(x(1), 0, 1e-6); 94 | assertInDelta(x(1.5), 0.41666667, 1e-6); 95 | assertInDelta(x(2), 1, 1e-6); 96 | assert.strictEqual(x.exponent(), 2); 97 | x.exponent(-1).domain([1, 2]); 98 | assertInDelta(x(1), 0, 1e-6); 99 | assertInDelta(x(1.5), 0.6666667, 1e-6); 100 | assertInDelta(x(2), 1, 1e-6); 101 | assert.strictEqual(x.exponent(), -1); 102 | }); 103 | 104 | it("pow.exponent(exponent) changing the exponent does not change the domain or range", () => { 105 | const x = scalePow().domain([1, 2]).range([3, 4]); 106 | x.exponent(0.5); 107 | assert.deepStrictEqual(x.domain(), [1, 2]); 108 | assert.deepStrictEqual(x.range(), [3, 4]); 109 | x.exponent(2); 110 | assert.deepStrictEqual(x.domain(), [1, 2]); 111 | assert.deepStrictEqual(x.range(), [3, 4]); 112 | x.exponent(-1); 113 | assert.deepStrictEqual(x.domain(), [1, 2]); 114 | assert.deepStrictEqual(x.range(), [3, 4]); 115 | }); 116 | 117 | it("pow.domain(domain) accepts an array of numbers", () => { 118 | assert.deepStrictEqual(scalePow().domain([]).domain(), []); 119 | assert.deepStrictEqual(scalePow().domain([1, 0]).domain(), [1, 0]); 120 | assert.deepStrictEqual(scalePow().domain([1, 2, 3]).domain(), [1, 2, 3]); 121 | }); 122 | 123 | it("pow.domain(domain) coerces domain values to numbers", () => { 124 | assert.deepStrictEqual(scalePow().domain([new Date(1990, 0, 1), new Date(1991, 0, 1)]).domain(), [631180800000, 662716800000]); 125 | assert.deepStrictEqual(scalePow().domain(["0.0", "1.0"]).domain(), [0, 1]); 126 | assert.deepStrictEqual(scalePow().domain([new Number(0), new Number(1)]).domain(), [0, 1]); 127 | }); 128 | 129 | it("pow.domain(domain) makes a copy of domain values", () => { 130 | const d = [1, 2], s = scalePow().domain(d); 131 | assert.deepStrictEqual(s.domain(), [1, 2]); 132 | d.push(3); 133 | assert.deepStrictEqual(s.domain(), [1, 2]); 134 | assert.deepStrictEqual(d, [1, 2, 3]); 135 | }); 136 | 137 | it("pow.domain() returns a copy of domain values", () => { 138 | const s = scalePow(), d = s.domain(); 139 | assert.deepStrictEqual(d, [0, 1]); 140 | d.push(3); 141 | assert.deepStrictEqual(s.domain(), [0, 1]); 142 | }); 143 | 144 | it("pow.range(range) does not coerce range to numbers", () => { 145 | const s = scalePow().range(["0px", "2px"]); 146 | assert.deepStrictEqual(s.range(), ["0px", "2px"]); 147 | assert.strictEqual(s(0.5), "1px"); 148 | }); 149 | 150 | it("pow.range(range) can accept range values as colors", () => { 151 | assert.strictEqual(scalePow().range(["red", "blue"])(0.5), "rgb(128, 0, 128)"); 152 | assert.strictEqual(scalePow().range(["#ff0000", "#0000ff"])(0.5), "rgb(128, 0, 128)"); 153 | assert.strictEqual(scalePow().range(["#f00", "#00f"])(0.5), "rgb(128, 0, 128)"); 154 | assert.strictEqual(scalePow().range(["rgb(255,0,0)", "hsl(240,100%,50%)"])(0.5), "rgb(128, 0, 128)"); 155 | assert.strictEqual(scalePow().range(["rgb(100%,0%,0%)", "hsl(240,100%,50%)"])(0.5), "rgb(128, 0, 128)"); 156 | assert.strictEqual(scalePow().range(["hsl(0,100%,50%)", "hsl(240,100%,50%)"])(0.5), "rgb(128, 0, 128)"); 157 | }); 158 | 159 | it("pow.range(range) can accept range values as arrays or objects", () => { 160 | assert.deepStrictEqual(scalePow().range([{color: "red"}, {color: "blue"}])(0.5), {color: "rgb(128, 0, 128)"}); 161 | assert.deepStrictEqual(scalePow().range([["red"], ["blue"]])(0.5), ["rgb(128, 0, 128)"]); 162 | }); 163 | 164 | it("pow.range(range) makes a copy of range values", () => { 165 | const r = [1, 2], s = scalePow().range(r); 166 | assert.deepStrictEqual(s.range(), [1, 2]); 167 | r.push(3); 168 | assert.deepStrictEqual(s.range(), [1, 2]); 169 | assert.deepStrictEqual(r, [1, 2, 3]); 170 | }); 171 | 172 | it("pow.range() returns a copy of range values", () => { 173 | const s = scalePow(), r = s.range(); 174 | assert.deepStrictEqual(r, [0, 1]); 175 | r.push(3); 176 | assert.deepStrictEqual(s.range(), [0, 1]); 177 | }); 178 | 179 | it("pow.rangeRound(range) is an alias for pow.range(range).interpolate(interpolateRound)", () => { 180 | assert.strictEqual(scalePow().rangeRound([0, 10])(0.59), 6); 181 | }); 182 | 183 | it("pow.clamp() is false by default", () => { 184 | assert.strictEqual(scalePow().clamp(), false); 185 | assert.strictEqual(scalePow().range([10, 20])(2), 30); 186 | assert.strictEqual(scalePow().range([10, 20])(-1), 0); 187 | assert.strictEqual(scalePow().range([10, 20]).invert(30), 2); 188 | assert.strictEqual(scalePow().range([10, 20]).invert(0), -1); 189 | }); 190 | 191 | it("pow.clamp(true) restricts output values to the range", () => { 192 | assert.strictEqual(scalePow().clamp(true).range([10, 20])(2), 20); 193 | assert.strictEqual(scalePow().clamp(true).range([10, 20])(-1), 10); 194 | }); 195 | 196 | it("pow.clamp(true) restricts input values to the domain", () => { 197 | assert.strictEqual(scalePow().clamp(true).range([10, 20]).invert(30), 1); 198 | assert.strictEqual(scalePow().clamp(true).range([10, 20]).invert(0), 0); 199 | }); 200 | 201 | it("pow.clamp(clamp) coerces the specified clamp value to a boolean", () => { 202 | assert.strictEqual(scalePow().clamp("true").clamp(), true); 203 | assert.strictEqual(scalePow().clamp(1).clamp(), true); 204 | assert.strictEqual(scalePow().clamp("").clamp(), false); 205 | assert.strictEqual(scalePow().clamp(0).clamp(), false); 206 | }); 207 | 208 | it("pow.interpolate(interpolate) takes a custom interpolator factory", () => { 209 | function interpolate(a, b) { return function(t) { return [a, b, t]; }; } 210 | const s = scalePow().domain([10, 20]).range(["a", "b"]).interpolate(interpolate); 211 | assert.strictEqual(s.interpolate(), interpolate); 212 | assert.deepStrictEqual(s(15), ["a", "b", 0.5]); 213 | }); 214 | 215 | it("pow.nice() is an alias for pow.nice(10)", () => { 216 | assert.deepStrictEqual(scalePow().domain([0, 0.96]).nice().domain(), [0, 1]); 217 | assert.deepStrictEqual(scalePow().domain([0, 96]).nice().domain(), [0, 100]); 218 | }); 219 | 220 | it("pow.nice(count) extends the domain to match the desired ticks", () => { 221 | assert.deepStrictEqual(scalePow().domain([0, 0.96]).nice(10).domain(), [0, 1]); 222 | assert.deepStrictEqual(scalePow().domain([0, 96]).nice(10).domain(), [0, 100]); 223 | assert.deepStrictEqual(scalePow().domain([0.96, 0]).nice(10).domain(), [1, 0]); 224 | assert.deepStrictEqual(scalePow().domain([96, 0]).nice(10).domain(), [100, 0]); 225 | assert.deepStrictEqual(scalePow().domain([0, -0.96]).nice(10).domain(), [0, -1]); 226 | assert.deepStrictEqual(scalePow().domain([0, -96]).nice(10).domain(), [0, -100]); 227 | assert.deepStrictEqual(scalePow().domain([-0.96, 0]).nice(10).domain(), [-1, 0]); 228 | assert.deepStrictEqual(scalePow().domain([-96, 0]).nice(10).domain(), [-100, 0]); 229 | }); 230 | 231 | it("pow.nice(count) nices the domain, extending it to round numbers", () => { 232 | assert.deepStrictEqual(scalePow().domain([1.1, 10.9]).nice(10).domain(), [1, 11]); 233 | assert.deepStrictEqual(scalePow().domain([10.9, 1.1]).nice(10).domain(), [11, 1]); 234 | assert.deepStrictEqual(scalePow().domain([0.7, 11.001]).nice(10).domain(), [0, 12]); 235 | assert.deepStrictEqual(scalePow().domain([123.1, 6.7]).nice(10).domain(), [130, 0]); 236 | assert.deepStrictEqual(scalePow().domain([0, 0.49]).nice(10).domain(), [0, 0.5]); 237 | }); 238 | 239 | it("pow.nice(count) has no effect on degenerate domains", () => { 240 | assert.deepStrictEqual(scalePow().domain([0, 0]).nice(10).domain(), [0, 0]); 241 | assert.deepStrictEqual(scalePow().domain([0.5, 0.5]).nice(10).domain(), [0.5, 0.5]); 242 | }); 243 | 244 | it("pow.nice(count) nicing a polypow domain only affects the extent", () => { 245 | assert.deepStrictEqual(scalePow().domain([1.1, 1, 2, 3, 10.9]).nice(10).domain(), [1, 1, 2, 3, 11]); 246 | assert.deepStrictEqual(scalePow().domain([123.1, 1, 2, 3, -0.9]).nice(10).domain(), [130, 1, 2, 3, -10]); 247 | }); 248 | 249 | it("pow.nice(count) accepts a tick count to control nicing step", () => { 250 | assert.deepStrictEqual(scalePow().domain([12, 87]).nice(5).domain(), [0, 100]); 251 | assert.deepStrictEqual(scalePow().domain([12, 87]).nice(10).domain(), [10, 90]); 252 | assert.deepStrictEqual(scalePow().domain([12, 87]).nice(100).domain(), [12, 87]); 253 | }); 254 | 255 | it("pow.ticks(count) returns the expected ticks for an ascending domain", () => { 256 | const s = scalePow(); 257 | assert.deepStrictEqual(s.ticks(10).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 258 | assert.deepStrictEqual(s.ticks(9).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 259 | assert.deepStrictEqual(s.ticks(8).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 260 | assert.deepStrictEqual(s.ticks(7).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 261 | assert.deepStrictEqual(s.ticks(6).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 262 | assert.deepStrictEqual(s.ticks(5).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 263 | assert.deepStrictEqual(s.ticks(4).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 264 | assert.deepStrictEqual(s.ticks(3).map(roundEpsilon), [0.0, 0.5, 1.0]); 265 | assert.deepStrictEqual(s.ticks(2).map(roundEpsilon), [0.0, 0.5, 1.0]); 266 | assert.deepStrictEqual(s.ticks(1).map(roundEpsilon), [0.0, 1.0]); 267 | s.domain([-100, 100]); 268 | assert.deepStrictEqual(s.ticks(10), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 269 | assert.deepStrictEqual(s.ticks(9), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 270 | assert.deepStrictEqual(s.ticks(8), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 271 | assert.deepStrictEqual(s.ticks(7), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 272 | assert.deepStrictEqual(s.ticks(6), [-100, -50, 0, 50, 100]); 273 | assert.deepStrictEqual(s.ticks(5), [-100, -50, 0, 50, 100]); 274 | assert.deepStrictEqual(s.ticks(4), [-100, -50, 0, 50, 100]); 275 | assert.deepStrictEqual(s.ticks(3), [-100, -50, 0, 50, 100]); 276 | assert.deepStrictEqual(s.ticks(2), [-100, 0, 100]); 277 | assert.deepStrictEqual(s.ticks(1), [ 0 ]); 278 | }); 279 | 280 | it("pow.ticks(count) returns the expected ticks for a descending domain", () => { 281 | const s = scalePow().domain([1, 0]); 282 | assert.deepStrictEqual(s.ticks(10).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].reverse()); 283 | assert.deepStrictEqual(s.ticks(9).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].reverse()); 284 | assert.deepStrictEqual(s.ticks(8).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].reverse()); 285 | assert.deepStrictEqual(s.ticks(7).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 286 | assert.deepStrictEqual(s.ticks(6).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 287 | assert.deepStrictEqual(s.ticks(5).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 288 | assert.deepStrictEqual(s.ticks(4).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 289 | assert.deepStrictEqual(s.ticks(3).map(roundEpsilon), [0.0, 0.5, 1.0].reverse()); 290 | assert.deepStrictEqual(s.ticks(2).map(roundEpsilon), [0.0, 0.5, 1.0].reverse()); 291 | assert.deepStrictEqual(s.ticks(1).map(roundEpsilon), [0.0, 1.0].reverse()); 292 | s.domain([100, -100]); 293 | assert.deepStrictEqual(s.ticks(10), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 294 | assert.deepStrictEqual(s.ticks(9), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 295 | assert.deepStrictEqual(s.ticks(8), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 296 | assert.deepStrictEqual(s.ticks(7), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 297 | assert.deepStrictEqual(s.ticks(6), [-100, -50, 0, 50, 100].reverse()); 298 | assert.deepStrictEqual(s.ticks(5), [-100, -50, 0, 50, 100].reverse()); 299 | assert.deepStrictEqual(s.ticks(4), [-100, -50, 0, 50, 100].reverse()); 300 | assert.deepStrictEqual(s.ticks(3), [-100, -50, 0, 50, 100].reverse()); 301 | assert.deepStrictEqual(s.ticks(2), [-100, 0, 100].reverse()); 302 | assert.deepStrictEqual(s.ticks(1), [ 0 ].reverse()); 303 | }); 304 | 305 | it("pow.ticks(count) returns the expected ticks for a polypow domain", () => { 306 | const s = scalePow().domain([0, 0.25, 0.9, 1]); 307 | assert.deepStrictEqual(s.ticks(10).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 308 | assert.deepStrictEqual(s.ticks(9).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 309 | assert.deepStrictEqual(s.ticks(8).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 310 | assert.deepStrictEqual(s.ticks(7).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 311 | assert.deepStrictEqual(s.ticks(6).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 312 | assert.deepStrictEqual(s.ticks(5).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 313 | assert.deepStrictEqual(s.ticks(4).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 314 | assert.deepStrictEqual(s.ticks(3).map(roundEpsilon), [0.0, 0.5, 1.0]); 315 | assert.deepStrictEqual(s.ticks(2).map(roundEpsilon), [0.0, 0.5, 1.0]); 316 | assert.deepStrictEqual(s.ticks(1).map(roundEpsilon), [0.0, 1.0]); 317 | s.domain([-100, 0, 100]); 318 | assert.deepStrictEqual(s.ticks(10), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 319 | assert.deepStrictEqual(s.ticks(9), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 320 | assert.deepStrictEqual(s.ticks(8), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 321 | assert.deepStrictEqual(s.ticks(7), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 322 | assert.deepStrictEqual(s.ticks(6), [-100, -50, 0, 50, 100]); 323 | assert.deepStrictEqual(s.ticks(5), [-100, -50, 0, 50, 100]); 324 | assert.deepStrictEqual(s.ticks(4), [-100, -50, 0, 50, 100]); 325 | assert.deepStrictEqual(s.ticks(3), [-100, -50, 0, 50, 100]); 326 | assert.deepStrictEqual(s.ticks(2), [-100, 0, 100]); 327 | assert.deepStrictEqual(s.ticks(1), [ 0 ]); 328 | }); 329 | 330 | it("pow.ticks(count) returns the empty array if count is not a positive integer", () => { 331 | const s = scalePow(); 332 | assert.deepStrictEqual(s.ticks(NaN), []); 333 | assert.deepStrictEqual(s.ticks(0), []); 334 | assert.deepStrictEqual(s.ticks(-1), []); 335 | assert.deepStrictEqual(s.ticks(Infinity), []); 336 | }); 337 | 338 | it("pow.ticks() is an alias for pow.ticks(10)", () => { 339 | const s = scalePow(); 340 | assert.deepStrictEqual(s.ticks(), s.ticks(10)); 341 | }); 342 | 343 | it("pow.tickFormat() is an alias for pow.tickFormat(10)", () => { 344 | assert.strictEqual(scalePow().tickFormat()(0.2), "0.2"); 345 | assert.strictEqual(scalePow().domain([-100, 100]).tickFormat()(-20), "−20"); 346 | }); 347 | 348 | it("pow.tickFormat(count) returns a format suitable for the ticks", () => { 349 | assert.strictEqual(scalePow().tickFormat(10)(0.2), "0.2"); 350 | assert.strictEqual(scalePow().tickFormat(20)(0.2), "0.20"); 351 | assert.strictEqual(scalePow().domain([-100, 100]).tickFormat(10)(-20), "−20"); 352 | }); 353 | 354 | it("pow.tickFormat(count, specifier) sets the appropriate fixed precision if not specified", () => { 355 | assert.strictEqual(scalePow().tickFormat(10, "+f")(0.2), "+0.2"); 356 | assert.strictEqual(scalePow().tickFormat(20, "+f")(0.2), "+0.20"); 357 | assert.strictEqual(scalePow().tickFormat(10, "+%")(0.2), "+20%"); 358 | assert.strictEqual(scalePow().domain([0.19, 0.21]).tickFormat(10, "+%")(0.2), "+20.0%"); 359 | }); 360 | 361 | it("pow.tickFormat(count, specifier) sets the appropriate round precision if not specified", () => { 362 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(10, "")(2.10), "2"); 363 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "")(2.01), "2"); 364 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "")(2.11), "2.1"); 365 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(10, "e")(2.10), "2e+0"); 366 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "e")(2.01), "2.0e+0"); 367 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "e")(2.11), "2.1e+0"); 368 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(10, "g")(2.10), "2"); 369 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "g")(2.01), "2.0"); 370 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "g")(2.11), "2.1"); 371 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(10, "r")(2.10e6), "2000000"); 372 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "r")(2.01e6), "2000000"); 373 | assert.strictEqual(scalePow().domain([0, 9]).tickFormat(100, "r")(2.11e6), "2100000"); 374 | assert.strictEqual(scalePow().domain([0, 0.9]).tickFormat(10, "p")(0.210), "20%"); 375 | assert.strictEqual(scalePow().domain([0.19, 0.21]).tickFormat(10, "p")(0.201), "20.1%"); 376 | }); 377 | 378 | it("pow.tickFormat(count, specifier) sets the appropriate prefix precision if not specified", () => { 379 | assert.strictEqual(scalePow().domain([0, 1e6]).tickFormat(10, "$s")(0.51e6), "$0.5M"); 380 | assert.strictEqual(scalePow().domain([0, 1e6]).tickFormat(100, "$s")(0.501e6), "$0.50M"); 381 | }); 382 | 383 | it("pow.copy() returns a copy with changes to the domain are isolated", () => { 384 | const x = scalePow(), y = x.copy(); 385 | x.domain([1, 2]); 386 | assert.deepStrictEqual(y.domain(), [0, 1]); 387 | assert.strictEqual(x(1), 0); 388 | assert.strictEqual(y(1), 1); 389 | y.domain([2, 3]); 390 | assert.strictEqual(x(2), 1); 391 | assert.strictEqual(y(2), 0); 392 | assert.deepStrictEqual(x.domain(), [1, 2]); 393 | assert.deepStrictEqual(y.domain(), [2, 3]); 394 | const y2 = x.domain([1, 1.9]).copy(); 395 | x.nice(5); 396 | assert.deepStrictEqual(x.domain(), [1, 2]); 397 | assert.deepStrictEqual(y2.domain(), [1, 1.9]); 398 | }); 399 | 400 | it("pow.copy() returns a copy with changes to the range are isolated", () => { 401 | const x = scalePow(), y = x.copy(); 402 | x.range([1, 2]); 403 | assert.strictEqual(x.invert(1), 0); 404 | assert.strictEqual(y.invert(1), 1); 405 | assert.deepStrictEqual(y.range(), [0, 1]); 406 | y.range([2, 3]); 407 | assert.strictEqual(x.invert(2), 1); 408 | assert.strictEqual(y.invert(2), 0); 409 | assert.deepStrictEqual(x.range(), [1, 2]); 410 | assert.deepStrictEqual(y.range(), [2, 3]); 411 | }); 412 | 413 | it("pow.copy() returns a copy with changes to the interpolator are isolated", () => { 414 | const x = scalePow().range(["red", "blue"]), 415 | y = x.copy(), 416 | i0 = x.interpolate(), 417 | i1 = function(a, b) { return function() { return b; }; }; 418 | x.interpolate(i1); 419 | assert.strictEqual(y.interpolate(), i0); 420 | assert.strictEqual(x(0.5), "blue"); 421 | assert.strictEqual(y(0.5), "rgb(128, 0, 128)"); 422 | }); 423 | 424 | it("pow.copy() returns a copy with changes to clamping are isolated", () => { 425 | const x = scalePow().clamp(true), y = x.copy(); 426 | x.clamp(false); 427 | assert.strictEqual(x(2), 2); 428 | assert.strictEqual(y(2), 1); 429 | assert.strictEqual(y.clamp(), true); 430 | y.clamp(false); 431 | assert.strictEqual(x(2), 2); 432 | assert.strictEqual(y(2), 2); 433 | assert.strictEqual(x.clamp(), false); 434 | }); 435 | 436 | it("pow().clamp(true).invert(x) cannot return a value outside the domain", () => { 437 | const x = scalePow().exponent(0.5).domain([1, 20]).clamp(true); 438 | assert.strictEqual(x.invert(0), 1); 439 | assert.strictEqual(x.invert(1), 20); 440 | }); 441 | 442 | it("scaleSqrt() is an alias for pow().exponent(0.5)", () => { 443 | const s = scaleSqrt(); 444 | assert.strictEqual(s.exponent(), 0.5); 445 | assertInDelta(s(0.5), Math.SQRT1_2, 1e-6); 446 | assertInDelta(s.invert(Math.SQRT1_2), 0.5, 1e-6); 447 | }); 448 | -------------------------------------------------------------------------------- /test/linear-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {scaleLinear} from "../src/index.js"; 3 | import {roundEpsilon} from "./roundEpsilon.js"; 4 | 5 | it("scaleLinear() has the expected defaults", () => { 6 | const s = scaleLinear(); 7 | assert.deepStrictEqual(s.domain(), [0, 1]); 8 | assert.deepStrictEqual(s.range(), [0, 1]); 9 | assert.strictEqual(s.clamp(), false); 10 | assert.strictEqual(s.unknown(), undefined); 11 | assert.deepStrictEqual(s.interpolate()({array: ["red"]}, {array: ["blue"]})(0.5), {array: ["rgb(128, 0, 128)"]}); 12 | }); 13 | 14 | it("scaleLinear(range) sets the range", () => { 15 | const s = scaleLinear([1, 2]); 16 | assert.deepStrictEqual(s.domain(), [0, 1]); 17 | assert.deepStrictEqual(s.range(), [1, 2]); 18 | assert.strictEqual(s(0.5), 1.5); 19 | }); 20 | 21 | it("scaleLinear(domain, range) sets the domain and range", () => { 22 | const s = scaleLinear([1, 2], [3, 4]); 23 | assert.deepStrictEqual(s.domain(), [1, 2]); 24 | assert.deepStrictEqual(s.range(), [3, 4]); 25 | assert.strictEqual(s(1.5), 3.5); 26 | }); 27 | 28 | it("linear(x) maps a domain value x to a range value y", () => { 29 | assert.strictEqual(scaleLinear().range([1, 2])(0.5), 1.5); 30 | }); 31 | 32 | it("linear(x) ignores extra range values if the domain is smaller than the range", () => { 33 | assert.strictEqual(scaleLinear().domain([-10, 0]).range([0, 1, 2]).clamp(true)(-5), 0.5); 34 | assert.strictEqual(scaleLinear().domain([-10, 0]).range([0, 1, 2]).clamp(true)(50), 1); 35 | }); 36 | 37 | it("linear(x) ignores extra domain values if the range is smaller than the domain", () => { 38 | assert.strictEqual(scaleLinear().domain([-10, 0, 100]).range([0, 1]).clamp(true)(-5), 0.5); 39 | assert.strictEqual(scaleLinear().domain([-10, 0, 100]).range([0, 1]).clamp(true)(50), 1); 40 | }); 41 | 42 | it("linear(x) maps an empty domain to the middle of the range", () => { 43 | assert.strictEqual(scaleLinear().domain([0, 0]).range([1, 2])(0), 1.5); 44 | assert.strictEqual(scaleLinear().domain([0, 0]).range([2, 1])(1), 1.5); 45 | }); 46 | 47 | it("linear(x) can map a bilinear domain with two values to the corresponding range", () => { 48 | const s = scaleLinear().domain([1, 2]); 49 | assert.deepStrictEqual(s.domain(), [1, 2]); 50 | assert.strictEqual(s(0.5), -0.5); 51 | assert.strictEqual(s(1.0), 0.0); 52 | assert.strictEqual(s(1.5), 0.5); 53 | assert.strictEqual(s(2.0), 1.0); 54 | assert.strictEqual(s(2.5), 1.5); 55 | assert.strictEqual(s.invert(-0.5), 0.5); 56 | assert.strictEqual(s.invert( 0.0), 1.0); 57 | assert.strictEqual(s.invert( 0.5), 1.5); 58 | assert.strictEqual(s.invert( 1.0), 2.0); 59 | assert.strictEqual(s.invert( 1.5), 2.5); 60 | }); 61 | 62 | it("linear(x) can map a polylinear domain with more than two values to the corresponding range", () => { 63 | const s = scaleLinear().domain([-10, 0, 100]).range(["red", "white", "green"]); 64 | assert.deepStrictEqual(s.domain(), [-10, 0, 100]); 65 | assert.strictEqual(s(-5), "rgb(255, 128, 128)"); 66 | assert.strictEqual(s(50), "rgb(128, 192, 128)"); 67 | assert.strictEqual(s(75), "rgb(64, 160, 64)"); 68 | s.domain([4, 2, 1]).range([1, 2, 4]); 69 | assert.strictEqual(s(1.5), 3); 70 | assert.strictEqual(s(3), 1.5); 71 | assert.strictEqual(s.invert(1.5), 3); 72 | assert.strictEqual(s.invert(3), 1.5); 73 | s.domain([1, 2, 4]).range([4, 2, 1]); 74 | assert.strictEqual(s(1.5), 3); 75 | assert.strictEqual(s(3), 1.5); 76 | assert.strictEqual(s.invert(1.5), 3); 77 | assert.strictEqual(s.invert(3), 1.5); 78 | }); 79 | 80 | it("linear.invert(y) maps a range value y to a domain value x", () => { 81 | assert.strictEqual(scaleLinear().range([1, 2]).invert(1.5), 0.5); 82 | }); 83 | 84 | it("linear.invert(y) maps an empty range to the middle of the domain", () => { 85 | assert.strictEqual(scaleLinear().domain([1, 2]).range([0, 0]).invert(0), 1.5); 86 | assert.strictEqual(scaleLinear().domain([2, 1]).range([0, 0]).invert(1), 1.5); 87 | }); 88 | 89 | it("linear.invert(y) coerces range values to numbers", () => { 90 | assert.strictEqual(scaleLinear().range(["0", "2"]).invert("1"), 0.5); 91 | assert.strictEqual(scaleLinear().range([new Date(1990, 0, 1), new Date(1991, 0, 1)]).invert(new Date(1990, 6, 2, 13)), 0.5); 92 | }); 93 | 94 | it("linear.invert(y) returns NaN if the range is not coercible to number", () => { 95 | assert(isNaN(scaleLinear().range(["#000", "#fff"]).invert("#999"))); 96 | assert(isNaN(scaleLinear().range([0, "#fff"]).invert("#999"))); 97 | }); 98 | 99 | it("linear.domain(domain) accepts an array of numbers", () => { 100 | assert.deepStrictEqual(scaleLinear().domain([]).domain(), []); 101 | assert.deepStrictEqual(scaleLinear().domain([1, 0]).domain(), [1, 0]); 102 | assert.deepStrictEqual(scaleLinear().domain([1, 2, 3]).domain(), [1, 2, 3]); 103 | }); 104 | 105 | it("linear.domain(domain) coerces domain values to numbers", () => { 106 | assert.deepStrictEqual(scaleLinear().domain([new Date(1990, 0, 1), new Date(1991, 0, 1)]).domain(), [631180800000, 662716800000]); 107 | assert.deepStrictEqual(scaleLinear().domain(["0.0", "1.0"]).domain(), [0, 1]); 108 | assert.deepStrictEqual(scaleLinear().domain([new Number(0), new Number(1)]).domain(), [0, 1]); 109 | }); 110 | 111 | it("linear.domain(domain) accepts an iterable", () => { 112 | assert.deepStrictEqual(scaleLinear().domain(new Set([1, 2])).domain(), [1, 2]); 113 | }); 114 | 115 | it("linear.domain(domain) makes a copy of domain values", () => { 116 | const d = [1, 2], s = scaleLinear().domain(d); 117 | assert.deepStrictEqual(s.domain(), [1, 2]); 118 | d.push(3); 119 | assert.deepStrictEqual(s.domain(), [1, 2]); 120 | assert.deepStrictEqual(d, [1, 2, 3]); 121 | }); 122 | 123 | it("linear.domain() returns a copy of domain values", () => { 124 | const s = scaleLinear(), d = s.domain(); 125 | assert.deepStrictEqual(d, [0, 1]); 126 | d.push(3); 127 | assert.deepStrictEqual(s.domain(), [0, 1]); 128 | }); 129 | 130 | it("linear.range(range) does not coerce range to numbers", () => { 131 | const s = scaleLinear().range(["0px", "2px"]); 132 | assert.deepStrictEqual(s.range(), ["0px", "2px"]); 133 | assert.strictEqual(s(0.5), "1px"); 134 | }); 135 | 136 | it("linear.range(range) accepts an iterable", () => { 137 | assert.deepStrictEqual(scaleLinear().range(new Set([1, 2])).range(), [1, 2]); 138 | }); 139 | 140 | it("linear.range(range) can accept range values as colors", () => { 141 | assert.strictEqual(scaleLinear().range(["red", "blue"])(0.5), "rgb(128, 0, 128)"); 142 | assert.strictEqual(scaleLinear().range(["#ff0000", "#0000ff"])(0.5), "rgb(128, 0, 128)"); 143 | assert.strictEqual(scaleLinear().range(["#f00", "#00f"])(0.5), "rgb(128, 0, 128)"); 144 | assert.strictEqual(scaleLinear().range(["rgb(255,0,0)", "hsl(240,100%,50%)"])(0.5), "rgb(128, 0, 128)"); 145 | assert.strictEqual(scaleLinear().range(["rgb(100%,0%,0%)", "hsl(240,100%,50%)"])(0.5), "rgb(128, 0, 128)"); 146 | assert.strictEqual(scaleLinear().range(["hsl(0,100%,50%)", "hsl(240,100%,50%)"])(0.5), "rgb(128, 0, 128)"); 147 | }); 148 | 149 | it("linear.range(range) can accept range values as arrays or objects", () => { 150 | assert.deepStrictEqual(scaleLinear().range([{color: "red"}, {color: "blue"}])(0.5), {color: "rgb(128, 0, 128)"}); 151 | assert.deepStrictEqual(scaleLinear().range([["red"], ["blue"]])(0.5), ["rgb(128, 0, 128)"]); 152 | }); 153 | 154 | it("linear.range(range) makes a copy of range values", () => { 155 | const r = [1, 2], s = scaleLinear().range(r); 156 | assert.deepStrictEqual(s.range(), [1, 2]); 157 | r.push(3); 158 | assert.deepStrictEqual(s.range(), [1, 2]); 159 | assert.deepStrictEqual(r, [1, 2, 3]); 160 | }); 161 | 162 | it("linear.range() returns a copy of range values", () => { 163 | const s = scaleLinear(), r = s.range(); 164 | assert.deepStrictEqual(r, [0, 1]); 165 | r.push(3); 166 | assert.deepStrictEqual(s.range(), [0, 1]); 167 | }); 168 | 169 | it("linear.rangeRound(range) is an alias for linear.range(range).interpolate(interpolateRound)", () => { 170 | assert.strictEqual(scaleLinear().rangeRound([0, 10])(0.59), 6); 171 | }); 172 | 173 | it("linear.rangeRound(range) accepts an iterable", () => { 174 | assert.deepStrictEqual(scaleLinear().rangeRound(new Set([1, 2])).range(), [1, 2]); 175 | }); 176 | 177 | it("linear.unknown(value) sets the return value for undefined, null, and NaN input", () => { 178 | const s = scaleLinear().unknown(-1); 179 | assert.strictEqual(s(null), -1); 180 | assert.strictEqual(s(undefined), -1); 181 | assert.strictEqual(s(NaN), -1); 182 | assert.strictEqual(s("N/A"), -1); 183 | assert.strictEqual(s(0.4), 0.4); 184 | }); 185 | 186 | it("linear.clamp() is false by default", () => { 187 | assert.strictEqual(scaleLinear().clamp(), false); 188 | assert.strictEqual(scaleLinear().range([10, 20])(2), 30); 189 | assert.strictEqual(scaleLinear().range([10, 20])(-1), 0); 190 | assert.strictEqual(scaleLinear().range([10, 20]).invert(30), 2); 191 | assert.strictEqual(scaleLinear().range([10, 20]).invert(0), -1); 192 | }); 193 | 194 | it("linear.clamp(true) restricts output values to the range", () => { 195 | assert.strictEqual(scaleLinear().clamp(true).range([10, 20])(2), 20); 196 | assert.strictEqual(scaleLinear().clamp(true).range([10, 20])(-1), 10); 197 | }); 198 | 199 | it("linear.clamp(true) restricts input values to the domain", () => { 200 | assert.strictEqual(scaleLinear().clamp(true).range([10, 20]).invert(30), 1); 201 | assert.strictEqual(scaleLinear().clamp(true).range([10, 20]).invert(0), 0); 202 | }); 203 | 204 | it("linear.clamp(clamp) coerces the specified clamp value to a boolean", () => { 205 | assert.strictEqual(scaleLinear().clamp("true").clamp(), true); 206 | assert.strictEqual(scaleLinear().clamp(1).clamp(), true); 207 | assert.strictEqual(scaleLinear().clamp("").clamp(), false); 208 | assert.strictEqual(scaleLinear().clamp(0).clamp(), false); 209 | }); 210 | 211 | it("linear.interpolate(interpolate) takes a custom interpolator factory", () => { 212 | function interpolate(a, b) { return function(t) { return [a, b, t]; }; } 213 | const s = scaleLinear().domain([10, 20]).range(["a", "b"]).interpolate(interpolate); 214 | assert.strictEqual(s.interpolate(), interpolate); 215 | assert.deepStrictEqual(s(15), ["a", "b", 0.5]); 216 | }); 217 | 218 | it("linear.nice() is an alias for linear.nice(10)", () => { 219 | assert.deepStrictEqual(scaleLinear().domain([0, 0.96]).nice().domain(), [0, 1]); 220 | assert.deepStrictEqual(scaleLinear().domain([0, 96]).nice().domain(), [0, 100]); 221 | }); 222 | 223 | it("linear.nice(count) extends the domain to match the desired ticks", () => { 224 | assert.deepStrictEqual(scaleLinear().domain([0, 0.96]).nice(10).domain(), [0, 1]); 225 | assert.deepStrictEqual(scaleLinear().domain([0, 96]).nice(10).domain(), [0, 100]); 226 | assert.deepStrictEqual(scaleLinear().domain([0.96, 0]).nice(10).domain(), [1, 0]); 227 | assert.deepStrictEqual(scaleLinear().domain([96, 0]).nice(10).domain(), [100, 0]); 228 | assert.deepStrictEqual(scaleLinear().domain([0, -0.96]).nice(10).domain(), [0, -1]); 229 | assert.deepStrictEqual(scaleLinear().domain([0, -96]).nice(10).domain(), [0, -100]); 230 | assert.deepStrictEqual(scaleLinear().domain([-0.96, 0]).nice(10).domain(), [-1, 0]); 231 | assert.deepStrictEqual(scaleLinear().domain([-96, 0]).nice(10).domain(), [-100, 0]); 232 | assert.deepStrictEqual(scaleLinear().domain([-0.1, 51.1]).nice(8).domain(), [-10, 60]); 233 | }); 234 | 235 | it("linear.nice(count) nices the domain, extending it to round numbers", () => { 236 | assert.deepStrictEqual(scaleLinear().domain([1.1, 10.9]).nice(10).domain(), [1, 11]); 237 | assert.deepStrictEqual(scaleLinear().domain([10.9, 1.1]).nice(10).domain(), [11, 1]); 238 | assert.deepStrictEqual(scaleLinear().domain([0.7, 11.001]).nice(10).domain(), [0, 12]); 239 | assert.deepStrictEqual(scaleLinear().domain([123.1, 6.7]).nice(10).domain(), [130, 0]); 240 | assert.deepStrictEqual(scaleLinear().domain([0, 0.49]).nice(10).domain(), [0, 0.5]); 241 | assert.deepStrictEqual(scaleLinear().domain([0, 14.1]).nice(5).domain(), [0, 20]); 242 | assert.deepStrictEqual(scaleLinear().domain([0, 15]).nice(5).domain(), [0, 20]); 243 | }); 244 | 245 | it("linear.nice(count) has no effect on degenerate domains", () => { 246 | assert.deepStrictEqual(scaleLinear().domain([0, 0]).nice(10).domain(), [0, 0]); 247 | assert.deepStrictEqual(scaleLinear().domain([0.5, 0.5]).nice(10).domain(), [0.5, 0.5]); 248 | }); 249 | 250 | it("linear.nice(count) nicing a polylinear domain only affects the extent", () => { 251 | assert.deepStrictEqual(scaleLinear().domain([1.1, 1, 2, 3, 10.9]).nice(10).domain(), [1, 1, 2, 3, 11]); 252 | assert.deepStrictEqual(scaleLinear().domain([123.1, 1, 2, 3, -0.9]).nice(10).domain(), [130, 1, 2, 3, -10]); 253 | }); 254 | 255 | it("linear.nice(count) accepts a tick count to control nicing step", () => { 256 | assert.deepStrictEqual(scaleLinear().domain([12, 87]).nice(5).domain(), [0, 100]); 257 | assert.deepStrictEqual(scaleLinear().domain([12, 87]).nice(10).domain(), [10, 90]); 258 | assert.deepStrictEqual(scaleLinear().domain([12, 87]).nice(100).domain(), [12, 87]); 259 | }); 260 | 261 | it("linear.ticks(count) returns the expected ticks for an ascending domain", () => { 262 | const s = scaleLinear(); 263 | assert.deepStrictEqual(s.ticks(10).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 264 | assert.deepStrictEqual(s.ticks(9).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 265 | assert.deepStrictEqual(s.ticks(8).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 266 | assert.deepStrictEqual(s.ticks(7).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 267 | assert.deepStrictEqual(s.ticks(6).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 268 | assert.deepStrictEqual(s.ticks(5).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 269 | assert.deepStrictEqual(s.ticks(4).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 270 | assert.deepStrictEqual(s.ticks(3).map(roundEpsilon), [0.0, 0.5, 1.0]); 271 | assert.deepStrictEqual(s.ticks(2).map(roundEpsilon), [0.0, 0.5, 1.0]); 272 | assert.deepStrictEqual(s.ticks(1).map(roundEpsilon), [0.0, 1.0]); 273 | s.domain([-100, 100]); 274 | assert.deepStrictEqual(s.ticks(10), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 275 | assert.deepStrictEqual(s.ticks(9), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 276 | assert.deepStrictEqual(s.ticks(8), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 277 | assert.deepStrictEqual(s.ticks(7), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 278 | assert.deepStrictEqual(s.ticks(6), [-100, -50, 0, 50, 100]); 279 | assert.deepStrictEqual(s.ticks(5), [-100, -50, 0, 50, 100]); 280 | assert.deepStrictEqual(s.ticks(4), [-100, -50, 0, 50, 100]); 281 | assert.deepStrictEqual(s.ticks(3), [-100, -50, 0, 50, 100]); 282 | assert.deepStrictEqual(s.ticks(2), [-100, 0, 100]); 283 | assert.deepStrictEqual(s.ticks(1), [ 0 ]); 284 | }); 285 | 286 | it("linear.ticks(count) returns the expected ticks for a descending domain", () => { 287 | const s = scaleLinear().domain([1, 0]); 288 | assert.deepStrictEqual(s.ticks(10).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].reverse()); 289 | assert.deepStrictEqual(s.ticks(9).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].reverse()); 290 | assert.deepStrictEqual(s.ticks(8).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].reverse()); 291 | assert.deepStrictEqual(s.ticks(7).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 292 | assert.deepStrictEqual(s.ticks(6).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 293 | assert.deepStrictEqual(s.ticks(5).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 294 | assert.deepStrictEqual(s.ticks(4).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].reverse()); 295 | assert.deepStrictEqual(s.ticks(3).map(roundEpsilon), [0.0, 0.5, 1.0].reverse()); 296 | assert.deepStrictEqual(s.ticks(2).map(roundEpsilon), [0.0, 0.5, 1.0].reverse()); 297 | assert.deepStrictEqual(s.ticks(1).map(roundEpsilon), [0.0, 1.0].reverse()); 298 | s.domain([100, -100]); 299 | assert.deepStrictEqual(s.ticks(10), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 300 | assert.deepStrictEqual(s.ticks(9), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 301 | assert.deepStrictEqual(s.ticks(8), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 302 | assert.deepStrictEqual(s.ticks(7), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100].reverse()); 303 | assert.deepStrictEqual(s.ticks(6), [-100, -50, 0, 50, 100].reverse()); 304 | assert.deepStrictEqual(s.ticks(5), [-100, -50, 0, 50, 100].reverse()); 305 | assert.deepStrictEqual(s.ticks(4), [-100, -50, 0, 50, 100].reverse()); 306 | assert.deepStrictEqual(s.ticks(3), [-100, -50, 0, 50, 100].reverse()); 307 | assert.deepStrictEqual(s.ticks(2), [-100, 0, 100].reverse()); 308 | assert.deepStrictEqual(s.ticks(1), [ 0 ].reverse()); 309 | }); 310 | 311 | it("linear.ticks(count) returns the expected ticks for a polylinear domain", () => { 312 | const s = scaleLinear().domain([0, 0.25, 0.9, 1]); 313 | assert.deepStrictEqual(s.ticks(10).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 314 | assert.deepStrictEqual(s.ticks(9).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 315 | assert.deepStrictEqual(s.ticks(8).map(roundEpsilon), [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]); 316 | assert.deepStrictEqual(s.ticks(7).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 317 | assert.deepStrictEqual(s.ticks(6).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 318 | assert.deepStrictEqual(s.ticks(5).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 319 | assert.deepStrictEqual(s.ticks(4).map(roundEpsilon), [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]); 320 | assert.deepStrictEqual(s.ticks(3).map(roundEpsilon), [0.0, 0.5, 1.0]); 321 | assert.deepStrictEqual(s.ticks(2).map(roundEpsilon), [0.0, 0.5, 1.0]); 322 | assert.deepStrictEqual(s.ticks(1).map(roundEpsilon), [0.0, 1.0]); 323 | s.domain([-100, 0, 100]); 324 | assert.deepStrictEqual(s.ticks(10), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 325 | assert.deepStrictEqual(s.ticks(9), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 326 | assert.deepStrictEqual(s.ticks(8), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 327 | assert.deepStrictEqual(s.ticks(7), [-100, -80, -60, -40, -20, 0, 20, 40, 60, 80, 100]); 328 | assert.deepStrictEqual(s.ticks(6), [-100, -50, 0, 50, 100]); 329 | assert.deepStrictEqual(s.ticks(5), [-100, -50, 0, 50, 100]); 330 | assert.deepStrictEqual(s.ticks(4), [-100, -50, 0, 50, 100]); 331 | assert.deepStrictEqual(s.ticks(3), [-100, -50, 0, 50, 100]); 332 | assert.deepStrictEqual(s.ticks(2), [-100, 0, 100]); 333 | assert.deepStrictEqual(s.ticks(1), [ 0 ]); 334 | }); 335 | 336 | it("linear.ticks(X) spans linear.nice(X).domain()", () => { 337 | function check(domain, count) { 338 | const s = scaleLinear().domain(domain).nice(count); 339 | const ticks = s.ticks(count); 340 | assert.deepStrictEqual([ticks[0], ticks[ticks.length - 1]], s.domain()); 341 | } 342 | check([1, 9], 2); 343 | check([1, 9], 3); 344 | check([1, 9], 4); 345 | check([8, 9], 2); 346 | check([8, 9], 3); 347 | check([8, 9], 4); 348 | check([1, 21], 2); 349 | check([2, 21], 2); 350 | check([3, 21], 2); 351 | check([4, 21], 2); 352 | check([5, 21], 2); 353 | check([6, 21], 2); 354 | check([7, 21], 2); 355 | check([8, 21], 2); 356 | check([9, 21], 2); 357 | check([10, 21], 2); 358 | check([11, 21], 2); 359 | }) 360 | 361 | it("linear.ticks(count) returns the empty array if count is not a positive integer", () => { 362 | const s = scaleLinear(); 363 | assert.deepStrictEqual(s.ticks(NaN), []); 364 | assert.deepStrictEqual(s.ticks(0), []); 365 | assert.deepStrictEqual(s.ticks(-1), []); 366 | assert.deepStrictEqual(s.ticks(Infinity), []); 367 | }); 368 | 369 | it("linear.ticks() is an alias for linear.ticks(10)", () => { 370 | const s = scaleLinear(); 371 | assert.deepStrictEqual(s.ticks(), s.ticks(10)); 372 | }); 373 | 374 | it("linear.tickFormat() is an alias for linear.tickFormat(10)", () => { 375 | assert.strictEqual(scaleLinear().tickFormat()(0.2), "0.2"); 376 | assert.strictEqual(scaleLinear().domain([-100, 100]).tickFormat()(-20), "−20"); 377 | }); 378 | 379 | it("linear.tickFormat(count) returns a format suitable for the ticks", () => { 380 | assert.strictEqual(scaleLinear().tickFormat(10)(0.2), "0.2"); 381 | assert.strictEqual(scaleLinear().tickFormat(20)(0.2), "0.20"); 382 | assert.strictEqual(scaleLinear().domain([-100, 100]).tickFormat(10)(-20), "−20"); 383 | }); 384 | 385 | it("linear.tickFormat(count, specifier) sets the appropriate fixed precision if not specified", () => { 386 | assert.strictEqual(scaleLinear().tickFormat(10, "+f")(0.2), "+0.2"); 387 | assert.strictEqual(scaleLinear().tickFormat(20, "+f")(0.2), "+0.20"); 388 | assert.strictEqual(scaleLinear().tickFormat(10, "+%")(0.2), "+20%"); 389 | assert.strictEqual(scaleLinear().domain([0.19, 0.21]).tickFormat(10, "+%")(0.2), "+20.0%"); 390 | }); 391 | 392 | it("linear.tickFormat(count, specifier) sets the appropriate round precision if not specified", () => { 393 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(10, "")(2.10), "2"); 394 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "")(2.01), "2"); 395 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "")(2.11), "2.1"); 396 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(10, "e")(2.10), "2e+0"); 397 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "e")(2.01), "2.0e+0"); 398 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "e")(2.11), "2.1e+0"); 399 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(10, "g")(2.10), "2"); 400 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "g")(2.01), "2.0"); 401 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "g")(2.11), "2.1"); 402 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(10, "r")(2.10e6), "2000000"); 403 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "r")(2.01e6), "2000000"); 404 | assert.strictEqual(scaleLinear().domain([0, 9]).tickFormat(100, "r")(2.11e6), "2100000"); 405 | assert.strictEqual(scaleLinear().domain([0, 0.9]).tickFormat(10, "p")(0.210), "20%"); 406 | assert.strictEqual(scaleLinear().domain([0.19, 0.21]).tickFormat(10, "p")(0.201), "20.1%"); 407 | }); 408 | 409 | it("linear.tickFormat(count, specifier) sets the appropriate prefix precision if not specified", () => { 410 | assert.strictEqual(scaleLinear().domain([0, 1e6]).tickFormat(10, "$s")(0.51e6), "$0.5M"); 411 | assert.strictEqual(scaleLinear().domain([0, 1e6]).tickFormat(100, "$s")(0.501e6), "$0.50M"); 412 | }); 413 | 414 | it("linear.tickFormat() uses the default precision when the domain is invalid", () => { 415 | const f = scaleLinear().domain([0, NaN]).tickFormat(); 416 | assert.strictEqual(f + "", " >-,f"); 417 | assert.strictEqual(f(0.12), "0.120000"); 418 | }); 419 | 420 | it("linear.copy() returns a copy with changes to the domain are isolated", () => { 421 | const x = scaleLinear(), y = x.copy(); 422 | x.domain([1, 2]); 423 | assert.deepStrictEqual(y.domain(), [0, 1]); 424 | assert.strictEqual(x(1), 0); 425 | assert.strictEqual(y(1), 1); 426 | y.domain([2, 3]); 427 | assert.strictEqual(x(2), 1); 428 | assert.strictEqual(y(2), 0); 429 | assert.deepStrictEqual(x.domain(), [1, 2]); 430 | assert.deepStrictEqual(y.domain(), [2, 3]); 431 | const y2 = x.domain([1, 1.9]).copy(); 432 | x.nice(5); 433 | assert.deepStrictEqual(x.domain(), [1, 2]); 434 | assert.deepStrictEqual(y2.domain(), [1, 1.9]); 435 | }); 436 | 437 | it("linear.copy() returns a copy with changes to the range are isolated", () => { 438 | const x = scaleLinear(), y = x.copy(); 439 | x.range([1, 2]); 440 | assert.strictEqual(x.invert(1), 0); 441 | assert.strictEqual(y.invert(1), 1); 442 | assert.deepStrictEqual(y.range(), [0, 1]); 443 | y.range([2, 3]); 444 | assert.strictEqual(x.invert(2), 1); 445 | assert.strictEqual(y.invert(2), 0); 446 | assert.deepStrictEqual(x.range(), [1, 2]); 447 | assert.deepStrictEqual(y.range(), [2, 3]); 448 | }); 449 | 450 | it("linear.copy() returns a copy with changes to the interpolator are isolated", () => { 451 | const x = scaleLinear().range(["red", "blue"]); 452 | const y = x.copy(); 453 | const i0 = x.interpolate(); 454 | const i1 = function(a, b) { return function() { return b; }; }; 455 | x.interpolate(i1); 456 | assert.strictEqual(y.interpolate(), i0); 457 | assert.strictEqual(x(0.5), "blue"); 458 | assert.strictEqual(y(0.5), "rgb(128, 0, 128)"); 459 | }); 460 | 461 | it("linear.copy() returns a copy with changes to clamping are isolated", () => { 462 | const x = scaleLinear().clamp(true), y = x.copy(); 463 | x.clamp(false); 464 | assert.strictEqual(x(2), 2); 465 | assert.strictEqual(y(2), 1); 466 | assert.strictEqual(y.clamp(), true); 467 | y.clamp(false); 468 | assert.strictEqual(x(2), 2); 469 | assert.strictEqual(y(2), 2); 470 | assert.strictEqual(x.clamp(), false); 471 | }); 472 | 473 | it("linear.copy() returns a copy with changes to the unknown value are isolated", () => { 474 | const x = scaleLinear(), y = x.copy(); 475 | x.unknown(2); 476 | assert.strictEqual(x(NaN), 2); 477 | assert.strictEqual(isNaN(y(NaN)), true); 478 | assert.strictEqual(y.unknown(), undefined); 479 | y.unknown(3); 480 | assert.strictEqual(x(NaN), 2); 481 | assert.strictEqual(y(NaN), 3); 482 | assert.strictEqual(x.unknown(), 2); 483 | }); 484 | --------------------------------------------------------------------------------