+/*
+ Find a set {LOWER, INTERVAL, N_TICKS} such that:
+
+ LOWER <= LOWDBL,
+ LOWER + INTERVAL > LOWDBL,
+
+ LOWER + N_TICKS * INTERVAL < HIGHDBL
+ LOWER + (N_TICKS + 1) * INTERVAL >= HIGHDBL
+
+ INTERVAL = X * 10^N
+ where: N is integer
+ and X is an element of {1, 2, 5}
+
+ In other words:
+
+ INTERVAL
+ > <
+ |....+....+....+. .+....|
+ LOWER 1 2 3 N_TICKS
+ ^LOWDBL ^HIGHDBL
+*/
+void
+chart_get_scale (double highdbl, double lowdbl,
+ struct decimal *lower,
+ struct decimal *interval,
+ int *n_ticks)
+{
+ int i;
+ double fitness = DBL_MAX;
+ *n_ticks = 0;
+ struct decimal high;
+ struct decimal low;
+
+ assert (highdbl >= lowdbl);
+
+ decimal_from_double (&high, highdbl);
+ decimal_from_double (&low, lowdbl);
+
+ struct decimal diff = high;
+ decimal_subtract (&diff, &low);
+
+ double expd = dec_log10 (&diff) - 2;
+
+ /* Find the most pleasing interval */
+ for (i = 1; i < 4; ++i)
+ {
+ struct decimal clbound = low;
+ struct decimal cubound = high;
+ struct decimal candidate;
+ decimal_init (&candidate, standard_tick[i], expd);
+
+ decimal_divide (&clbound, &candidate);
+ int fl = decimal_floor (&clbound);
+ decimal_int_multiply (&candidate, fl);
+ clbound = candidate;
+
+
+ decimal_init (&candidate, standard_tick[i], expd);
+ decimal_divide (&cubound, &candidate);
+ int fu = decimal_ceil (&cubound);
+ decimal_int_multiply (&candidate, fu);
+
+ cubound = candidate;
+
+ decimal_init (&candidate, standard_tick[i], expd);
+ decimal_subtract (&cubound, &clbound);
+ decimal_divide (&cubound, &candidate);
+
+
+ ord_t n_int = decimal_floor (&cubound);
+
+ /* We prefer to have between 5 and 10 tick marks on a scale */
+ double f = 1 - exp (-0.2 * fabs (n_int - 7.5) / 7.5);
+
+ if (f < fitness)
+ {
+ fitness = f;
+ *lower = clbound;
+ *interval = candidate;
+ *n_ticks = n_int;
+ }
+ }
+}