DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - download
Index: ┃ T d

⟦a76b77843⟧ TextFile

    Length: 27046 (0x69a6)
    Types: TextFile
    Names: »dumpgrph.c«

Derivation

└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
    └─ ⟦this⟧ »EUUGD11/euug-87hel/sec1/graph/dumpgrph.c« 

TextFile

/*
 * Copyright (C) 1986   Alan Kent
 *
 * Permission is granted to freely distribute part or
 * all of this code as long as it is not for profit
 * and this message is retained in the code.
 *
 * No resposibility is taken for any damage or incorect
 * results this program generates.
 * 
 */

/*
 * WARNING: This file is extremely sensative to changes.
 *	Making one minor change may break other features.
 *   	This file probably needs to be re-written.
 */

#include <stdio.h>
#include <math.h>
#include "graph.h"
#include "y.tab.h"


#define DEBUG		0
#define DEBUG_LOG	0


/* this info is for trying to place the text nicely on the screen */
/* however, unfortunately it is different for all the different plotters! */

#ifdef  LASER
#define CHAR_WIDTH	48
#define CHAR_HEIGHT	100
#else
#define CHAR_WIDTH	80
#define CHAR_HEIGHT	100
#endif


#define XSPACE		4096
#define YSPACE		4096
#define XORIGIN		(15*CHAR_WIDTH)
#define YORIGIN		650
#define XRANGE		(XSPACE-XORIGIN)
#define YRANGE		(YSPACE-YORIGIN-3*CHAR_HEIGHT)
#define TICK_SIZE	50
#define TEXT_GAP	400

#define TICK_TEXT_GAP	400		/* minimum gap between ticks for log */
#define TICK_GAP	40

#define RAD		(XSPACE/200)

#define LEGEND_LINE_LENGTH	400


extern double min_fun ();
extern double max_fun ();
extern double ceil ();
extern double pow ();
extern double log10 ();


extern graph_st graph[];
extern int num_graphs;
extern char *graph_label;
extern axis_st xaxis;
extern axis_st yaxis;
extern int horiz_legend;
extern int vert_legend;



static double lastx_clip;	/* buffered coordinate for clip function */
static double lasty_clip;


dump_graphs ()
{
    int i;

    if ( num_graphs < 1 ) {
	fprintf ( stderr , "No graphs to print\n" );
	exit ( 0 );
    }

    if ( xaxis.linear == LOGRITHMIC ) {
	for ( i = 0; i < num_graphs; i++ )
	    log_tab ( graph[i].table , graph[i].table->next );
    }

    if ( yaxis.linear == LOGRITHMIC ) {
	for ( i = 0; i < num_graphs; i++ )
	    log_tab ( graph[i].table->next , graph[i].table );
    }

    /* plot(3x) routines */
    openpl ();
    erase ();
    space ( 0 , 0 , XSPACE , YSPACE );

    if ( graph_label != NULL ) {
	move ( XORIGIN + XRANGE / 2 - strlen ( graph_label ) * CHAR_WIDTH / 2 ,
	    YSPACE - CHAR_HEIGHT );
	label ( graph_label );
    }

    determine_range ( XAXIS , &xaxis );
    determine_range ( YAXIS , &yaxis );

    axes ();

    dump_legend ();

    for ( i = 0; i < num_graphs; i++ )
	draw_graph ( &graph[i] );

    /* plot(3x) */
    move ( 0 , 0 );
    closepl ();
}



/* discards values <= 0.0 */

static
log_tab ( table , othercol )
table_st *table , *othercol;
{
    int i , j;
    int warn_count;

    warn_count = 0;
    j = 0;
    for ( i = 0; i < table->size; i++ ) {
	if ( table->data[i] > 0.0 ) {
	    othercol->data[j] = othercol->data[i];
	    table->data[j] = log10 ( table->data[i] );
	    j++;
	}
	else if ( warn_count++ == 0 )
	    warn ( "negative or zero data to be plotted logrithmically discarded" );
    }
    table->size = j;
    othercol->size = j;
}



