plt - Software for 2D Plots 2.5
(9,319 bytes)
/* file: tick.c Paul Albrecht March 1988
Last revised: 15 April 2001
Tick selection for plt
Copyright (C) Paul Albrecht 1988
Recent changes (by George Moody, george@mit.edu):
15 April 2001: general cleanup
_______________________________________________________________________________
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 the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.
You may contact the maintainer by e-mail (george@mit.edu) or postal mail
(MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software,
please visit PhysioNet (http://www.physionet.org/).
_______________________________________________________________________________
Attempt to make a smart choice for the tick marks. To avoid worries
about roundoff errors, all computation is done with long integers.
*/
#include "plt.h"
#define WORKDIG 7 /* number of digits in intermediate results;
don't go over 8 */
#define SIGDIG 5 /* max number of significant digits in ticks */
#define WORKLONG (10000000L) /* 10**WORKDIG */
#define PLT_MINLONG (100L) /* 10**(WORKDIG-SIGDIG) */
typedef struct {
double dbl;
long l; /* the shifted WORKDIG digit representation dbl */
short exp; /* the exponent of dbl */
char str[20]; /* string representation of dbl */
} NumInfo, *NumPtr;
typedef struct {
short mult; /* must divide WORKLONG without remainder! */
float icost;
float cost;
long tick;
long min;
long max;
long tmark;
short ntick;
short tickdp;
short mindp;
short maxdp;
short minfit;
short maxfit;
} TLWInfo, *TLWPtr; /* Tick mark logic workspace */
/* Prototypes of functions defined in this module. */
void LinearTickLogic(AxisPtr a);
static void TickSetup(AxisPtr a, TLWPtr tc);
static void TickRound(AxisPtr a, TLWPtr tc);
static void TickConfigCost(AxisPtr a, TLWPtr tc);
static short DecimalPlaces(long lnum);
static void MakeLongs(void);
static void GetString(NumPtr num);
static void GetLong(NumPtr num, int eref);
static NumInfo lo; /* minimum axis value */
static NumInfo hi; /* maximum axis value */
static NumInfo diff; /* length of the axis */
static NumInfo tmark; /* where a labeled tick mark should fall */
static TLWInfo tcs[]= { {10}, {50}, {25}, {20}, {30,1}, {40}, {60,1}, {75} };
static double tenf;
static int havetmark;
void LinearTickLogic(AxisPtr a)
{
TLWPtr tc, tcbest;
lo.dbl = (a->min == DEFAULT) ? ((a->name == 'x') ? xmin : ymin) : a->min;
hi.dbl = (a->max == DEFAULT) ? ((a->name == 'x') ? xmax : ymax) : a->max;
if (lo.dbl == hi.dbl) {
err(NO, "All %c values are %lg", a->name, lo.dbl);
if (lo.dbl == 0)
hi.dbl = 1;
else {
lo.dbl = floor(lo.dbl);
hi.dbl = lo.dbl + 1;
}
a->acclo = a->acchi = 0;
}
if (a->skp == DEFAULT || a->skp < 1)
a->skp = 2;
diff.dbl = hi.dbl - lo.dbl;
tmark.dbl = a->tmark;
havetmark = !(tmark.dbl == DEFAULT ||
tmark.dbl < lo.dbl ||
tmark.dbl > hi.dbl);
MakeLongs();
if (ticklogic) {
err(NO, "\nGIVEN: %g %g (%g)\n", lo.dbl, hi.dbl, tmark.dbl);
err(NO,
" MIN MAX TICK NTICK DEC PLACES COST TMARK\n");
}
tcbest = tcs;
for (tc = tcs; tc < ENDP(tcs); tc++) {
TickSetup(a, tc);
TickRound(a, tc);
tc->tickdp = DecimalPlaces(tc->tick);
tc->mindp = DecimalPlaces(tc->min);
tc->maxdp = DecimalPlaces(tc->max);
TickConfigCost(a, tc);
if (tcbest->cost > tc->cost)
tcbest = tc;
if (ticklogic)
err(NO,"%8.3f%8.3f%8.3f%6d%5d%3d/%d%3d/%d%8.3f%8.3f\n",
tc->min*tenf, tc->max*tenf, tc->tick*tenf,
tc->ntick, tc->tickdp, tc->mindp, tc->minfit,
tc->maxdp, tc->maxfit, tc->cost, tc->tmark*tenf);
if (a->tick != DEFAULT || a->mlt != DEFAULT)
break;
}
a->min = tcbest->min * tenf;
a->max = tcbest->max * tenf;
a->tick = tcbest->tick/a->skp * tenf;
if (!havetmark)
a->tmark = tcbest->tmark * tenf;
if (a->pfm == NULL) {
if (tcbest->tickdp <= 0 || tcbest->tickdp > SIGDIG)
a->pfm = "%lg";
else {
char fmt[30];
sprintf(fmt, "%%.%dlf", (int)tcbest->tickdp);
a->pfm = StringSave(fmt);
}
}
if (a->tmark == DEFAULT) {
a->tmark = (a->name == 'x') ? ya.cr : xa.cr;
if (a->tmark < a->min)
a->tmark = a->min;
if (a->tmark > a->max)
a->tmark = a->max;
}
if (ticklogic)
err(NO, "CHOOSE: %g %g %g using %s (%g)\n",
a->min, a->max, a->tick, a->pfm, a->tmark);
}
static void TickSetup(AxisPtr a, TLWPtr tc)
{
double dbl;
if (a->tick == DEFAULT) {
if (a->mlt != DEFAULT) {
dbl = a->skp*a->mlt/tenf;
while (dbl < PLT_MINLONG)
dbl *= 10;
tc->tick = dbl + 0.5;
}
else
tc->tick = tc->mult;
while (tc->tick*20 < diff.l)
tc->tick *= 10;
}
else
tc->tick = a->skp*a->tick/tenf + 0.5;
tc->min = lo.l;
tc->max = hi.l;
tc->tmark = havetmark ? tmark.l : (lo.l+tc->tick)/tc->tick*tc->tick;
tc->cost = tc->icost;
}
/* Extend the axes to a multiple of the tick spacing */
static void TickRound(AxisPtr a, TLWPtr tc)
{
long test, stick;
int n;
tc->minfit = tc->maxfit = 0;
for (n = 0; n < a->skp; n++) {
stick = tc->tick*(n+1)/a->skp;
test = tc->tmark - (tc->tmark-lo.l+stick-1)/stick*stick;
if ((test == 0 || tc->minfit == 0) &&
(double)(lo.l-test)/diff.l < a->acclo) {
tc->min = test;
tc->minfit = (test/tc->tick*tc->tick == test);
}
test = tc->tmark + (hi.l-tc->tmark+stick-1)/stick*stick;
if ((test == 0 || tc->maxfit == 0) &&
(double)(test-hi.l)/diff.l < a->acchi) {
tc->max = test;
tc->maxfit = (test/tc->tick*tc->tick == test);
}
}
if (tc->max > 0)
tc->ntick = tc->max/tc->tick;
else
tc->ntick = (tc->max - (tc->tick-1))/tc->tick;
if (tc->min > 0)
tc->ntick -= (tc->min + (tc->tick-1))/tc->tick;
else
tc->ntick -= tc->min/tc->tick;
tc->ntick += 1;
}
/* Compute undesirability of tick configuration */
static void TickConfigCost(AxisPtr a, TLWPtr tc)
{
AxisPtr oa;
double dbl;
int n;
n = (tc->tickdp > 0) ? tc->tickdp : 0;
tc->cost += n;
tc->cost += tc->minfit ? ((tc->mindp > 0) ? tc->mindp : 0) : n;
tc->cost += tc->maxfit ? ((tc->maxdp > 0) ? tc->maxdp : 0) : n;
n = tc->maxfit + tc->minfit;
dbl = 3 + n/2.0;
tc->cost += (tc->ntick < dbl) ? 2*(dbl-tc->ntick) : 0;;
dbl = 6 + n/2.0;
tc->cost += (tc->ntick > dbl) ? 2*(tc->ntick-dbl) : 0;;
oa = (a->name == 'x' ? &ya : &xa);
if (oa->cr == DEFAULT || oa->cr == a->min)
tc->cost -= tc->minfit/2.0;
else if (oa->cr == a->max)
tc->cost -= tc->maxfit/2.0;
}
/* How many decimal places are needed to print lnum */
static short DecimalPlaces(long lnum)
{
short dp = -(WORKDIG + diff.exp);
while (lnum = 10*(lnum - lnum/WORKLONG*WORKLONG))
dp++;
return (dp);
}
static void MakeLongs(void)
{
int eref;
GetString(&lo);
GetString(&hi);
GetString(&diff);
eref = (lo.exp > hi.exp) ? lo.exp : hi.exp;
if (diff.exp > eref)
eref = diff.exp;
GetLong(&lo, eref);
GetLong(&hi, eref);
GetLong(&diff, eref);
if (diff.l < PLT_MINLONG)
diff.l = PLT_MINLONG;
if (havetmark) {
GetString(&tmark);
GetLong(&tmark, eref);
}
tenf = pow(10.0, (double)eref);
}
/* GetString(num) takes fabs(num->dbl) and puts the first WORKDIG significant
digits into the string num->str. The base 10 exponent is stored in
num->exp.
The variable eref is the largest base 10 exponent value of the following
three numbers: the axis min, axis max, and axis max-min.
Subroutine GetLong(num,eref) produces a long integer num->l, which is equal
to num->dbl*WORKLONG/10**eref. The exponent num->exp becomes eref. The
actual value of num->l is always less than WORKLONG. This provides the
first WORKDIG working digits for the three relevant numbers (min, max,
max-min). The numbers are scaled so that num->l (?) is greater than or
equal to WORKLONG/10. */
static void GetString(NumPtr num)
{
int n; /* must be int */
short ndig;
char *str;
str = num->str;
sprintf(str, "%.8le", num->dbl);
while (*str && (*str == '0' || *str == '-' || *str == ' '))
str++;
ndig = 0;
while (*str && *str != 'e' && *str != 'E') {
if (*str == '.')
num->exp = ndig;
else if (ndig < WORKDIG)
num->str[ndig++] = *str;
str++;
}
while (ndig < WORKDIG)
num->str[ndig++] = '0';
num->str[ndig] = 0;
sscanf(str+1, "%d", &n);
num->exp += n - WORKDIG;
}
static void GetLong(NumPtr num, int eref)
{
long atol();
short ndigs;
ndigs = WORKDIG - (eref - num->exp);
if (ndigs > 0) {
num->str[ndigs] = 0;
num->l = atol(num->str);
}
else
num->l = 0;
num->exp = eref;
if (num->dbl < 0)
num->l = -num->l;
}