/* plt/tic.c Paul Albrecht March 1988 Copyright (C) Paul Albrecht 1988. All rights reserved. Last Update: 13 June 1995 (GBM) EMACS_MODES: tabstop=4 Attempt to make a smart choice for the tic marks. To avoid worries about roundoff errors, all computation is done with long integers. */ #include "plt.h" #include "axis.h" static void TicSetup(); static void TicRound(); static void TicConfigCost(); static short DecimalPlaces(); static void MakeLongs(); static void GetString(); static void GetLong(); #define WORKDIG 7 /* don't go over 8 */ #define SIGDIG 5 /* max sig dig for tics */ #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 tic; long min; long max; long tmark; short ntic; short ticdp; short mindp; short maxdp; short minfit; short maxfit; } TLWInfo, *TLWPtr; /* Tic mark logic workspace */ static NumInfo lo, /* minimum axis value */ hi, /* maximum axis value */ diff, /* length of the axis */ tmark; /* where a labeled tic mark should fall */ static TLWInfo tcs[]= { {10}, {50}, {25}, {20}, {30,1}, {40}, {60,1}, {75} }; static double tenf; static int havetmark; void TicInit( mode ) Mode mode; { } LinearTicLogic( a ) AxisPtr a; { TLWPtr tc, tcbest; if( a->min == DEFAULT ) lo.dbl = (a->name == 'x') ? xmin : ymin; else lo.dbl = a->min; if( a->max == DEFAULT ) hi.dbl = (a->name == 'x') ? xmax : ymax; else hi.dbl = 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( ticlogic ) { eprintf( "\nGIVEN: %g %g (%g)\n", lo.dbl, hi.dbl, tmark.dbl ); eprintf( " MIN MAX TIC NTIC DEC PLACES COST TMARK\n" ); } tcbest = tcs; for( tc=tcs; tc < ENDP(tcs); tc++ ) { TicSetup( a, tc ); TicRound( a, tc ); tc->ticdp = DecimalPlaces( tc->tic ); tc->mindp = DecimalPlaces( tc->min ); tc->maxdp = DecimalPlaces( tc->max ); TicConfigCost( a, tc ); if( tcbest->cost > tc->cost ) tcbest = tc; if( ticlogic ) eprintf( "%8.3f%8.3f%8.3f%6d%5d%3d/%d%3d/%d%8.3f%8.3f\n", tc->min*tenf, tc->max*tenf, tc->tic*tenf, tc->ntic, tc->ticdp, tc->mindp, tc->minfit, tc->maxdp, tc->maxfit, tc->cost, tc->tmark*tenf ); if( a->tic != DEFAULT || a->mlt != DEFAULT ) break; } a->min = tcbest->min * tenf; a->max = tcbest->max * tenf; a->tic = tcbest->tic/a->skp * tenf; if( !havetmark ) a->tmark = tcbest->tmark * tenf; if( a->pfm == NULLS ) { if( tcbest->ticdp <= 0 || tcbest->ticdp > SIGDIG ) a->pfm = "%lg"; else { char fmt[30]; sprintf( fmt, "%%.%dlf", (int)tcbest->ticdp ); 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( ticlogic ) eprintf( "CHOOSE: %g %g %g using %s (%g)\n", a->min, a->max, a->tic, a->pfm, a->tmark ); } /*************************************************************************/ static void TicSetup( a, tc ) AxisPtr a; TLWPtr tc; { double dbl; if( a->tic == DEFAULT ) { if( a->mlt != DEFAULT ) { dbl = a->skp*a->mlt/tenf; while( dbl < PLT_MINLONG ) dbl *= 10; tc->tic = dbl + 0.5; } else tc->tic = tc->mult; while( tc->tic*20 < diff.l ) tc->tic *= 10; } else tc->tic = a->skp*a->tic/tenf + 0.5; tc->min = lo.l; tc->max = hi.l; tc->tmark = havetmark ? tmark.l : (lo.l+tc->tic)/tc->tic*tc->tic; tc->cost = tc->icost; } /* Extend the axes to a multiple of the tic spacing */ static void TicRound(a, tc) AxisPtr a; TLWPtr tc; { long test, stic; int n; tc->minfit = tc->maxfit = 0; for( n=0; n < a->skp; n++ ) { stic = tc->tic*(n+1)/a->skp; test = tc->tmark - (tc->tmark-lo.l+stic-1)/stic*stic; if( (test == 0 || tc->minfit == 0) && (double)(lo.l-test)/diff.l < a->acclo ) { tc->min = test; tc->minfit = (test/tc->tic*tc->tic == test); } test = tc->tmark + (hi.l-tc->tmark+stic-1)/stic*stic; if( (test == 0 || tc->maxfit == 0) && (double)(test-hi.l)/diff.l < a->acchi ) { tc->max = test; tc->maxfit = (test/tc->tic*tc->tic == test); } } if( tc->max > 0 ) tc->ntic = tc->max/tc->tic; else tc->ntic = (tc->max - (tc->tic-1))/tc->tic; if( tc->min > 0 ) tc->ntic -= (tc->min + (tc->tic-1))/tc->tic; else tc->ntic -= tc->min/tc->tic; tc->ntic += 1; } /* Compute undesirability of tic configuration */ static void TicConfigCost(a, tc) AxisPtr a; TLWPtr tc; { AxisPtr oa; double dbl; int n; n = (tc->ticdp > 0) ? tc->ticdp : 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->ntic < dbl) ? 2*(dbl-tc->ntic) : 0;; dbl = 6 + n/2.0; tc->cost += (tc->ntic > dbl) ? 2*(tc->ntic-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( lnum ) long lnum; { short dp; dp = -(WORKDIG + diff.exp); while( YES ) { lnum = 10*(lnum - lnum/WORKLONG*WORKLONG); if( lnum == 0 ) break; dp++; } return( dp ); } /************************************************************************** Subroutine 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 the is greater than or equal to WORKLONG/10. */ static void MakeLongs() { 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 ); } static void GetString( num ) 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( num, eref ) NumPtr num; { 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; }