[comp.graphics] I need a wheel...

vmrad@deneb.ucdavis.edu (0048;0000008890;500;737;56;) (12/03/88)

I am currently creating a graph drawing package for our Sophy nuclear
medicine computer.  Among the items I wish this package to do is label
the axis with pleasing numbers, and place tick marks at pleasing
intervals.  I found that writing a function which produces such
pleasing numbers given the min and max as input is decidedly
non-trivial.  Rather than re-invent a wheel, I thought I would try and
tap the collective expertise of comp.graphics.

What I am looking for in a nutshell:

axisMin axisMax   =somefunction=>   graphMin graphMax majorTick minorTick

Can anyone point me toward some code that generates such axis values?
A reference that discusses such code would be very nice, too.  We have
almost 300 books on graphics here at UCD, and I picked the ten most
likely to have such a discussion and found nothing.  I do not savor the
idea of perusing the remainder of the books for such a function.

vmrad@deneb.ucdavis.edu (0048;0000008890;500;737;56;) (12/03/88)

OK.  I figured out when to hit CR and when not to.  

Here is my name in the header and my .signature for my previous
message.  Sign, these things are so complex.


Bernard Littau

VM Radiological Sciences          Telephone: (916) 752-0184
School of Veterinary Medicine     Internet:  vmrad@ucdavis.edu
University of California          BITNET:    vmrad@ucdavis
Davis, CA 95616                   UUCP: {ucbvax,lll-crg,sdcsvax}!ucdavis!vmrad

ph@miro.Berkeley.EDU (Paul Heckbert) (12/03/88)

In article <3343@ucdavis.ucdavis.edu> vmrad@deneb.ucdavis.edu
asked for code to label axes with pleasing numbers and place tick marks
at pleasing intervals.

Here is some code I've used.
The heart of it is a simple little routine called "nicenum" that picks
"nice-looking" numbers.

The below is geared toward y axis labeling but of course it could
work equally well for the x axis.

Paul Heckbert, CS grad student
508-7 Evans Hall, UC Berkeley		UUCP: ucbvax!miro.berkeley.edu!ph
Berkeley, CA 94720			ARPA: ph@miro.berkeley.edu

/*
 * label: test program to demonstrate nice graph axis labeling
 *
 * Paul Heckbert, 2 Dec 88
 */

#include <stdio.h>
#include <math.h>
double expt(), tick(), nicenum();

#define NTICK 5			/* desired number of tick marks */

main(ac, av)
int ac;
char **av;
{
    double ymin, ymax;

    if (ac!=3) {
	fprintf(stderr, "Usage: label <ymin> <ymax>\n");
	exit(1);
    }
    ymin = atof(av[1]);
    ymax = atof(av[2]);

    ylabel(ymin, ymax);
}

ylabel(ymin, ymax)
double ymin, ymax;
{
    char str[6], temp[20];
    int exp;
    double graphymin, graphymax, range, d, y;

    /* we expect ymin!=ymax */
    range = nicenum(ymax-ymin, 0);
    d = nicenum(range/(NTICK-1), 1);		/* tick mark spacing */
    graphymin = floor(ymin/d)*d;
    graphymax = ceil(ymax/d)*d;
    exp = floor(log10(d));
    sprintf(str, "%%.%df", exp<0 ? -exp : 0);	/* simplest axis labels */

    printf("graphymin=%g graphymax=%g increment=%g\n", graphymin, graphymax, d);
    for (y=graphymin; y<graphymax+.5*d; y+=d) {
	sprintf(temp, str, y);
	printf("(%s)\n", temp);
    }
}

/*
 * nicenum: find a "nice" number approximately equal to x
 * round if round=1, ceil if round=0
 */

static double nicenum(x, round)
double x;
int round;
{
    int exp;
    double f, y;

    exp = floor(log10(x));
    f = x/expt(10., exp);	/* fraction between 1 and 10 */
    if (round)
	if (f<1.5) y = 1.;
	else if (f<3.) y = 2.;
	else if (f<7.) y = 5.;
	else y = 10.;
    else
	if (f<=1.) y = 1.;
	else if (f<=2.) y = 2.;
	else if (f<=5.) y = 5.;
	else y = 10.;
    return y*expt(10., exp);
}

/*
 * expt(a,n)=a^n for integer n
 * roundoff errors in pow were causing problems, so I wrote my own
 */

double expt(a, n)
double a;
register int n;
{
    double x;

    x = 1.;
    if (n>0) for (; n>0; n--) x *= a;
    else for (; n<0; n++) x /= a;
    return x;
}

kent@decwrl.dec.com (Christopher A. Kent) (12/05/88)

The ACM "Collected Algorithms" has routines (in FORTRAN) that do a very
nice job of this. See Algorithm 463. (I believe the 464 does the same
thing for a log scale.)
-- 
Chris Kent	Western Research Laboratory	Digital Equipment Corporation
kent@decwrl.dec.com	decwrl!kent			(415) 853-6639

wetter@cit-vax.Caltech.Edu (Pierce T. Wetter) (12/06/88)

in article <3343@ucdavis.ucdavis.edu>, vmrad@deneb.ucdavis.edu (0048;0000008890;500;737;56;) says:
> 
> I am currently creating a graph drawing package for our Sophy nuclear
> medicine computer.  Among the items I wish this package to do is label
> the axis with pleasing numbers, and place tick marks at pleasing
> intervals.  I found that writing a function which produces such
> pleasing numbers given the min and max as input is decidedly
> non-trivial.  Rather than re-invent a wheel, I thought I would try and
> tap the collective expertise of comp.graphics.
>
    Here are the two functions I use in a package I wrote about 3 yrs ago.
  Getdiv returns the spacing between ticks given their minimum and maximum
  values find start finds a good starting point to start placing ticks
  at (i.e. if your range is from -27 to 0 use -25.

    You can also use the value of getdiv to round your numbers. Remember
   .1 + .1 + .1 + .1 +.1 +.1+ .1 +.1 +.1 +.1 is not equal to 1. This isn't
   a problem generally, but it is at zero because you have to make sure you
   label that point 0 not 2e-26.


double getdiv(xmn, xmx)
double xmn, xmx;
	{
	double diff, lgdiff, div, temp;
	diff = xmx - xmn;
	diff = sqrt(diff*diff);	
	temp = log10(diff);
	lgdiff = (double) ((int) (temp + 0.5));
	lgdiff -= 1.0;
	div = 10.0 / wglobals.minorcount * pow(10.0, lgdiff);
	if ((diff/div) < (2.1*wglobals.minorcount))
		div /= 10.0;
	return (div);
	}

double findstart(xmn, xmx)
double xmn;
double xmx;
	{
  /* find starting point given min max  majorspacing*/

	double x, dv;
	int xi, between();
	dv = wglobals.minorcount * getdiv(xmn, xmx);
        /*minorcount is the number of minor ticks per major tick*/
	xi = (int) xmn / (dv);
	x = (xi + 1) * dv;
	if (between(x, xmn, xmx))  /* xmn<= x <=xmx */
		return x;
	while (!between(x, xmn, xmx) && (x < xmx))
		{
		x += dv;
		}
	if (between(x, xmn, xmx))
		return x;
	else 
		return xmn;
	}


Pierce 
-- 
____________________________________________________________________________
You can flame or laud me at:
wetter@tybalt.caltech.edu or wetter@csvax.caltech.edu or pwetter@caltech.bitnet
  (There would be a witty saying here, but my signature has to be < 4lines)