plt - Software for 2D Plots 2.5

File: <base>/plt/classic/tic.c (7,928 bytes)
/*	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;
}