charts: switched from bullet operator to multiplication sign for numbers
[pspp] / src / math / chart-geometry.c
index 35380a6929b6c4529e0fe2d5579e03cad9b25f23..f74c0aaf9ca31a5ef98fef5682c629d436a62c90 100644 (file)
 #include <assert.h>
 
 #include "chart-geometry.h"
-#include "decimal.h"
 #include <stdlib.h>
 
-static const double standard_tick[] = {1, 2, 5, 10};
-
-/* Adjust tick to be a sensible value
-   ie:  ... 0.1,0.2,0.5,   1,2,5,  10,20,50 ... */
-void
-chart_rounded_tick (double tick, struct decimal *result)
-{
-  int i;
-
-  struct decimal ddif = {1, 1000};
-
-  /* Avoid arithmetic problems with very small values */
-  if (fabs (tick) < DBL_EPSILON)
-    {
-      result->ordinate = 0;
-      result->mantissa = 0;
-      return;
-    }
-
-  struct decimal dt;
-  decimal_from_double (&dt, tick);
-  
-  double expd = dec_log10 (&dt) - 1;
+#include "gl/xalloc.h"
+#include "gl/minmax.h"
+#include "gl/xvasprintf.h"
 
-  for (i = 0  ; i < 4 ; ++i)
-    {
-      struct decimal candidate;
-      struct decimal delta;
-
-      decimal_init (&candidate, standard_tick[i], expd);
-      
-      delta = dt;
-      decimal_subtract (&delta, &candidate);
-      delta.ordinate = llabs (delta.ordinate);
-
-      if (decimal_cmp (&delta, &ddif) < 0)
-       {
-         ddif = delta;
-         *result = candidate;
-       }
-    }
-}
+static const double standard_tick[] = {1, 2, 5, 10};
 
 /* 
    Find a set {LOWER, INTERVAL, N_TICKS} such that:
@@ -88,64 +50,112 @@ chart_rounded_tick (double tick, struct decimal *result)
         ^LOWDBL                 ^HIGHDBL
 */
 void
-chart_get_scale (double highdbl, double lowdbl,
-                struct decimal *lower, 
-                struct decimal *interval,
+chart_get_scale (double high, double low,
+                double *lower, double *interval,
                 int *n_ticks)
 {
   int i;
   double fitness = DBL_MAX;
+  double logrange;
   *n_ticks = 0;
-  struct decimal high;
-  struct decimal low;
 
-  assert (highdbl >= lowdbl);
+  assert (high >= low);
 
-  decimal_from_double (&high, highdbl);
-  decimal_from_double (&low, lowdbl);
-  
-  struct decimal diff = high;
-  decimal_subtract (&diff, &low);
+  if ((high - low) < 10 * DBL_MIN) {
+    *n_ticks = 0;
+    *lower = low;
+    *interval = 0.0;
+    return;
+  }
 
-  double expd = dec_log10 (&diff) - 2;
+  logrange = floor(log10(high-low));
 
   /* 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);
+      double cinterval = standard_tick[i] * pow(10.0,logrange-1);
+      double clower = floor(low/cinterval) * cinterval;
+      int cnticks = ceil((high - clower) / cinterval)-1;
+      double cfitness = fabs(7.5 - cnticks);
+
+      if (cfitness < fitness) {
+       fitness = cfitness;
+       *lower = clower;
+       *interval = cinterval;
+       *n_ticks = cnticks;
+      }
+    }
+}
 
-      /* 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);
+/*
+ * Compute the optimum format string and the scaling
+ * for the tick drawing on a chart axis
+ * Input:  lower:   the lowest tick
+ *         interval:the interval between the ticks
+ *         nticks:  the number of tick intervals (bins) on the axis
+ * Return: fs:      format string for printf to print the tick value
+ *         scale:   scaling factor for the tick value
+ * The format string has to be freed after usage.
+ * An example format string and scalefactor:
+ * Non Scientific: "%.3lf", scale=1.00
+ * Scientific:     "%.2lfe3", scale = 0.001
+ * Usage example:
+ *   fs = chart_get_ticks_format(-0.7,0.1,8,&scale);
+ *   printf(fs,value*scale);
+ *   free(fs);
+ */
+char *
+chart_get_ticks_format (const double lower, const double interval,
+                       const unsigned int nticks, double *scale)
+{
+  double logmax = log10(fmax(fabs(lower + (nticks+1)*interval),fabs(lower)));
+  double logintv = log10(interval);
+  int logshift = 0;
+  char *format_string = NULL;
+  int nrdecs = 0;
 
-      if (f < fitness)
+  if (logmax > 0.0 && logintv < 0.0)
+    {
+      nrdecs = MIN(6,(int)(ceil(fabs(logintv))));
+      logshift = 0;
+      if (logmax < 12.0)
+       format_string = xasprintf("%%.%dlf",nrdecs);
+      else
+       format_string = xasprintf("%%lg");
+    }
+  else if (logmax > 0.0) /*logintv is > 0*/
+    {
+      if (logintv < 5.0 && logmax < 10.0)
+       {
+         logshift = 0; /* No scientific format */
+         nrdecs = 0;
+         format_string = xstrdup("%.0lf");
+       }
+      else
+       {
+         logshift = (int)logmax;
+         /* Possible intervals are 0.2Ex, 0.5Ex, 1.0Ex                    */
+         /* log10(0.2E9) = 8.30, log10(0.5E9) = 8.69, log10(1.0E9) = 9    */
+         /* 0.2 and 0.5 need one decimal more. For stability subtract 0.1 */
+         nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
+         format_string = xasprintf("%%.%dlf&#215;10<sup>%d</sup>",nrdecs,logshift);
+       }
+    }
+  else /* logmax and logintv are < 0 */
+    {
+      if (logmax > -3.0)
+       {
+         logshift = 0; /* No scientific format */
+         nrdecs = MIN(8,(int)(ceil(-logintv)));
+         format_string = xasprintf("%%.%dlf",nrdecs);
+       }
+      else
        {
-         fitness = f;
-         *lower = clbound;
-         *interval = candidate;
-         *n_ticks = n_int;
+         logshift = (int)logmax-1;
+         nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
+         format_string = xasprintf("%%.%dlf&#215;10<sup>%d</sup>",nrdecs,logshift);
        }
     }
+  *scale = pow(10.0,-(double)logshift);
+  return format_string;
 }