Choosing an attractive linear scale for a graph's Y Axis

A long time ago I have written a graph module that covered this nicely. Digging in the grey mass gets the following:

  • Determine lower and upper bound of the data. (Beware of the special case where lower bound = upper bound!
  • Divide range into the required amount of ticks.
  • Round the tick range up into nice amounts.
  • Adjust the lower and upper bound accordingly.

Lets take your example:

15, 234, 140, 65, 90 with 10 ticks
  1. lower bound = 15
  2. upper bound = 234
  3. range = 234-15 = 219
  4. tick range = 21.9. This should be 25.0
  5. new lower bound = 25 * round(15/25) = 0
  6. new upper bound = 25 * round(1+235/25) = 250

So the range = 0,25,50,...,225,250

You can get the nice tick range with the following steps:

  1. divide by 10^x such that the result lies between 0.1 and 1.0 (including 0.1 excluding 1).
  2. translate accordingly:
    • 0.1 -> 0.1
    • <= 0.2 -> 0.2
    • <= 0.25 -> 0.25
    • <= 0.3 -> 0.3
    • <= 0.4 -> 0.4
    • <= 0.5 -> 0.5
    • <= 0.6 -> 0.6
    • <= 0.7 -> 0.7
    • <= 0.75 -> 0.75
    • <= 0.8 -> 0.8
    • <= 0.9 -> 0.9
    • <= 1.0 -> 1.0
  3. multiply by 10^x.

In this case, 21.9 is divided by 10^2 to get 0.219. This is <= 0.25 so we now have 0.25. Multiplied by 10^2 this gives 25.

Lets take a look at the same example with 8 ticks:

15, 234, 140, 65, 90 with 8 ticks
  1. lower bound = 15
  2. upper bound = 234
  3. range = 234-15 = 219
  4. tick range = 27.375
    1. Divide by 10^2 for 0.27375, translates to 0.3, which gives (multiplied by 10^2) 30.
  5. new lower bound = 30 * round(15/30) = 0
  6. new upper bound = 30 * round(1+235/30) = 240

Which give the result you requested ;-).

------ Added by KD ------

Here's code that achieves this algorithm without using lookup tables, etc...:

double range = ...;
int tickCount = ...;
double unroundedTickSize = range/(tickCount-1);
double x = Math.ceil(Math.log10(unroundedTickSize)-1);
double pow10x = Math.pow(10, x);
double roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
return roundedTickRange;

Generally speaking, the number of ticks includes the bottom tick, so the actual y-axis segments are one less than the number of ticks.


Here is a PHP example I am using. This function returns an array of pretty Y axis values that encompass the min and max Y values passed in. Of course, this routine could also be used for X axis values.

It allows you to "suggest" how many ticks you might want, but the routine will return what looks good. I have added some sample data and shown the results for these.

#!/usr/bin/php -q
<?php

function makeYaxis($yMin, $yMax, $ticks = 10)
{
  // This routine creates the Y axis values for a graph.
  //
  // Calculate Min amd Max graphical labels and graph
  // increments.  The number of ticks defaults to
  // 10 which is the SUGGESTED value.  Any tick value
  // entered is used as a suggested value which is
  // adjusted to be a 'pretty' value.
  //
  // Output will be an array of the Y axis values that
  // encompass the Y values.
  $result = array();
  // If yMin and yMax are identical, then
  // adjust the yMin and yMax values to actually
  // make a graph. Also avoids division by zero errors.
  if($yMin == $yMax)
  {
    $yMin = $yMin - 10;   // some small value
    $yMax = $yMax + 10;   // some small value
  }
  // Determine Range
  $range = $yMax - $yMin;
  // Adjust ticks if needed
  if($ticks < 2)
    $ticks = 2;
  else if($ticks > 2)
    $ticks -= 2;
  // Get raw step value
  $tempStep = $range/$ticks;
  // Calculate pretty step value
  $mag = floor(log10($tempStep));
  $magPow = pow(10,$mag);
  $magMsd = (int)($tempStep/$magPow + 0.5);
  $stepSize = $magMsd*$magPow;

  // build Y label array.
  // Lower and upper bounds calculations
  $lb = $stepSize * floor($yMin/$stepSize);
  $ub = $stepSize * ceil(($yMax/$stepSize));
  // Build array
  $val = $lb;
  while(1)
  {
    $result[] = $val;
    $val += $stepSize;
    if($val > $ub)
      break;
  }
  return $result;
}

// Create some sample data for demonstration purposes
$yMin = 60;
$yMax = 330;
$scale =  makeYaxis($yMin, $yMax);
print_r($scale);

$scale = makeYaxis($yMin, $yMax,5);
print_r($scale);

$yMin = 60847326;
$yMax = 73425330;
$scale =  makeYaxis($yMin, $yMax);
print_r($scale);
?>

Result output from sample data

# ./test1.php
Array
(
    [0] => 60
    [1] => 90
    [2] => 120
    [3] => 150
    [4] => 180
    [5] => 210
    [6] => 240
    [7] => 270
    [8] => 300
    [9] => 330
)

Array
(
    [0] => 0
    [1] => 90
    [2] => 180
    [3] => 270
    [4] => 360
)

Array
(
    [0] => 60000000
    [1] => 62000000
    [2] => 64000000
    [3] => 66000000
    [4] => 68000000
    [5] => 70000000
    [6] => 72000000
    [7] => 74000000
)

Try this code. I've used it in a few charting scenarios and it works well. It's pretty fast too.

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

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

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

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5.0)
            magMsd = 10.0f;
        else if (magMsd > 2.0)
            magMsd = 5.0f;
        else if (magMsd > 1.0)
            magMsd = 2.0f;

        return magMsd*magPow;
    }
}