/** * "Nice numbers" axis tick generator (Heckbert 1990) — produces clean, * human-friendly tick values (0 / 50 / 100 / 150 rather than 0 / 37 / 74 / * 111, an artifact of slicing frac*max into equal fractions) for any linear * axis, from just the data's max value. */ function niceNumber(range, round) { const exponent = Math.floor(Math.log10(range)) const fraction = range / 10 ** exponent let niceFraction if (round) { if (fraction < 1.5) niceFraction = 1 else if (fraction < 3) niceFraction = 2 else if (fraction < 7) niceFraction = 5 else niceFraction = 10 } else { if (fraction <= 1) niceFraction = 1 else if (fraction <= 2) niceFraction = 2 else if (fraction <= 5) niceFraction = 5 else niceFraction = 10 } return niceFraction * 10 ** exponent } /** * @param {number} maxValue - the largest value the axis must cover * @param {number} tickCount - approximate desired number of ticks * @returns {{ ticks: number[], niceMax: number }} */ export function niceTicks(maxValue, tickCount = 5) { if (!(maxValue > 0)) return { ticks: [0], niceMax: 1 } const niceRange = niceNumber(maxValue, false) const niceStep = niceNumber(niceRange / (tickCount - 1), true) const niceMax = Math.ceil(maxValue / niceStep) * niceStep const ticks = [] for (let v = 0; v <= niceMax + niceStep / 2; v += niceStep) { ticks.push(Math.round(v * 1e6) / 1e6) } return { ticks, niceMax } }