static
draw_graph ( gptr )
graph_st *gptr;
{
    int i , size;
    int reseti;
    int basex , basey;
    double *xdata , *ydata;
    double lastx , lasty;
    double newx , newy;


    /* necessary for clip function to work safely */

    lasty_clip = -1;
    lastx_clip = -1;

    /* no data to plot */

    if ( gptr->table->size < 1 )
	return;

    xdata = gptr->table->data;
    ydata = gptr->table->next->data;
    size = gptr->table->size;

    /* draw the line (with clipping) */

    if ( gptr->line_type != NO ) {
	set_line ( gptr->line_type );
	lastx = xdata[0];
	lasty = ydata[0];
	/* start from 0 (not 1) so a single point will come out as a dot */
	for ( i = 0; i < size; i++ ) {
	    newx = xdata[i];
	    newy = ydata[i];
	    clip_line ( lastx , lasty , newx , newy );
	    lastx = newx;
	    lasty = newy;
	}
    }

    /* Draw any points along the line */

    if ( gptr->point_type != 0 ) {
	set_line ( SOLID );
	for ( i = 0; i < size; i++ ) {

	    basex = lxscale ( xdata[i] );
	    basey = lyscale ( ydata[i] );

	    if ( basex >= XORIGIN  &&  basex <= XORIGIN + XRANGE
	    &&   basey >= YORIGIN  &&  basey <= YORIGIN + YRANGE )

		draw_point ( gptr->point_type , basex , basey );
	}
    }

    /* output the graph label */

    set_line ( SOLID );
    if ( gptr->label != NULL ) {
	move ( lxscale ( lastx_clip ) , lyscale ( lasty_clip ) );
	/*move ( lxscale ( xdata[size-1] ) , lyscale ( ydata[size-1] ) );*/
	label ( gptr->label );
    }
}



draw_point ( point_type , basex , basey )
int point_type , basex , basey;
{
    if ( point_type & MSK_TRIANGLE ) {
	move ( basex - RAD , basey - RAD * 2 / 3 );
	cont ( basex + RAD , basey - RAD * 2 / 3 );
	cont ( basex , basey + RAD * 4 / 3 );
	cont ( basex - RAD , basey - RAD * 2 / 3 );
    }
    
    if ( point_type & MSK_CROSS ) {
	move ( basex - RAD , basey - RAD );
	cont ( basex + RAD , basey + RAD );
	move ( basex - RAD , basey + RAD );
	cont ( basex + RAD , basey - RAD );
    }

    if ( point_type & MSK_PLUS ) {
	move ( basex - RAD , basey - RAD );
	cont ( basex + RAD , basey + RAD );
	move ( basex - RAD , basey + RAD );
	cont ( basex + RAD , basey - RAD );
    }

    if ( point_type & MSK_CIRCLE ) {
	circle ( basex , basey , RAD );
    }
    
    if ( point_type & MSK_SQUARE ) {
	/* *4/5 is just to make the shapes appear the same size */
	move ( basex - RAD * 4 / 5 , basey - RAD * 4 / 5 );
	cont ( basex + RAD * 4 / 5 , basey - RAD * 4 / 5 );
	cont ( basex + RAD * 4 / 5 , basey + RAD * 4 / 5 );
	cont ( basex - RAD * 4 / 5 , basey + RAD * 4 / 5 );
	cont ( basex - RAD * 4 / 5 , basey - RAD * 4 / 5 );
    }
}


