-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
Problem
Recharts' default tick generation frequently produces non-round, hard-to-read axis values like $6,000, $28,000, $51,000 instead of clean values like $0, $25,000, $50,000, $75,000, $100,000.
This is a long-standing issue (#2140, #777, #1164) that affects virtually every chart that uses numeric axes with auto-generated ticks. The standard workaround is to compute explicit ticks arrays and pass them manually — but this defeats the purpose of a charting library handling it automatically.
Root cause
getTickValuesFixedDomain() in src/util/scale/getNiceTickValues.ts doesn't produce aesthetically clean tick values when the domain is explicitly set. The main getNiceTickValues() does better but still struggles with tickCount respect.
D3's d3-scale.nice() solves this well by snapping to 1/2/5 multiples of powers of 10. The current Recharts algorithm attempts something similar but the results are often not "nice" in practice.
Proposed fix
I'd be happy to submit a PR improving getTickValuesFixedDomain() to:
- Always start ticks at 0 when the domain includes 0
- Use clean 1/2/2.5/5/10 step multiples (similar to D3's approach)
- Respect
tickCountmore accurately - Maintain backward compatibility with existing tests
Here's a minimal version of the algorithm that works well in practice:
function niceTicks(dataMax: number, targetCount: number = 5): number[] {
if (dataMax <= 0) return [0];
const rawStep = dataMax / targetCount;
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
const normalized = rawStep / magnitude;
let niceStep: number;
if (normalized <= 1) niceStep = 1 * magnitude;
else if (normalized <= 2) niceStep = 2 * magnitude;
else if (normalized <= 2.5) niceStep = 2.5 * magnitude;
else if (normalized <= 5) niceStep = 5 * magnitude;
else niceStep = 10 * magnitude;
const niceMax = Math.ceil(dataMax / niceStep) * niceStep;
const ticks: number[] = [];
for (let v = 0; v <= niceMax; v += niceStep) {
ticks.push(Math.round(v * 1e10) / 1e10);
}
return ticks;
}Before/After
Before (current Recharts default):
- X axis:
$6,000$28,000$51,000$75,000$100,000 - Y axis:
$6,500$13,000$19,500$26,000
After (with nice ticks):
- X axis:
$0$20,000$40,000$60,000$80,000$100,000 - Y axis:
$0$5,000$10,000$15,000$20,000$25,000$30,000
Context
- Related: Problems with specifying domain as ['dataMin', 'dataMax'], the ticks are not calculated correctly, allowDecimals does not work, tickCount is not respected. #2140 (PavelVanecek mentioned fixing in Jul 2025), <YAxis/> Not Respecting allowDecimals and tickCount Prop when domain is set to non-int values #777, Floating point imprecision skews YAxis ticks #1164, [feature] initialTickValue and tickModulus axis properties #1052
- Aware of PR Rendered ticks in hooks #6990 (rendered ticks in hooks) — happy to coordinate timing
- We've implemented this workaround in multiple projects and it would be great to have it built into Recharts