Algorithm for "nice" grid line intervals on a graph

CPAN provides an implementation here (see source link)

See also Tickmark algorithm for a graph axis

FYI, with your sample data:

  • Maple: Min=8, Max=74, Labels=10,20,..,60,70, Ticks=10,12,14,..70,72
  • MATLAB: Min=10, Max=80, Labels=10,20,,..,60,80

I use the following algorithm. It's similar to others posted here but it's the first example in C#.

public static class AxisUtil
{
    public static float CalcStepSize(float range, float targetSteps)
    {
        // calculate an initial guess at step size
        var tempStep = range/targetSteps;

        // get the magnitude of the step size
        var mag = (float)Math.Floor(Math.Log10(tempStep));
        var magPow = (float)Math.Pow(10, mag);

        // calculate most significant digit of the new step size
        var magMsd = (int)(tempStep/magPow + 0.5);

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5)
            magMsd = 10;
        else if (magMsd > 2)
            magMsd = 5;
        else if (magMsd > 1)
            magMsd = 2;

        return magMsd*magPow;
    }
}

There are 2 pieces to the problem:

  1. Determine the order of magnitude involved, and
  2. Round to something convenient.

You can handle the first part by using logarithms:

range = max - min;  
exponent = int(log(range));       // See comment below.
magnitude = pow(10, exponent);

So, for example, if your range is from 50 - 1200, the exponent is 3 and the magnitude is 1000.

Then deal with the second part by deciding how many subdivisions you want in your grid:

value_per_division = magnitude / subdivisions;

This is a rough calculation because the exponent has been truncated to an integer. You may want to tweak the exponent calculation to handle boundary conditions better, e.g. by rounding instead of taking the int() if you end up with too many subdivisions.


I've done this with kind of a brute force method. First, figure out the maximum number of tick marks you can fit into the space. Divide the total range of values by the number of ticks; this is the minimum spacing of the tick. Now calculate the floor of the logarithm base 10 to get the magnitude of the tick, and divide by this value. You should end up with something in the range of 1 to 10. Simply choose the round number greater than or equal to the value and multiply it by the logarithm calculated earlier. This is your final tick spacing.

Example in Python:

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum, 10))
    residual = minimum / magnitude
    if residual > 5:
        tick = 10 * magnitude
    elif residual > 2:
        tick = 5 * magnitude
    elif residual > 1:
        tick = 2 * magnitude
    else:
        tick = magnitude
    return tick

Edit: you are free to alter the selection of "nice" intervals. One commenter appears to be dissatisfied with the selections provided, because the actual number of ticks can be up to 2.5 times less than the maximum. Here's a slight modification that defines a table for the nice intervals. In the example, I've expanded the selections so that the number of ticks won't be less than 3/5 of the maximum.

import bisect

def BestTick2(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum, 10))
    residual = minimum / magnitude
    # this table must begin with 1 and end with 10
    table = [1, 1.5, 2, 3, 5, 7, 10]
    tick = table[bisect.bisect_right(table, residual)] if residual < 10 else 10
    return tick * magnitude