clip_line ( x1 , y1 , x2 , y2 )
double x1 , y1 , x2 , y2;
{
    double minx , miny , maxx , maxy;
    int mx1 , my1 , mx2 , my2;
    double xedge , yedge;


    minx = xaxis.range.min;
    miny = yaxis.range.min;
    maxx = xaxis.range.max;
    maxy = yaxis.range.max;

    /* determine which sector the end points are in */

    mx1 = sector ( x1 , minx , maxx );
    my1 = sector ( y1 , miny , maxy );
    mx2 = sector ( x2 , minx , maxx );
    my2 = sector ( y2 , miny , maxy );

    /* points must not be in same sector and off screen */

    if ( mx1 * mx2 != 1  &&  my1 * my2 != 1 ) {

	if ( mx1 != 0 ) {

	    /* move point 1 nearer the x-edge of the frame */

	    if ( mx1 == 1 )
		xedge = maxx;
	    else
		xedge = minx;
	    y1 = y1 + ( y2 - y1 ) * ( xedge - x1 ) / ( x2 - x1 );
	    x1 = xedge;

	    /* recompute sector to see if point needs another shift */

	    my1 = sector ( y1 , miny , maxy );
	}

	if ( my1 != 0 ) {

	    /* move point 1 nearer the y-edge of the frame */

	    if ( my1 == 1 )
		yedge = maxy;
	    else
		yedge = miny;
	    x1 = x1 + ( x2 - x1 ) * ( yedge - y1 ) / ( y2 - y1 );
	    y1 = yedge;

	}

	/* ok, now repeat the above for point 2 */

	if ( mx2 != 0 ) {

	    /* move point 2 nearer the x-edge of the frame */

	    if ( mx2 == 1 )
		xedge = maxx;
	    else
		xedge = minx;
	    y2 = y2 + ( y1 - y2 ) * ( xedge - x2 ) / ( x1 - x2 );
	    x2 = xedge;

	    /* recompute sector to see if point needs another shift */

	    my2 = sector ( y2 , miny , maxy );
	}

	if ( my2 != 0 ) {

	    /* move point 2 nearer the y-edge of the frame */

	    if ( my2 == 1 )
		yedge = maxy;
	    else
		yedge = miny;
	    x2 = x2 + ( x1 - x2 ) * ( yedge - y2 ) / ( y1 - y2 );
	    y2 = yedge;

	}

	if ( x1 >= minx  &&  x1 <= maxx  &&  x2 >= minx  &&  x2 <= maxx 
	&&   y1 >= miny  &&  y1 <= maxy  &&  y2 >= miny  &&  y2 <= maxy ) {

	    /* use buffered move and cont calls where possible as some */
	    /* plotters do dotted lines better if cont is used for continuous */
	    /* lines. Using separate line() calls means the dotted lines */
	    /* start again per line */

	    if ( x1 != lastx_clip  ||  y1 != lasty_clip )
		move ( xscale ( x1 ) , yscale ( y1 ) );
	    cont ( xscale ( x2 ) , yscale ( y2 ) );
	    lastx_clip = x2;
	    lasty_clip = y2;
	}
    }
}


static int
sector ( val , min , max )
double val , min , max;
{
    if ( val < min ) return ( -1 );
    if ( val > max ) return ( 1 );
    return ( 0 );
}


static
set_line ( type )
int type;
{
    switch ( type ) {
    case DOTTED : linemod ( "dotted" ); break;
    case DOTDASHED : linemod ( "dotdashed" ); break;
    case SHORTDASHED : linemod ( "shortdashed" ); break;
    case LONGDASHED : linemod ( "longdashed" ); break;
    default : linemod ( "solid" ); break;
    }
}


static int
xscale ( coord )
double coord;
{
    double xcoord;

    xcoord = ( ( coord - xaxis.range.min )
	/ ( xaxis.range.max - xaxis.range.min ) ) * (double)XRANGE;
    return ( (int)xcoord + XORIGIN );
}


static int
yscale ( coord )
double coord;
{
    double ycoord;

    ycoord = ( ( coord - yaxis.range.min )
	/ ( yaxis.range.max - yaxis.range.min ) ) * (double)YRANGE;
    return ( (int)ycoord + YORIGIN );
}


static int
lxscale ( coord )
double coord;
{
    int xcoord;

    xcoord = xscale ( coord );
    if ( xcoord < 0 )
	return ( 0 );
    if ( xcoord >= XSPACE )
	return ( XSPACE - 1 );
    return ( xcoord );
}


static int
lyscale ( coord )
double coord;
{
    int ycoord;

    ycoord = yscale ( coord );
    if ( ycoord < 0 )
	return ( 0 );
    if ( ycoord >= YSPACE )
	return ( YSPACE - 1 );
    return ( ycoord );
}


char *
choose_format ( paxis )
struct axis_st *paxis;
{
    double first_log_tick ();

    if ( paxis->linear == LOGRITHMIC ) {
	paxis->power_10 = floor ( paxis->range.min );
	paxis->format = "%.0f";
	/* STILL NOT RIGHT
	if ( xscale ( first_log_tick ( xaxis.range.min ) ) >= XRANGE + XORIGIN )
	    paxis->format = "%.2f";
	*/
    }
    else if ( paxis->auto_tick_size )
	calc_ticks ( paxis , &paxis->tick_size , &paxis->power_10 );

    if ( paxis->scale == AUTO
    /* &&  paxis->linear != LOGRITHMIC ???? special scaling ok for log too? */
    &&  paxis->auto_tick_size ) {
	if ( paxis->power_10 == 1.0
	||   paxis->power_10 == 2.0 ) {
	    paxis->format = "%.0f";
	    paxis->scale = NO;
	}
	else if ( paxis->tick_size >= 10.0 )
	    paxis->format = "%.0f";
    }
    return ( ( paxis->user_format == NULL )
	? paxis->format : paxis->user_format );
}


