simplify function specifications in CTABLES
[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 #include "gettext.h"
30 #define _(msgid) gettext (msgid)
31
32 static const double standard_tick[] = {1, 2, 5, 10};
33
34 /*
35    Find a set {LOWER, INTERVAL, N_TICKS} such that:
36
37    LOWER <= LOWDBL,
38    LOWER + INTERVAL > LOWDBL,
39
40    LOWER + N_TICKS * INTERVAL < HIGHDBL
41    LOWER + (N_TICKS + 1) * INTERVAL >= HIGHDBL
42
43    INTERVAL = X * 10^N
44     where: N is integer
45     and    X is an element of {1, 2, 5}
46
47    In other words:
48
49          INTERVAL
50          >      <
51      |....+....+....+.      .+....|
52    LOWER  1    2    3     N_TICKS
53         ^LOWDBL                 ^HIGHDBL
54 */
55 void
56 chart_get_scale (double high, double low,
57                  double *lower, double *interval,
58                  int *n_ticks)
59 {
60   int i;
61   double fitness = DBL_MAX;
62   double logrange;
63   *n_ticks = 0;
64
65   assert (high >= low);
66
67   if ((high - low) < 10 * DBL_MIN) {
68     *n_ticks = 0;
69     *lower = low;
70     *interval = 0.0;
71     return;
72   }
73
74   logrange = floor(log10(high-low));
75
76   /* Find the most pleasing interval */
77   for (i = 1; i < 4; ++i)
78     {
79       double cinterval = standard_tick[i] * pow(10.0,logrange-1);
80       double clower = floor(low/cinterval) * cinterval;
81       int cnticks = ceil((high - clower) / cinterval)-1;
82       double cfitness = fabs(7.5 - cnticks);
83
84       if (cfitness < fitness) {
85         fitness = cfitness;
86         *lower = clower;
87         *interval = cinterval;
88         *n_ticks = cnticks;
89       }
90     }
91 }
92
93 /*
94    Generate a format string which can be passed to printf like functions,
95    which will produce a string in scientific notation representing a real
96    number.  N_DECIMALS is the number of decimal places EXPONENT is the
97    value of the exponent.
98 */
99 static inline char *
100 gen_pango_markup_scientific_format_string (int n_decimals, int exponent)
101 {
102   /* TRANSLATORS: This is a format string which, when presented to
103      printf like functions, will create a pango markup string to
104      display real number in scientific  notation.
105
106      In its untranslated form, it will display similar to "1.23 x 10^4". You
107      can leave it untranslated if this is how scientific notation is usually
108      presented in your language.
109
110      Some locales (such as German) prefer the centered dot rather than the
111      multiplication sign between the mantissa an exponent. In which
112      case, you can change "#215;" to "#8901;" or other unicode code
113      point as appropriate.
114
115      The . in this string does not and should not be changed, since
116      that is taken care of by the stdc library.
117
118      For information on Pango markup, see
119      http://developer.gnome.org/pango/stable/PangoMarkupFormat.html
120
121      For tables of unicode code points, see http://unicode.org/charts
122    */
123   return xasprintf(_("%%.%dlf&#215;10<sup>%d</sup>"), n_decimals, exponent);
124 }
125
126 /*
127  * Compute the optimum format string and the scaling
128  * for the tick drawing on a chart axis
129  * Input:  lower:   the lowest tick
130  *         interval:the interval between the ticks
131  *         nticks:  the number of tick intervals (bins) on the axis
132  * Return: fs:      format string for printf to print the tick value
133  *         scale:   scaling factor for the tick value
134  * The format string has to be freed after usage.
135  * An example format string and scalefactor:
136  * Non Scientific: "%.3lf", scale=1.00
137  * Scientific:     "%.2lfe3", scale = 0.001
138  * Usage example:
139  *   fs = chart_get_ticks_format(-0.7,0.1,8,&scale);
140  *   printf(fs,value*scale);
141  *   free(fs);
142  */
143 char *
144 chart_get_ticks_format (const double lower, const double interval,
145                         const unsigned int nticks, double *scale)
146 {
147   double logmax = log10(fmax(fabs(lower + (nticks+1)*interval),fabs(lower)));
148   double logintv = log10(interval);
149   int logshift = 0;
150   char *format_string = NULL;
151   int nrdecs = 0;
152
153   if (logmax > 0.0 && logintv < 0.0)
154     {
155       nrdecs = MIN(6,(int)(ceil(fabs(logintv))));
156       logshift = 0;
157       if (logmax < 12.0)
158         format_string = xasprintf("%%.%dlf",nrdecs);
159       else
160         format_string = xasprintf("%%lg");
161     }
162   else if (logmax > 0.0) /*logintv is > 0*/
163     {
164       if (logintv < 5.0 && logmax < 10.0)
165         {
166           logshift = 0; /* No scientific format */
167           nrdecs = 0;
168           format_string = xstrdup("%.0lf");
169         }
170       else
171         {
172           logshift = (int)logmax;
173           /* Possible intervals are 0.2Ex, 0.5Ex, 1.0Ex                    */
174           /* log10(0.2E9) = 8.30, log10(0.5E9) = 8.69, log10(1.0E9) = 9    */
175           /* 0.2 and 0.5 need one decimal more. For stability subtract 0.1 */
176           nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
177           format_string = gen_pango_markup_scientific_format_string (nrdecs, logshift);
178         }
179     }
180   else /* logmax and logintv are < 0 */
181     {
182       if (logmax > -3.0)
183         {
184           logshift = 0; /* No scientific format */
185           nrdecs = MIN(8,(int)(ceil(-logintv)));
186           format_string = xasprintf("%%.%dlf",nrdecs);
187         }
188       else
189         {
190           logshift = (int)logmax-1;
191           nrdecs = MIN(8,(int)(ceil(logshift-logintv-0.1)));
192           format_string = gen_pango_markup_scientific_format_string (nrdecs, logshift);
193         }
194       }
195   *scale = pow(10.0,-(double)logshift);
196   return format_string;
197 }