Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intelligently calculating chart tick positions

Tags:

python

math

graph

Whatever I'm using matplotlib, Open-Flash-Charts or other charts frameworks I always end needing to find a way to set x/y scales limits and intervals since builtins are not enough smart (or not at all...)

just try this in pylab (ipyhton -pylab) to understand what I mean:

In [1]: a, b, x = np.zeros(10), np.ones(10), np.arange(10)

In [2]: plot(x, a); plot(x, b)

you'll see just and empty frame grid hiding the 2 horizontal lines under it's its top and bottom borders.

I wonder if there is some algorithm around (that I can port to python) to set smartly top and bottom y limits and steps and also calculate every how many values show the x thick.

For example, let's say I have 475 measures as (datetime, temperature) as (x, y) with

2011-01-15 10:45:00 < datetime < 2011-01-17 02:20:00

(one every 5 minutes) and

26.5 < temperature < 28.3

My suggestion for this particular case could be to set:

26.4 <= y_scale <= 28.4 with a thick every .2

and a tick on x_scale every 12 items (once per hour).

But what about if I have just 20 measures over 20 days with -21.5 < temperature < 38.7, and so on? Is there a standardized method around?

like image 251
neurino Avatar asked Feb 09 '11 16:02

neurino


1 Answers

The following is what I've used for years which is simple and works well enough. Forgive me for it being C but translating to Python shouldn't be difficult.

The following function is needed and is from Graphic Gems volume 1.

double NiceNumber (const double Value, const int Round) {
  int    Exponent;
  double Fraction;
  double NiceFraction;

  Exponent = (int) floor(log10(Value));
  Fraction = Value/pow(10, (double)Exponent);

  if (Round) {
    if (Fraction < 1.5) 
      NiceFraction = 1.0;
    else if (Fraction < 3.0)
      NiceFraction = 2.0;
    else if (Fraction < 7.0)
      NiceFraction = 5.0;
    else
      NiceFraction = 10.0;
   }
  else {
    if (Fraction <= 1.0)
      NiceFraction = 1.0;
    else if (Fraction <= 2.0)
      NiceFraction = 2.0;
    else if (Fraction <= 5.0)
      NiceFraction = 5.0;
    else
      NiceFraction = 10.0;
   }

  return NiceFraction*pow(10, (double)Exponent);
 }

Use it like in the following example to choose a "nice" start/end of the axis based on the number of major ticks you wish displayed. If you don't care about ticks you can just set it to a constant value (ex: 10).

      //Input parameters
  double AxisStart = 26.5;
  double AxisEnd   = 28.3;
  double NumTicks  = 10;

  double AxisWidth;
  double NewAxisStart;
  double NewAxisEnd;
  double NiceRange;
  double NiceTick;

    /* Check for special cases */
  AxisWidth = AxisEnd - AxisStart;
  if (AxisWidth == 0.0) return (0.0);

    /* Compute the new nice range and ticks */
  NiceRange = NiceNumber(AxisEnd - AxisStart, 0);
  NiceTick = NiceNumber(NiceRange/(NumTicks - 1), 1);

    /* Compute the new nice start and end values */
  NewAxisStart = floor(AxisStart/NiceTick)*NiceTick;
  NewAxisEnd = ceil(AxisEnd/NiceTick)*NiceTick;

  AxisStart = NewAxisStart; //26.4
  AxisEnd = NewAxisEnd;     //28.4
like image 188
uesp Avatar answered Sep 22 '22 10:09

uesp