static
axes ()
{
    int xscale ();
    int yscale ();
    int lxscale ();
    int lyscale ();
    double first_log_tick ();
    double next_log_tick ();
    double next_10_tick ();

    int base_x , base_y;
    int tick_pos;
    int last_tick_pos;
    int last_text_pos;
    int tick_num;
    int loop_count;
    int i;
    double tick_value;
    double base;
    double xaxis_min , yaxis_min;
    char buf[500];
    char small_buf[2];
    char *p , *beg;
    int longest , length , num_lines , line_num;
    int print_tick;
    int best_log_ticks;
    char *xformat , *yformat;


    xformat = choose_format ( &xaxis );
    yformat = choose_format ( &yaxis );

    base_x = lxscale ( xaxis.range.min );
    base_y = lyscale ( yaxis.range.min );
    base = 10.0;
    set_line ( SOLID );

    if ( xaxis.frame != NO ) {

	/* First the x-axis */

	/* draw base line */

	line ( base_x , base_y , base_x + XRANGE - 1 , base_y );

	if ( xaxis.frame == OUTLINE )
	    line ( base_x , base_y + YRANGE - 1 , base_x + XRANGE - 1 , base_y + YRANGE - 1 );

	/* Label the axis */

	if ( xaxis.label != NULL ) {
	    sprintf ( buf ,
		( xaxis.scale != AUTO  ||  xaxis.power_10 == 0.0 )
		    ? "%s" : "%s x 10^%d" ,
		xaxis.label ,
		(int)xaxis.power_10 );
	    move ( base_x + XRANGE / 2 - strlen ( buf ) * CHAR_WIDTH / 2 ,
		base_y - TEXT_GAP );
	    label ( buf );
/* fprintf(stderr,"xaxis label '%s'\n",buf);*/
	}

	/* Put ticks on the axis */

/*fprintf(stderr,"power_10 = %f\n",xaxis.power_10);*/
	tick_num = 0;
	loop_count = 0;
	last_tick_pos = 0;
	last_text_pos = 0;
		
	if ( xaxis.linear == LOGRITHMIC ) {
	    xaxis_min = xaxis.range.min;

#if DEBUG
fprintf(stderr,"min = %f\n",xaxis.range.min);
#endif

	    best_log_ticks = xscale ( first_log_tick ( xaxis.range.min ) )
		< XRANGE + XORIGIN;
	    best_log_ticks = 1;	/* override for now */

#if DEBUG_LOG
fprintf(stderr,"best = %d, xscale(min) = %d\n",best_log_ticks,xscale(first_log_tick(xaxis.range.min)));
#endif

	}
	else {

	    /* now, round the value off! If we dont do that, min */
	    /* values that are strange fractions will cause misleading */
	    /* values to be printed (%.1f will round fractions to one */
	    /* decimal place which will be totally wrong for a tick size */
	    /* of 0.1 */

	    xaxis_min = ceil ( xaxis.range.min
		/ ( xaxis.tick_size  * pow ( base , xaxis.power_10 ) ) )
		* ( xaxis.tick_size * pow ( base , xaxis.power_10 ) );
#if DEBUG
fprintf(stderr,"x.min %f, x.tick_size %f, x.power_10 %f , xaxis_min %f\n",
xaxis.range.min,xaxis.tick_size,xaxis.power_10,xaxis_min);
#endif
	}

	while ( 1 ) {

	    /* determine value to display next to tick */

	    if ( xaxis.linear == LOGRITHMIC ) {
		if ( best_log_ticks ) {
		    if ( loop_count == 0 ) {
			tick_value = first_log_tick ( xaxis.range.min );
#if DEBUG_LOG
fprintf(stderr,"min = %f, max = %f, tick = %f\n",xaxis.range.min,xaxis.range.max,tick_value);
#endif
		    }
		    else
			tick_value = next_log_tick ();
		}
		else {
		    tick_value = pow ( 10.0 , xaxis.range.min )
			+ (double)tick_num
			* ( pow( 10.0 , xaxis.range.max )
			    - pow ( 10.0 , xaxis.range.min ) ) / 5.0;
#if DEBUG_LOG
fprintf(stderr,"tick value is %f\n",tick_value);
#endif
		}
	    }
	    else {
		tick_value = xaxis_min + (double)tick_num
		    * xaxis.tick_size * pow ( base , xaxis.power_10 );
	    }
	    loop_count++;
	    
	    /* determine offset along axis to place tick */

	    if ( xaxis.linear == LOGRITHMIC )
		tick_pos = xscale ( log10 ( tick_value ) );
	    else
		tick_pos = xscale ( tick_value );

	    /* see if meant to auto scale value */

	    if ( xaxis.scale == AUTO )
		tick_value /= pow ( base , xaxis.power_10 );
#if DEBUG_LOG
fprintf(stderr,"scaled tickvalue to %f\n",tick_value);
#endif

	    /* run out of axis? */

	    if ( tick_pos > XRANGE + XORIGIN )
		break;

	    /* if logrithmic axis, must also check that ticks wont be */
	    /* too close together */

	    if ( xaxis.linear == LOGRITHMIC
	    &&  tick_pos != xscale ( next_10_tick () - 1.0 ) ) {
		/* is tick too close to last tick? */
		if ( tick_pos < last_tick_pos + TICK_GAP ) {
#if DEBUG_LOG
fprintf(stderr,"tick %d (%d) too close to LAST position\n",tick_num,tick_pos);
#endif
		    continue;		/* try next tick */
		}
		/* is tick too close to next 1,10,100,1000 value? */
		if ( tick_pos + TICK_GAP > xscale ( next_10_tick () ) ) {
#if DEBUG_LOG
fprintf(stderr,"tick %d (%d) too close to NEXT position\n",tick_num,tick_pos);
fprintf(stderr,"next = %f, xscale(next) = %d\n",next_10_tick (),xscale(next_10_tick()));
#endif
		    continue;		/* try next tick */
		}
		last_tick_pos = tick_pos;
	    }

	    /* draw the actual tick */

	    if ( xaxis.frame == GRID )
		line ( tick_pos , base_y + YRANGE - 1 , tick_pos , base_y );
	    else
		line ( tick_pos , base_y , tick_pos , base_y - TICK_SIZE );

	    /* print the tick value */

	    print_tick = 0;
	    if ( xaxis.linear != LOGRITHMIC )
		print_tick = 1;
	    else if ( tick_pos >= last_text_pos + TICK_TEXT_GAP  ) {
		if ( xscale ( 2.0 ) - xscale ( 1.0 ) <= TICK_TEXT_GAP ) {
		    if ( tick_pos == xscale ( next_10_tick () - 1.0 ) )
			print_tick = 1;
		}
		else {
		    if ( tick_pos + TICK_TEXT_GAP <= xscale ( next_10_tick () ) )
			print_tick = 1;
		}
	    }
#if DEBUG|DEBUG_LOG
fprintf(stderr,"tick_pos = %d, 2.0-1.0 = %d, print it %d\n",
tick_pos,xscale(2.0)-xscale(1.0),print_tick);
#endif

	    if ( print_tick ) {
		sprintf ( buf , xformat , tick_value );
		move ( tick_pos - strlen ( buf ) * CHAR_WIDTH / 2 ,
		    base_y - ( xaxis.frame == GRID ? 2 : 3 ) * TICK_SIZE );
		label ( buf );
		last_text_pos = tick_pos;
#if DEBUG|DEBUG_LOG
fprintf(stderr,"TICK %d at %d: '%s'\n",tick_num,tick_pos,buf);
#endif

	    }

	    /* loop until tick position off axis */

	    tick_num++;
	    if ( tick_num > 100 )
		abort ( "axis tick algorithm failed!!" );
	}
    }

    /* Second, the y-axis */

    if ( yaxis.frame != NO ) {

	/* draw base line */

	line ( base_x , base_y , base_x , base_y + YRANGE - 1 );

	if ( yaxis.frame == OUTLINE )
	    line ( base_x , base_y + YRANGE - 1 , base_x + XRANGE - 1 , base_y + YRANGE - 1 );

	/* Label the axis by writing values DOWN the axis */

	if ( yaxis.label != NULL ) {
	    sprintf ( buf ,
		( yaxis.scale != AUTO  ||  yaxis.power_10 == 0.0 )
		    ? "%s" : "%s x~10^%d" ,
		yaxis.label ,
		(int)yaxis.power_10 );

#ifdef OLD_DOWN_AXIS
	    for ( i = 0; buf[i] != '\0'; i++ ) {
		move ( base_x - TEXT_GAP - 2 * CHAR_WIDTH , base_y + YRANGE / 2
		    + strlen ( buf ) * CHAR_HEIGHT / 2 - i * CHAR_HEIGHT );
		small_buf[0] = buf[i];
		small_buf[1] = '\0';
		label ( small_buf );
	    }
#else

	    /* split label into many lines, each line separated by a space. */
	    /* to allow a space to be force, ~ is mapped to a space at the */
	    /* last moment. to place the text, each line is centred and */
	    /* placed so that the longest line touches the edge of the */
	    /* graph. */

	    /* first find longest line and number of lines */

	    longest = 0;
	    num_lines = 0;
	    p = buf;
	    while ( *p != '\0' ) {
		num_lines++;
		length = 0;
		while ( *p != ' '  &&  *p != '\0' ) {
		    p++;
		    length++;
		}
		if ( *p == ' ' )
		    p++;
		if ( length > longest )
		    longest = length;
	    }

	    /* now print yaxis label by replacing blanks with \0 and */
	    /* ~ with blanks and output each line */

	    line_num = 0;
	    p = buf;
	    while ( *p != '\0' ) {
		beg = p;
		length = 0;
		while ( *p != '\0'  &&  *p != ' ' ) {
		    if ( *p == '~' )
			*p = ' ';
		    length++;
		    p++;
		}
		if ( *p == ' ' )
		    *p++ = '\0';
		move ( ( longest * CHAR_WIDTH/2 ) - ( length * CHAR_WIDTH/2 ) ,
		    base_y + YRANGE / 2 + num_lines * CHAR_HEIGHT
		    - line_num * CHAR_HEIGHT * 3 / 2 );
		label ( beg );
		line_num++;
	    }

#endif
	}

	/* Put ticks on the axis */

	tick_num = 0;
	last_tick_pos = 0;
	last_text_pos = 0;
	loop_count = 0;
	
	if ( yaxis.linear == LOGRITHMIC ) {
	    yaxis_min = yaxis.range.min;
	}
	else {

	    /* now, round the value off! If we dont do that, min */
	    /* values that are strange fractions will cause misleading */
	    /* values to be printed (%.1f will round fractions to one */
	    /* decimal place which will be totally wrong for a tick size */
	    /* of 0.1 */

	    yaxis_min = ceil ( yaxis.range.min
		/ ( yaxis.tick_size * pow ( base , yaxis.power_10 ) ) )
		* ( yaxis.tick_size * pow ( base , yaxis.power_10 ) );
	}

	while ( 1 ) {

	    /* determine value to display next to tick */

	    if ( yaxis.linear == LOGRITHMIC ) {
		if ( loop_count == 0 )
		    tick_value = first_log_tick ( yaxis.range.min );
		else
		    tick_value = next_log_tick ();
	    }
	    else {
		tick_value = yaxis_min + (double)tick_num
		    * yaxis.tick_size * pow ( base , yaxis.power_10 );
	    }
	    loop_count++;
	    
	    /* determine offset along axis to place tick */

	    if ( yaxis.linear == LOGRITHMIC )
		tick_pos = yscale ( log10 ( tick_value ) );
	    else
		tick_pos = yscale ( tick_value );

	    /* see if meant to auto scale value */

	    if ( yaxis.scale == AUTO )
		tick_value /= pow ( base , yaxis.power_10 );

	    /* run out of axis? */

	    if ( tick_pos > YRANGE + YORIGIN )
		break;

	    /* if logrithmic axis, must also check that ticks wont be */
	    /* too close together */

	    if ( yaxis.linear == LOGRITHMIC
	    &&  tick_pos != yscale ( next_10_tick () - 1.0 ) ) {
		/* is tick too close to last tick? */
		if ( tick_pos < last_tick_pos + TICK_GAP )
		    continue;		/* try next tick */
		/* is tick too close to next 1,10,100,1000 value? */
		if ( tick_pos + TICK_GAP > yscale ( next_10_tick () ) )
		    continue;		/* try next tick */
		last_tick_pos = tick_pos;
	    }

	    /* draw the actual tick */

	    if ( yaxis.frame == GRID )
		line ( base_x + XRANGE - 1 , tick_pos , base_x , tick_pos );
	    else
		line ( base_x , tick_pos , base_x - TICK_SIZE , tick_pos );

	    /* print the tick value */

	    print_tick = 0;
	    if ( yaxis.linear != LOGRITHMIC )
		print_tick = 1;
	    else if ( tick_pos >= last_text_pos + TICK_TEXT_GAP  ) {
		if ( yscale ( 2.0 ) - yscale ( 1.0 ) <= TICK_TEXT_GAP ) {
		    if ( tick_pos == yscale ( next_10_tick () - 1.0 ) )
			print_tick = 1;
		}
		else {
		    if ( tick_pos + TICK_TEXT_GAP <= yscale ( next_10_tick () ) )
			print_tick = 1;
		}
	    }

	    if ( print_tick ) {
		sprintf ( buf , yformat , tick_value );
		move ( base_x - strlen ( buf ) * CHAR_WIDTH
		    - ( yaxis.frame == GRID ? 1 : 2 ) * TICK_SIZE ,
		    tick_pos - CHAR_WIDTH / 2 );
		label ( buf );
		last_text_pos = tick_pos;
	    }

	    /* loop until tick position off axis */

	    tick_num++;
	    if ( tick_num > 100 )
		abort ( "axis tick algorithm failed!!" );
	}
    }
}


