Skip to content

Improve default tick generation to produce round numbers #7008

@MaxGhenis

Description

@MaxGhenis

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:

  1. Always start ticks at 0 when the domain includes 0
  2. Use clean 1/2/2.5/5/10 step multiples (similar to D3's approach)
  3. Respect tickCount more accurately
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions