d3-array
d3-array copied to clipboard
optional log base for ticks functions
Adds optional base value to ticks, tickIncrement and tickStep functions. Using default base value of 10 results in no functional changes to existing code.
These changes enable rendering linear ticks for binary, natural and other log bases.
cc: @monfera
closes #232
@mbostock any feedback on this?
@Fil That's a really good point about the <base>^0. It appears this is a limitation with the implementation, I can't think of another way to get around this, anytime the power falls between 0 and 1 it just falls in that hole where the resulting step is 1 for any base (excluding 0).
y^x

I thought about forcing the power to be -1 for Math.E base such as...
function roundPower(power, base) {
if (base !== Math.E) return Math.floor(power);
return Math.floor(power) === 0 ? -1 : Math.floor(power);
}
export function tickIncrement(start, stop, count, base = 10) {
var step = (stop - start) / Math.max(0, count),
power = roundPower(Math.log(step) / Math.log(base) + Number.EPSILON, base),
error = step / Math.pow(base, power);
return power >= 0
? (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1) * Math.pow(base, power)
: -Math.pow(base, -power) / (error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1);
}
But then Math.E would be the only case this applies to which could be strange.
Another approach would be to allow 0.5 as a power. This would provide a much nicer tick values for cases in the 0 to 1 abyss. Something like...
function roundPower(power) {
if (Math.floor(power) !== 0) return Math.floor(power);
return power >= 0.5 ? 0.5 : 0;
}
With allowing 0.5 power you would get something like this...
// with power 0.5
ticks(0, 10, 3, 4) // [0, 4, 8]
// without power 0.5
ticks(0, 10, 3, 4) // [0, 5, 10]
This approach causes edge case issues with base
10
I'm inclined to just keep the value of power at 0. Another idea would be to round the power to some value, instead of flooring it, but that seems have a more broad impact and increased tick counts. Any thoughts?
@Fil Is it possible for me to update the examples linked in the readme? Such as https://observablehq.com/@d3/d3-ticks
@Fil any update on this?
My concerns here:
- Too many positional arguments (start, stop, count, base)
- Other implicit dependencies on base 10 (e.g., 2 and 5 are factors of 10, and 10 appears elsewhere in code)
- Tiny loss of precision and performance using Math.log(base) etc. instead of Math.LN10
- In practice will probably only be used for base = 2 and base = e
Overall, I think I would prefer to keep the existing methods as-is and have base-specific variants of d3.ticks and d3.tickIncrement for base = 2 (binary) and base = e (“natural”).
That's understandable, I can update the pr to have base-specific function variants.