X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fmath%2Fchart-geometry.c;h=f74c0aaf9ca31a5ef98fef5682c629d436a62c90;hb=dc93fae2746573f1e71d93fbc297b48dc0c32b90;hp=573459892e109781e4eaa079ed0d34d5549cbfc7;hpb=8afe280ae36feb9ba7af31094f163795b84f2df8;p=pspp diff --git a/src/math/chart-geometry.c b/src/math/chart-geometry.c index 573459892e..f74c0aaf9c 100644 --- a/src/math/chart-geometry.c +++ b/src/math/chart-geometry.c @@ -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 @@ -17,41 +17,145 @@ #include #include #include +#include #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 + +#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×10%d",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×10%d",nrdecs,logshift); + } + } + *scale = pow(10.0,-(double)logshift); + return format_string; } -