static
determine_range ( which , paxis )
int which;
axis_st *paxis;
{
    double floor () , log10 () , pow ();
    int i;
    double best_min , best_max;
    double new_min , new_max;
    double round_max;
    table_st *table;


    /* only automatically calculate range if no range has been specified */
    /* (This is indicated by the min value being greater than the max value) */

    if ( paxis->range.min >= paxis->range.max ) {

	/* scan through all the graphs and determine the largest and */
	/* smallest value. */

	if ( which == XAXIS )
	    table = graph[0].table;
	else
	    table = graph[0].table->next;
	best_min = min_fun ( table , 0 , table->size - 1 );
	best_max = max_fun ( table , 0 , table->size - 1 );
	for ( i = 1; i < num_graphs; i++ ) {
	    if ( which == XAXIS )
		table = graph[i].table;
	    else
		table = graph[i].table->next;
	    new_min = min_fun ( table , 0 , table->size - 1 );
	    new_max = max_fun ( table , 0 , table->size - 1 );
	    if ( new_min < best_min )
		best_min = new_min;
	    if ( new_max > best_max )
		best_max = new_max;
	}

	/* check for zero height graph */

	if ( best_min == best_max ) {
	    
	    /* give the graph some height */

	    if ( best_min == 0.0 ) {
		best_min = -1.0;
		best_max = 1.0;
	    }
	    else if ( best_min > 0.0 ) {
		if ( paxis->linear == LOGRITHMIC )
		    best_min = best_min / 2.0;
		else
		    best_min = 0;
		best_max = 2.0 * best_max;
	    }
	    else {
		best_min = best_min * 0.9;
		best_max = best_max * 1.1;
	    }
	}

	/* make min a nice round value (eg: 4-1000 => 0-1000) */

	if ( best_max > 0.0 ) {
	    round_max = pow ( (double)10.0 , floor ( log10 ( best_max / 3.0 ) ) );
	    if ( round_max > 0.0 )
		best_min = floor ( best_min / round_max ) * round_max * 0.999;
	}
	/*
	if ( paxis->linear != LOGRITHMIC ) {
	    if ( best_max > 0.0 ) {
		round_max = pow ( (double)10.0 , floor ( log10 ( best_max ) ) );
		best_min = floor ( best_min / round_max ) * round_max;
	    }
	}
	else {
	    if ( best_max > 1.0 ) {
		round_max = pow ( (double)10.0 , floor ( log10 ( best_max ) ) );
		best_min = floor ( best_min / round_max ) * round_max;
	    }
	}
	*/
#if 0
	/* to stop 0->infinity graphs from hanging the program */
	if ( paxis->linear == LOGRITHMIC ) {
	    if ( best_min < best_max / 5.0 )
		best_min = best_max / 5.0;
			/* remember, this is 10^5.0 */
	}
#endif
	paxis->range.min = best_min;
	paxis->range.max = best_max;
    }
    else if ( paxis->linear == LOGRITHMIC ) {

	/* convert values to actual values */

	if ( paxis->range.min <= 0.0 )
	    abort ( "Minimum value for axis on log graph is <= 0" );
	paxis->range.min = log10 ( paxis->range.min );
	paxis->range.max = log10 ( paxis->range.max );
    }
}



