|
import {ticks} from "d3-array"; |
|
import {format, formatSpecifier} from "d3-format"; |
|
import nice from "./nice.js"; |
|
import {copy, transformer} from "./continuous.js"; |
|
import {initRange} from "./init.js"; |
|
|
|
function transformLog(x) { |
|
return Math.log(x); |
|
} |
|
|
|
function transformExp(x) { |
|
return Math.exp(x); |
|
} |
|
|
|
function transformLogn(x) { |
|
return -Math.log(-x); |
|
} |
|
|
|
function transformExpn(x) { |
|
return -Math.exp(-x); |
|
} |
|
|
|
function pow10(x) { |
|
return isFinite(x) ? +("1e" + x) : x < 0 ? 0 : x; |
|
} |
|
|
|
function powp(base) { |
|
return base === 10 ? pow10 |
|
: base === Math.E ? Math.exp |
|
: x => Math.pow(base, x); |
|
} |
|
|
|
function logp(base) { |
|
return base === Math.E ? Math.log |
|
: base === 10 && Math.log10 |
|
|| base === 2 && Math.log2 |
|
|| (base = Math.log(base), x => Math.log(x) / base); |
|
} |
|
|
|
function reflect(f) { |
|
return (x, k) => -f(-x, k); |
|
} |
|
|
|
export function loggish(transform) { |
|
const scale = transform(transformLog, transformExp); |
|
const domain = scale.domain; |
|
let base = 10; |
|
let logs; |
|
let pows; |
|
|
|
function rescale() { |
|
logs = logp(base), pows = powp(base); |
|
if (domain()[0] < 0) { |
|
logs = reflect(logs), pows = reflect(pows); |
|
transform(transformLogn, transformExpn); |
|
} else { |
|
transform(transformLog, transformExp); |
|
} |
|
return scale; |
|
} |
|
|
|
scale.base = function(_) { |
|
return arguments.length ? (base = +_, rescale()) : base; |
|
}; |
|
|
|
scale.domain = function(_) { |
|
return arguments.length ? (domain(_), rescale()) : domain(); |
|
}; |
|
|
|
scale.ticks = count => { |
|
const d = domain(); |
|
let u = d[0]; |
|
let v = d[d.length - 1]; |
|
const r = v < u; |
|
|
|
if (r) ([u, v] = [v, u]); |
|
|
|
let i = logs(u); |
|
let j = logs(v); |
|
let k; |
|
let t; |
|
const n = count == null ? 10 : +count; |
|
let z = []; |
|
|
|
if (!(base % 1) && j - i < n) { |
|
i = Math.floor(i), j = Math.ceil(j); |
|
if (u > 0) for (; i <= j; ++i) { |
|
for (k = 1; k < base; ++k) { |
|
t = i < 0 ? k / pows(-i) : k * pows(i); |
|
if (t < u) continue; |
|
if (t > v) break; |
|
z.push(t); |
|
} |
|
} else for (; i <= j; ++i) { |
|
for (k = base - 1; k >= 1; --k) { |
|
t = i > 0 ? k / pows(-i) : k * pows(i); |
|
if (t < u) continue; |
|
if (t > v) break; |
|
z.push(t); |
|
} |
|
} |
|
if (z.length * 2 < n) z = ticks(u, v, n); |
|
} else { |
|
z = ticks(i, j, Math.min(j - i, n)).map(pows); |
|
} |
|
return r ? z.reverse() : z; |
|
}; |
|
|
|
scale.tickFormat = (count, specifier) => { |
|
if (count == null) count = 10; |
|
if (specifier == null) specifier = base === 10 ? "s" : ","; |
|
if (typeof specifier !== "function") { |
|
if (!(base % 1) && (specifier = formatSpecifier(specifier)).precision == null) specifier.trim = true; |
|
specifier = format(specifier); |
|
} |
|
if (count === Infinity) return specifier; |
|
const k = Math.max(1, base * count / scale.ticks().length); |
|
return d => { |
|
let i = d / pows(Math.round(logs(d))); |
|
if (i * base < base - 0.5) i *= base; |
|
return i <= k ? specifier(d) : ""; |
|
}; |
|
}; |
|
|
|
scale.nice = () => { |
|
return domain(nice(domain(), { |
|
floor: x => pows(Math.floor(logs(x))), |
|
ceil: x => pows(Math.ceil(logs(x))) |
|
})); |
|
}; |
|
|
|
return scale; |
|
} |
|
|
|
export default function log() { |
|
const scale = loggish(transformer()).domain([1, 10]); |
|
scale.copy = () => copy(scale, log()).base(scale.base()); |
|
initRange.apply(scale, arguments); |
|
return scale; |
|
} |
|
|