replaced decimal module, xrchart_scale with autoformat, histogram x-axis ticks changed.
[pspp] / src / math / chart-geometry.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004, 2015 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include <math.h>
19 #include <float.h>
20 #include <assert.h>
21
22 #include "chart-geometry.h"
23 #include <stdlib.h>
24
25 #include "gl/xalloc.h"
26 #include "gl/minmax.h"
27 #include "gl/xvasprintf.h"
28
29 static const double standard_tick[] = {1, 2, 5, 10};
30
31 /* 
32    Find a set {LOWER, INTERVAL, N_TICKS} such that:
33
34    LOWER <= LOWDBL,
35    LOWER + INTERVAL > LOWDBL,
36    
37    LOWER + N_TICKS * INTERVAL < HIGHDBL
38    LOWER + (N_TICKS + 1) * INTERVAL >= HIGHDBL
39
40    INTERVAL = X * 10^N
41     where: N is integer 
42     and    X is an element of {1, 2, 5}
43
44    In other words:
45
46          INTERVAL
47          >      <
48      |....+....+....+.      .+....|
49    LOWER  1    2    3     N_TICKS
50         ^LOWDBL                 ^HIGHDBL
51 */
52 void
53 chart_get_scale (double high, double low,
54                  double *lower, double *interval,
55                  int *n_ticks)
56 {
57   int i;
58   double fitness = DBL_MAX;
59   double logrange;
60   *n_ticks = 0;
61
62   assert (high >= low);
63
64   if ((high - low) < 10 * DBL_MIN) {
65     *n_ticks = 0;
66     *lower = low;
67     *interval = 0.0;
68     return;
69   }
70
71   logrange = floor(log10(high-low));
72
73   /* Find the most pleasing interval */
74   for (i = 1; i < 4; ++i)
75     {
76       double cinterval = standard_tick[i] * pow(10.0,logrange-1);
77       double clower = floor(low/cinterval) * cinterval;
78       int cnticks = ceil((high - clower) / cinterval)-1;
79       double cfitness = fabs(7.5 - cnticks);
80
81       if (cfitness < fitness) {
82         fitness = cfitness;
83         *lower = clower;
84         *interval = cinterval;
85         *n_ticks = cnticks;
86       }
87     }
88 }
89
90 /*
91  * Compute the optimum format string and the scaling
92  * for the tick drawing on a chart axis
93  * Input:  lower:   the lowest tick
94  *         interval:the interval between the ticks
95  *         nticks:  the number of tick intervals (bins) on the axis
96  * Return: fs:      format string for printf to print the tick value
97  *         scale:   scaling factor for the tick value
98  * The format string has to be freed after usage.
99  * An example format string and scalefactor:
100  * Non Scientific: "%.3lf", scale=1.00
101  * Scientific:     "%.2lfe3", scale = 0.001
102  * Usage example:
103  *   fs = chart_get_ticks_format(-0.7,0.1,8,&scale);
104  *   printf(fs,value*scale);
105  *   free(fs);
106  */
107 char *
108 chart_get_ticks_format (const double lower, const double interval,
109                         const unsigned int nticks, double *scale)
110 {
111   double logmax = log10(fmax(fabs(lower + (nticks+1)*interval),fabs(lower)));
112   double logintv = log10(interval);
113   int logshift = 0;
114   char *format_string = NULL;
115   int nrdecs = 0;
116
117   if (logmax > 0.0 && logintv < 0.0)
118     {
119       nrdecs = MIN(6,(int)(ceil(fabs(logintv))));
120       logshift = 0;
121       if (logmax < 12.0)
122         format_string = xasprintf("%%.%dlf",nrdecs);
123       else
124         format_string = xasprintf("%%lg");
125     }
126   else if (logmax > 0.0) /*logintv is > 0*/
127     {
128       if (logintv < 5.0 && logmax < 10.0)
129         {
130           logshift = 0; /* No scientific format */
131           nrdecs = 0;
132           format_string = xstrdup("%.0lf");
133         }
134       else
135         {
136           logshift = (int)logmax;
137           /* Possible intervals are 0.2Ex, 0.5Ex, 1.0Ex                    */
138           /* log10(0.2E9) = 8.30, log10(0.5E9) = 8.69, log10(1.0E9) = 9    */
139           /* 0.2 and 0.5 need one decimal more. For stability subtract 0.1 */
140           nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
141           format_string = xasprintf("%%.%dlf&#8901;10<sup>%d</sup>",nrdecs,logshift);
142         }
143     }
144   else /* logmax and logintv are < 0 */
145     {
146       if (logmax > -3.0)
147         {
148           logshift = 0; /* No scientific format */
149           nrdecs = MIN(8,(int)(ceil(-logintv)));
150           format_string = xasprintf("%%.%dlf",nrdecs);
151         }
152       else
153         {
154           logshift = (int)logmax-1;
155           nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
156           format_string = xasprintf("%%.%dlf&#8901;10<sup>%d</sup>",nrdecs,logshift);
157         }
158     }
159   *scale = pow(10.0,-(double)logshift);
160   return format_string;
161 }