static
calc_ticks ( paxis , tick , power )
axis_st *paxis;
double *tick , *power;
{
    double log10 ();
    double base , delta;
    int power_of_10;
    double tick_size;

    base = 10.0;
    delta = paxis->range.max - paxis->range.min;
    power_of_10 = (int) floor ( log10 ( delta ) );
    tick_size = delta / pow ( base , (double)(power_of_10+1) );
    if ( tick_size <= 0.5 ) {
	tick_size = ceil ( (double)( tick_size * 10.0 ) ) / (double)10.0;
    }
    else {
	tick_size = 0.1;
	power_of_10++;
    }

    /* round tick size off to x10 ^3,6,9,12,.... which people understand */
    switch ( power_of_10 % 3 ) {
    case 0 :		/* already mult of 3 */
	break;
    case 1 :		/* its just too high (by one) */
	tick_size *= 10.0;
	power_of_10--;
	break;
    case 2 :		/* its just too low (by one) */
	/*
	tick_size /= 10.0;
	power_of_10++;
	*/
	tick_size *= 100.0;
	power_of_10 -= 2;
	break;
    }

    *tick = tick_size;
    *power = (double)power_of_10;
}



static int lt_value;
static int lt_power;


static double
first_log_tick ( value )
double value;
{
    double actual;
    double ret_val;

    actual = pow ( (double)10.0 , value );
    lt_power = (int) floor ( log10 ( actual ) );
    lt_value = (int) ceil ( actual / pow ( (double)10.0 , (double)lt_power ) );
    while ( lt_value >= 10 ) {
	lt_value /= 10;
	lt_power++;
    }
    ret_val = (double)lt_value * pow ( (double)10.0 , (double)lt_power );
/*fprintf(stderr,"first_log_tick: lt_power = %d, lt_value = %d, ret_val = %f\n",lt_power,lt_value,ret_val);*/
    return ( ret_val );
}


