From 7fe033252a73e6c34a923ba742a59d87e59d87be Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 27 Mar 2021 18:21:09 -0700 Subject: [PATCH] fix #278; robust log domain --- src/defined.js | 4 +++ src/scales/quantitative.js | 25 +++++++++---- test/output/logDegenerate.svg | 68 +++++++++++++++++++++++++++++++++++ test/plots/index.js | 1 + test/plots/log-degenerate.js | 12 +++++++ 5 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 test/output/logDegenerate.svg create mode 100644 test/plots/log-degenerate.js diff --git a/src/defined.js b/src/defined.js index 352c57b91c..617c3f345f 100644 --- a/src/defined.js +++ b/src/defined.js @@ -27,6 +27,10 @@ export function positive(x) { return x > 0 ? x : NaN; } +export function negative(x) { + return x < 0 ? x : NaN; +} + export function firstof(...values) { for (const v of values) { if (v !== undefined) { diff --git a/src/scales/quantitative.js b/src/scales/quantitative.js index d376c09229..a0183910c9 100644 --- a/src/scales/quantitative.js +++ b/src/scales/quantitative.js @@ -50,7 +50,7 @@ import { } from "d3"; import {scaleDiverging, scaleLinear, scaleLog, scalePow, scaleSymlog} from "d3"; import {registry, radius, color} from "./index.js"; -import {positive} from "../defined.js"; +import {positive, negative} from "../defined.js"; const constant = x => () => x; const flip = i => t => i(1 - t); @@ -179,8 +179,8 @@ export function ScalePow(key, channels, {exponent = 1, ...options}) { return ScaleQ(key, scalePow().exponent(exponent), channels, options); } -export function ScaleLog(key, channels, {base = 10, ...options}) { - return ScaleQ(key, scaleLog().base(base), channels, options); +export function ScaleLog(key, channels, {base = 10, domain = inferLogDomain(channels), ...options}) { + return ScaleQ(key, scaleLog().base(base), channels, {domain, ...options}); } export function ScaleSymlog(key, channels, {constant = 1, ...options}) { @@ -215,10 +215,10 @@ export function ScaleDiverging(key, channels, { return {type: "quantitative", invert, domain, scale}; } -function inferDomain(channels) { +function inferDomain(channels, f) { return [ - min(channels, ({value}) => value === undefined ? value : min(value)), - max(channels, ({value}) => value === undefined ? value : max(value)) + min(channels, ({value}) => value === undefined ? value : min(value, f)), + max(channels, ({value}) => value === undefined ? value : max(value, f)) ]; } @@ -232,3 +232,16 @@ function inferRadialRange(channels, domain) { const h25 = quantile(channels, 0.5, ({value}) => value === undefined ? NaN : quantile(value, 0.25, positive)); return domain.map(d => 3 * Math.sqrt(d / h25)); } + +function inferLogDomain(channels) { + for (const {value} of channels) { + if (value !== undefined) { + for (let v of value) { + v = +v; + if (v > 0) return inferDomain(channels, positive); + if (v < 0) return inferDomain(channels, negative); + } + } + } + return [1, 10]; +} diff --git a/test/output/logDegenerate.svg b/test/output/logDegenerate.svg new file mode 100644 index 0000000000..8f170ebf58 --- /dev/null +++ b/test/output/logDegenerate.svg @@ -0,0 +1,68 @@ + + + + 1e-1 + + + 2e-1 + + + 3e-1 + + + + + + + + + + + + + + + + + + + + + 1e+0 + + + 2e+0 + + + 3e+0 + + + + + + + + + + + + + + + + + + + + + 1e+1 + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index 53962fa05e..4ea36010d0 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -35,6 +35,7 @@ export {default as letterFrequencyBar} from "./letter-frequency-bar.js"; export {default as letterFrequencyColumn} from "./letter-frequency-column.js"; export {default as letterFrequencyDot} from "./letter-frequency-dot.js"; export {default as letterFrequencyLollipop} from "./letter-frequency-lollipop.js"; +export {default as logDegenerate} from "./log-degenerate.js"; export {default as metroInequality} from "./metro-inequality.js"; export {default as metroInequalityChange} from "./metro-inequality-change.js"; export {default as metroUnemployment} from "./metro-unemployment.js"; diff --git a/test/plots/log-degenerate.js b/test/plots/log-degenerate.js new file mode 100644 index 0000000000..1e5a9d06b2 --- /dev/null +++ b/test/plots/log-degenerate.js @@ -0,0 +1,12 @@ +import * as Plot from "@observablehq/plot"; + +export default async function() { + return Plot.plot({ + x: { + type: "log" + }, + marks: [ + Plot.dotX([0, 0.1, 1, 2, 10]) + ] + }); +}