charts: switched from bullet operator to multiplication sign for numbers
[pspp] / src / math / chart-geometry.c
index 573459892e109781e4eaa079ed0d34d5549cbfc7..f74c0aaf9ca31a5ef98fef5682c629d436a62c90 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2004 Free Software Foundation, Inc.
+   Copyright (C) 2004, 2015 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 #include <config.h>
 #include <math.h>
 #include <float.h>
+#include <assert.h>
 
 #include "chart-geometry.h"
-
-/* Adjust tick to be a sensible value
-   ie:  ... 0.1,0.2,0.5,   1,2,5,  10,20,50 ... */
-double
-chart_rounded_tick(double tick)
+#include <stdlib.h>
+
+#include "gl/xalloc.h"
+#include "gl/minmax.h"
+#include "gl/xvasprintf.h"
+
+static const double standard_tick[] = {1, 2, 5, 10};
+
+/* 
+   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 high, double low,
+                double *lower, double *interval,
+                int *n_ticks)
 {
   int i;
+  double fitness = DBL_MAX;
+  double logrange;
+  *n_ticks = 0;
 
-  double diff = DBL_MAX;
-  double t = tick;
+  assert (high >= low);
 
-  static const double standard_ticks[] = {1, 2, 5, 10};
+  if ((high - low) < 10 * DBL_MIN) {
+    *n_ticks = 0;
+    *lower = low;
+    *interval = 0.0;
+    return;
+  }
 
-  double factor;
+  logrange = floor(log10(high-low));
 
-  /* Avoid arithmetic problems with very small values */
-  if (abs (tick) < DBL_EPSILON)
-     return 0;
+  /* Find the most pleasing interval */
+  for (i = 1; i < 4; ++i)
+    {
+      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;
+      }
+    }
+}
 
-  factor = pow(10,ceil(log10(standard_ticks[0] / tick))) ;
+/*
+ * 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;
 
-  for (i = 3  ; i >= 0 ; --i)
+  if (logmax > 0.0 && logintv < 0.0)
     {
-      const double d = fabs( tick - standard_ticks[i] / factor ) ;
-
-      if ( d < diff )
+      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
        {
-         diff = d;
-         t = standard_ticks[i] / factor ;
+         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);
        }
     }
-
-  return t;
-
+  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
+       {
+         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;
 }
-