static double
next_log_tick ()
{
    lt_value++;
    while ( lt_value >= 10 ) {
	lt_value /= 10;
	lt_power++;
    }
    return ( (double)lt_value * pow ( (double)10.0 , (double)lt_power ) );
}


static double
next_10_tick ()
{
    return ( (double)( lt_power + 1 ) );
}


static
dump_legend ()
{
    int i;
    int num_lines;
    int longest;
    int basex , basey;


    /* first determine how much is going to be output, and what is the */
    /* longest string that is to be output */

    num_lines = 0;
    longest = 0;
    for ( i = 0; i < num_graphs; i++ ) {
	if ( graph[i].legend != NULL ) {
	    num_lines++;
	    if ( strlen ( graph[i].legend ) > longest )
		longest = strlen ( graph[i].legend );
	}
    }

    /* ok, now we want to work out where to start outputing the legend  */

    switch ( horiz_legend ) {

    case LEFT :
	basex = XORIGIN + CHAR_WIDTH * 3;
	break;

    case CENTER :
	basex = XORIGIN + XRANGE / 2 - ( longest * CHAR_WIDTH - LEGEND_LINE_LENGTH ) / 2;
	break;
    
    case RIGHT :
	basex = XORIGIN + XRANGE - longest * CHAR_WIDTH - 3 * CHAR_WIDTH - LEGEND_LINE_LENGTH - CHAR_WIDTH;
	break;
    }

    switch ( vert_legend ) {

    case TOP :
	basey = YORIGIN + YRANGE - CHAR_HEIGHT * 2;
	break;

    case MIDDLE :
	basey = YORIGIN + YRANGE / 2 + num_lines * CHAR_HEIGHT / 2;
	break;

    case BOTTOM :
	basey = YORIGIN + CHAR_HEIGHT * 1 + num_lines * CHAR_HEIGHT;
	break;
    }

    /* ok, now loop through and acutally output the legend! */

    for ( i = 0; i < num_graphs; i++ ) {
	if ( graph[i].legend != NULL ) {
	    if ( graph[i].line_type != NO ) {
		set_line ( graph[i].line_type );
		line ( basex , basey , basex + LEGEND_LINE_LENGTH , basey );
	    }
	    set_line ( SOLID );
	    draw_point ( graph[i].point_type , basex + LEGEND_LINE_LENGTH / 2 , basey );
	    move ( basex + LEGEND_LINE_LENGTH + CHAR_WIDTH , basey - CHAR_HEIGHT / 3 );
	    label ( graph[i].legend );
	    basey -= CHAR_HEIGHT;
	}
    }
}