/*
 * xgraph - A Simple Plotter for X
 *
 * David Harrison
 * University of California,  Berkeley
 * 1986, 1987, 1988, 1989
 *
 * Please see copyright.h concerning the formal reproduction rights
 * of this software.
 */

#include "copyright.h"
#include <stdio.h>
#include <math.h>
#include <pwd.h>
#include <ctype.h>
#include "xgout.h"
#include "xgraph.h"
#include "xtb.h"
#include "hard_devices.h"
#include <X11/Xutil.h>

#define ZOOM
#define TOOLBOX

#ifndef MAXFLOAT
#define MAXFLOAT	HUGE
#endif

#define BIGINT		0xfffffff

#define GRIDPOWER 	10
#define INITSIZE 	128

#define CONTROL_D	'\004'
#define CONTROL_C	'\003'
#define TILDE		'~'

#define BTNPAD		1
#define BTNINTER	3

#define MAX(a,b)	((a) > (b) ? (a) : (b))
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define ABS(x)		((x) < 0 ? -(x) : (x))
#define ZERO_THRES	1.0E-07

/* To get around an inaccurate log */
#define nlog10(x)	(x == 0.0 ? 0.0 : log10(x) + 1e-15)

#define ISCOLOR		(wi->dev_info.dev_flags & D_COLOR)

#define PIXVALUE(set) 	((set) % MAXATTR)

#define LINESTYLE(set) \
(ISCOLOR ?  ((set)/MAXATTR) : ((set) % MAXATTR))

#define MARKSTYLE(set) \
(colorMark ? COLMARK(set) : BWMARK(set))

#define COLMARK(set) \
((set) / MAXATTR)

#define BWMARK(set) \
((set) % MAXATTR)

extern void init_X();
#ifdef TOOLBOX
extern void do_error();
#endif
static char *tildeExpand();

AttrSet AllAttrs[MAXATTR];

typedef struct data_set {
    char *setName;		/* Name of set     */
    int numPoints;		/* How many points */
    int allocSize;		/* Allocated size  */
    double *xvec;		/* X values        */
    double *yvec;		/* Y values        */
} DataSet;

static DataSet AllSets[MAXSETS];

static XSegment *Xsegs;		/* Point space for X */

/* Basic transformation stuff */

static double llx, lly, urx, ury; /* Bounding box of all data */

typedef struct local_win {
    double loX, loY, hiX, hiY;	/* Local bounding box of window         */
    int XOrgX, XOrgY;		/* Origin of bounding box on screen     */
    int XOppX, XOppY;		/* Other point defining bounding box    */
    double UsrOrgX, UsrOrgY;	/* Origin of bounding box in user space */
    double UsrOppX, UsrOppY;	/* Other point of bounding box          */
    double XUnitsPerPixel;	/* X Axis scale factor                  */
    double YUnitsPerPixel;	/* Y Axis scale factor                  */
    xgOut dev_info;		/* Device information                   */
    Window close, hardcopy;	/* Buttons for closing and hardcopy     */
} LocalWin;

#define SCREENX(ws, userX) \
	(((int) (((userX) - ws->UsrOrgX)/ws->XUnitsPerPixel + 0.5)) + ws->XOrgX)
#define SCREENY(ws, userY) \
	(ws->XOppY - ((int) (((userY) - ws->UsrOrgY)/ws->YUnitsPerPixel + 0.5)))

static XContext win_context = (XContext) 0;

/* Other globally set defaults */

Display *disp;			/* Open display            */
Visual *vis;			/* Standard visual         */
Colormap cmap;			/* Standard colormap       */
int screen;			/* Screen number           */
int depth;			/* Depth of screen         */
int install_flag;		/* Install colormaps       */
Pixel black_pixel;		/* Actual black pixel      */
Pixel white_pixel;		/* Actual white pixel      */
Pixel bgPixel;			/* Background color        */
int bdrSize;			/* Width of border         */
Pixel bdrPixel;			/* Border color            */
Pixel zeroPixel;		/* Zero grid color         */
int zeroWidth;			/* Width of zero line      */
char zeroLS[MAXLS];		/* Line style spec         */
int zeroLSLen;			/* Length of zero LS spec  */
Pixel normPixel;		/* Norm grid color         */
int axisWidth;			/* Width of axis line      */
char axisLS[MAXLS];		/* Axis line style spec    */
int axisLSLen;			/* Length of axis line style */
Pixel echoPix;			/* Echo pixel value        */
XFontStruct *axisFont;		/* Font for axis labels    */
XFontStruct *titleFont;		/* Font for title labels   */
char titleText[MAXBUFSIZE]; 	/* Plot title              */
char XUnits[MAXBUFSIZE];	/* X Unit string           */
char YUnits[MAXBUFSIZE];	/* Y Unit string	   */
int bwFlag;			/* Black and white flag    */
int tickFlag;			/* Don't draw full grid    */
int bbFlag;			/* Whether to draw bb      */
int noLines;			/* Don't draw lines        */
int markFlag;			/* Draw marks at points    */
int pixelMarks;			/* Draw pixel markers      */
int bigPixel;			/* Draw big pixels         */
int colorMark;			/* Normal markers track color */
int logXFlag;			/* Logarithmic X axis      */
int logYFlag;			/* Logarithmic Y axis      */
int barFlag;			/* Draw bar graph          */
double barBase, barWidth;	/* Base and width of bars  */
int lineWidth;			/* Width of data lines     */
char *geoSpec = "";		/* Geometry specification  */
int numFiles = 0;		/* Number of input files   */
char *inFileNames[MAXSETS]; 	/* File names              */
char *Odevice = "";		/* Output device   	   */
char *Odisp = "To Device"; 	/* Output disposition      */
char *OfileDev = "";		/* Output file or device   */
int debugFlag = 0;		/* Whether debugging is on */

/* Possible user specified bounding box */
static double UsrLX, UsrLY, UsrRX, UsrRY;

/* Total number of active windows */
static int Num_Windows = 0;
static char *Prog_Name;


/*
 * Marker bitmaps
 */

#include "dot.11"

#include "mark1.11"
#include "mark2.11"
#include "mark3.11"
#include "mark4.11"
#include "mark5.11"
#include "mark6.11"
#include "mark7.11"
#include "mark8.11"

/* Sizes exported for marker drawing */
unsigned int dot_w = dot_width;
unsigned int dot_h = dot_height;
unsigned int mark_w = mark1_width;
unsigned int mark_h = mark1_height;
int mark_cx = mark1_x_hot;
/* Contrary to what lint says,  these are used in xgX.c */
int mark_cy = mark1_y_hot;

Pixmap dotMap = (Pixmap) 0;

static int XErrHandler();	/* Handles error messages */



main(argc, argv)
int argc;
char *argv[];
/*
 * This sets up the hard-wired defaults and reads the X defaults.
 * The command line format is: xgraph [host:display].
 */
{
    Window primary, NewWindow();
    XEvent theEvent;
    LocalWin *win_info;
    Cursor zoomCursor;
    FILE *strm;
    XColor fg_color, bg_color;
    char keys[MAXKEYS];
    int nbytes, idx, maxitems;

    /* Open up new display */
    Prog_Name = argv[0];
    disp = ux11_open_display(argc, argv);
    XSetErrorHandler(XErrHandler);

    /* Set up hard-wired defaults and allocate spaces */
    InitSets();

    /* Read X defaults and override hard-coded defaults */
    ReadDefaults();

    /* Parse the argument list */
    ParseArgs(argc, argv);

    /* Read the data into the data sets */
    llx = lly = MAXFLOAT;
    urx = ury = -MAXFLOAT;
    for (idx = 0;  idx < numFiles;  idx++) {
	strm = fopen(inFileNames[idx], "r");
	if (!strm) {
	    (void) fprintf(stderr, "Warning:  cannot open file `%s'\n",
			   inFileNames[idx]);
	} else {
	    if ((maxitems = ReadData(strm, inFileNames[idx])) < 0) {
		(void) fprintf(stderr, "data formatting error\n");
		exit(1);
	    }
	    (void) fclose(strm);
	}
    }
    if (!numFiles) {
	if ((maxitems = ReadData(stdin, (char *) 0)) < 0) {
	    (void) fprintf(stderr, "data formatting error\n");
	    exit(1);
	}
    }
    Xsegs = (XSegment *) malloc((unsigned) (maxitems * sizeof(XSegment)));

    /* Nasty hack here for bar graphs */
    if (barFlag) {
	llx -= barWidth;
	urx += barWidth;
    }

    /* Create initial window */
#ifdef TOOLBOX
    xtb_init(disp, screen, normPixel, bgPixel, axisFont);
#endif
    primary = NewWindow(Prog_Name, UsrLX, UsrLY, UsrRX, UsrRY, 1.0);
    if (!primary) {
	(void) fprintf(stderr, "Main window would not open\n");
	exit(1);
    }

    zoomCursor = XCreateFontCursor(disp, XC_sizing);
    fg_color.pixel = normPixel;
    XQueryColor(disp, cmap, &fg_color);
    bg_color.pixel = bgPixel;
    XQueryColor(disp, cmap, &bg_color);
    XRecolorCursor(disp, zoomCursor, &fg_color, &bg_color);

    Num_Windows = 1;
    while (Num_Windows > 0) {
	XNextEvent(disp, &theEvent);
#ifdef TOOLBOX
	if (xtb_dispatch(&theEvent) != XTB_NOTDEF) continue;
#endif
	if (XFindContext(theEvent.xany.display,
			 theEvent.xany.window,
			 win_context, (caddr_t *) &win_info)) {
	    /* Nothing found */
	    continue;
	}
	switch (theEvent.type) {
	case Expose:
#ifdef PEXPOSE
	    printf("expose: ");
	    if (theEvent.xexpose.send_event) {
		printf(" sendevent");
	    }
	    printf(" win=0x%x x=%d y=%d width=%d height=%d count=%d\n",
		   theEvent.xexpose.window, theEvent.xexpose.x,
		   theEvent.xexpose.y, theEvent.xexpose.width,
		   theEvent.xexpose.height, theEvent.xexpose.count);
#endif
	    if (theEvent.xexpose.count <= 0) {
		XWindowAttributes win_attr;

		XGetWindowAttributes(disp, theEvent.xany.window, &win_attr);
		win_info->dev_info.area_w = win_attr.width;
		win_info->dev_info.area_h = win_attr.height;
		init_X(win_info->dev_info.user_state);
		DrawWindow(win_info);
	    }
	    break;
	case KeyPress:
	    nbytes = XLookupString(&theEvent.xkey, keys, MAXKEYS,
				   (KeySym *) 0, (XComposeStatus *) 0);
	    for (idx = 0;  idx < nbytes;  idx++) {
		if (keys[idx] == CONTROL_D) {
		    /* Delete this window */
		    DelWindow(theEvent.xkey.window, win_info);
		} else if (keys[idx] == CONTROL_C) {
		    /* Exit program */
		    Num_Windows = 0;
		} else if (keys[idx] == 'h') {
#ifdef TOOLBOX		    
		    PrintWindow(theEvent.xany.window, win_info);
#endif
		}
	    }
	    break;
	case ButtonPress:
	    /* Handle creating a new window */
#ifdef ZOOM
	    Num_Windows += HandleZoom(Prog_Name,
				      &theEvent.xbutton,
				      win_info, zoomCursor);
#endif
	    break;
	default:
	    (void) fprintf(stderr, "Unknown event type: %x\n", theEvent.type);
	    break;
	}
    }
    exit(0);
}

#ifdef TOOLBOX

/*
 * Button handling functions
 */

/*ARGSUSED*/
xtb_hret del_func(win, bval, info)
Window win;			/* Button window    */
int bval;			/* Button value     */
char *info;			/* User information */
/*
 * This routine is called when the `Close' button is pressed in
 * an xgraph window.  It causes the window to go away.
 */
{
    Window the_win = (Window) info;
    LocalWin *win_info;
    
    xtb_bt_set(win, 1, (char *) 0);
    if (!XFindContext(disp, the_win, win_context, (caddr_t *) &win_info)) {
	DelWindow(the_win, win_info);
    }
    return XTB_HANDLED;
}

/*ARGSUSED*/
xtb_hret hcpy_func(win, bval, info)
Window win;			/* Button Window    */
int bval;			/* Button value     */
char *info;			/* User Information */
/*
 * This routine is called when the hardcopy button is pressed
 * in an xgraph window.  It causes the output dialog to be
 * posted.
 */
{
    Window the_win = (Window) info;
    LocalWin *win_info;
    
    xtb_bt_set(win, 1, (char *) 0);
    if (!XFindContext(disp, the_win, win_context, (caddr_t *) &win_info)) {
	PrintWindow(the_win, win_info);
    }
    xtb_bt_set(win, 0, (char *) 0);
    return XTB_HANDLED;
}
#endif



#define NORMSIZE	600
#define MINDIM		100

Window NewWindow(progname, lowX, lowY, upX, upY, asp)
char *progname;			/* Name of program    */
double lowX, lowY;		/* Lower left corner  */
double upX, upY;		/* Upper right corner */
double asp;			/* Aspect ratio       */
/*
 * Creates and maps a new window.  This includes allocating its
 * local structure and associating it with the XId for the window.
 * The aspect ratio is specified as the ratio of width over height.
 */
{
    Window new_window;
    LocalWin *new_info;
    static Cursor theCursor = (Cursor) 0;
    XSizeHints sizehints;
    XSetWindowAttributes wattr;
    XWMHints wmhints;
    XColor fg_color, bg_color;
    int geo_mask;
    int width, height;
    unsigned long wamask;
    char defSpec[120];
    double pad;
    
    new_info = (LocalWin *) malloc(sizeof(LocalWin));

    if (upX > lowX) {
	new_info->loX = lowX;
	new_info->hiX = upX;
    } else {
	new_info->loX = llx;
	new_info->hiX = urx;
    }
    if (upY > lowY) {
	new_info->loY = lowY;
	new_info->hiY = upY;
    } else {
	new_info->loY = lly;
	new_info->hiY = ury;
    }

    /* Increase the padding for aesthetics */
    if (new_info->hiX - new_info->loX == 0.0) {
	pad = MAX(0.5, fabs(new_info->hiX/2.0));
	new_info->hiX += pad;
	new_info->loX -= pad;
    }
    if (new_info->hiY - new_info->loY == 0) {
	pad = MAX(0.5, fabs(ury/2.0));
	new_info->hiY += pad;
	new_info->loY -= pad;
    }

    /* Add 10% padding to bounding box (div by 20 yeilds 5%) */
    pad = (new_info->hiX - new_info->loX) / 20.0;
    new_info->loX -= pad;  new_info->hiX += pad;
    pad = (new_info->hiY - new_info->loY) / 20.0;
    new_info->loY -= pad;  new_info->hiY += pad;

    /* Aspect ratio computation */
    if (asp < 1.0) {
	height = NORMSIZE;
	width = ((int) (((double) NORMSIZE) * asp));
    } else {
	width = NORMSIZE;
	height = ((int) (((double) NORMSIZE) / asp));
    }
    height = MAX(MINDIM, height);
    width = MAX(MINDIM, width);
    (void) sprintf(defSpec, "%dx%d+100+100", width, height);

    wamask = ux11_fill_wattr(&wattr, CWBackPixel, bgPixel,
			     CWBorderPixel, bdrPixel,
			     CWColormap, cmap, UX11_END);

    sizehints.flags = PPosition|PSize;
    sizehints.x = sizehints.y = 100;
    sizehints.width = width;
    sizehints.height = height;

    new_window = XCreateWindow(disp, RootWindow(disp, screen),
			       sizehints.x, sizehints.y,
			       (unsigned int) sizehints.width,
			       (unsigned int) sizehints.height,
			       (unsigned int) bdrSize,
			       depth, InputOutput, vis,
			       wamask, &wattr);


    if (new_window) {
	xtb_frame cl_frame, hd_frame;

	XStoreName(disp, new_window, progname);
	XSetIconName(disp, new_window, progname);

	wmhints.flags = InputHint | StateHint;
	wmhints.input = True;
	wmhints.initial_state = NormalState;
	XSetWMHints(disp, new_window, &wmhints);

	geo_mask = XParseGeometry(geoSpec, &sizehints.x, &sizehints.y,
				  (unsigned int *) &sizehints.width,
				  (unsigned int *) &sizehints.height);
	if (geo_mask & (XValue | YValue)) {
	    sizehints.flags = (sizehints.flags & ~PPosition) | USPosition;
	}
	if (geo_mask & (WidthValue | HeightValue)) {
	    sizehints.flags = (sizehints.flags & ~PSize) | USSize;
	}
	XSetNormalHints(disp, new_window, &sizehints);

	/* Set device info */
	set_X(new_window, &(new_info->dev_info));

#ifdef TOOLBOX
	/* Make buttons */
	xtb_bt_new(new_window, "Close", del_func,
		   (xtb_data) new_window, &cl_frame);
	new_info->close = cl_frame.win;
	XMoveWindow(disp, new_info->close, (int) BTNPAD, (int) BTNPAD);
	xtb_bt_new(new_window, "Hardcopy", hcpy_func,
		   (xtb_data) new_window, &hd_frame);
	new_info->hardcopy = hd_frame.win;
	XMoveWindow(disp, new_info->hardcopy,
		    (int) (BTNPAD + cl_frame.width + BTNINTER),
		    BTNPAD);
#endif

	XSelectInput(disp, new_window,
		     ExposureMask|KeyPressMask|ButtonPressMask);
	if (!theCursor) {
	    theCursor = XCreateFontCursor(disp, XC_top_left_arrow);
	    fg_color.pixel = normPixel;
	    XQueryColor(disp, cmap, &fg_color);
	    bg_color.pixel = bgPixel;
	    XQueryColor(disp, cmap, &bg_color);
	    XRecolorCursor(disp, theCursor, &fg_color, &bg_color);
	}
	XDefineCursor(disp, new_window, theCursor);
	if (!win_context) {
	    win_context = XUniqueContext();
	}
	XSaveContext(disp, new_window, win_context, (caddr_t) new_info);
	XMapWindow(disp, new_window);
	return new_window;
    } else {
	return (Window) 0;
    }
}


DelWindow(win, win_info)
Window win;			/* Window     */
LocalWin *win_info;		/* Local Info */
/*
 * This routine actually deletes the specified window and
 * decrements the window count.
 */
{
    xtb_data info;

    XDeleteContext(disp, win, win_context);
#ifdef TOOLBOX
    xtb_bt_del(win_info->close, &info);
    xtb_bt_del(win_info->hardcopy, &info);
#endif
    free((char *) win_info);
    XDestroyWindow(disp, win);
    Num_Windows -= 1;
}

#ifdef TOOLBOX
PrintWindow(win, win_info)
Window win;			/* Window       */
LocalWin *win_info;		/* Local Info   */
/*
 * This routine posts a dialog asking about the hardcopy
 * options desired.  If the user hits `OK',  the hard
 * copy is performed.
 */
{
    ho_dialog(win, Prog_Name, (char *) win_info);
}
#endif

#ifdef ZOOM

static XRectangle boxEcho;
static GC echoGC = (GC) 0;

#define DRAWBOX \
if (startX < curX) { \
   boxEcho.x = startX; \
   boxEcho.width = curX - startX; \
} else { \
   boxEcho.x = curX; \
   boxEcho.width = startX - curX; \
} \
if (startY < curY) { \
   boxEcho.y = startY; \
   boxEcho.height = curY - startY; \
} else { \
   boxEcho.y = curY; \
   boxEcho.height = startY - curY; \
} \
XDrawRectangles(disp, win, echoGC, &boxEcho, 1);

#endif

#define TRANX(xval) \
(((double) ((xval) - wi->XOrgX)) * wi->XUnitsPerPixel + wi->UsrOrgX)

#define TRANY(yval) \
(wi->UsrOppY - (((double) ((yval) - wi->XOrgY)) * wi->YUnitsPerPixel))

#ifdef ZOOM
int HandleZoom(progname, evt, wi, cur)
char *progname;
XButtonPressedEvent *evt;
LocalWin *wi;
Cursor cur;
{
    Window win, new_win;
    Window root_rtn, child_rtn;
    XEvent theEvent;
    int startX, startY, curX, curY, newX, newY, stopFlag, numwin;
    int root_x, root_y;
    unsigned int mask_rtn;
    double loX, loY, hiX, hiY, asp;

    win = evt->window;
    if (XGrabPointer(disp, win, True,
		     (unsigned int) (ButtonPressMask|ButtonReleaseMask|
				     PointerMotionMask|PointerMotionHintMask),
		     GrabModeAsync, GrabModeAsync,
		     win, cur, CurrentTime) != GrabSuccess) {
	XBell(disp, 0);
	return 0;
    }
    if (echoGC == (GC) 0) {
	unsigned long gcmask;
	XGCValues gcvals;

	gcmask = ux11_fill_gcvals(&gcvals, GCForeground, zeroPixel ^ bgPixel,
				  GCFunction, GXxor,
				  UX11_END);
	echoGC = XCreateGC(disp, win, gcmask, &gcvals);
    }
    startX = evt->x;  startY = evt->y;
    XQueryPointer(disp, win, &root_rtn, &child_rtn, &root_x, &root_y,
		  &curX, &curY, &mask_rtn);
    /* Draw first box */
    DRAWBOX;
    stopFlag = 0;
    while (!stopFlag) {
	XNextEvent(disp, &theEvent);
	switch (theEvent.xany.type) {
	case MotionNotify:
	    XQueryPointer(disp, win, &root_rtn, &child_rtn, &root_x, &root_y,
			  &newX, &newY, &mask_rtn);
	    /* Undraw the old one */
	    DRAWBOX;
	    /* Draw the new one */
	    curX = newX;  curY = newY;
	    DRAWBOX;
	    break;
	case ButtonRelease:
	    DRAWBOX;
	    XUngrabPointer(disp, CurrentTime);
	    stopFlag = 1;
	    if ((startX-curX != 0) && (startY-curY != 0)) {
		/* Figure out relative bounding box */
		loX = TRANX(startX);   loY = TRANY(startY);
		hiX = TRANX(curX);     hiY = TRANY(curY);
		if (loX > hiX) {
		    double temp;
		    
		    temp = hiX;
		    hiX = loX;
		    loX = temp;
		}
		if (loY > hiY) {
		    double temp;
		    
		    temp = hiY;
		    hiY = loY;
		    loY = temp;
		}
		/* physical aspect ratio */
		asp = ((double) ABS(startX-curX))/((double) ABS(startY-curY));
		new_win = NewWindow(progname, loX, loY, hiX, hiY, asp);
		if (new_win) {
		    numwin = 1;
		} else {
		    numwin = 0;
		}
	    } else {
		numwin = 0;
	    }
	    break;
	default:
	    printf("unknown event: %d\n", theEvent.xany.type);
	    break;
	}
    }
    return numwin;
}
#endif


unsigned long GetColor(name)
char *name;
/* 
 * Given a standard color name,  this routine fetches the associated
 * pixel value from the global `cmap'.  The name may be specified
 * using the standard specification format parsed by XParseColor.
 * Some sort of parsing error causes the program to exit.
 */
{
    XColor def;

    if (XParseColor(disp, cmap, name, &def)) {
	if (XAllocColor(disp, cmap, &def)) {
	    return def.pixel;
	} else {
	    (void) fprintf(stderr, "xgraph: could not allocate color: `%s'\n",
			   name);
	    exit(1);
	}
    } else {
	(void) fprintf(stderr, "xgraph: cannot parse color specification: `%s'\n",
		       name);
	exit(1);
    }
    return 0;
}


/* Default line styles */
static char *defStyle[MAXATTR] = {
    "11", "44", "1142", "31", "88", "113111", "2231", "224" 
  };

/* Default color names */
static char *defColors[MAXATTR] = {
    "red", "SpringGreen", "blue", "yellow",
    "cyan", "plum", "orange", "coral"
  };

int InitSets()
/*
 * Initializes the data sets with default information.
 */
{
    int idx;
    char setname[40];
    Window temp_win;
    XSetWindowAttributes wattr;
    char *font_name;

    if (ux11_std_vismap(disp, &vis, &cmap, &screen, &depth) == UX11_ALTERNATE) {
	install_flag = 1;
    } else {
	install_flag = 0;
    }
    black_pixel = GetColor("black");
    white_pixel = GetColor("white");

    /* Create a temporary window for creating graphics contexts */
    temp_win = XCreateWindow(disp, RootWindow(disp, screen),
			     0, 0, 10, 10, 0, depth, InputOutput,
			     vis, (unsigned long) 0, &wattr);
    bwFlag = (depth < 4);
    bdrSize = 2;
    bdrPixel = black_pixel;
    (void) strcpy(titleText, "X Graph");
    (void) strcpy(XUnits, "X");
    (void) strcpy(YUnits, "Y");
    tickFlag = 0;
    markFlag = 0;
    pixelMarks = 0;
    bigPixel = 0;
    colorMark = 0;
    bbFlag = 0;
    noLines = 0;
    logXFlag = 0;
    logYFlag = 0;
    barFlag = 0;
    barBase = 0.0;
    barWidth = -1.0;		/* Holder */
    lineWidth = 1;
    echoPix = BIGINT;
    /* Set the user bounding box */
    UsrLX = UsrLY = MAXFLOAT;
    UsrRX = UsrRY = -MAXFLOAT;
    /* Depends critically on whether the display has color */
    if (bwFlag) {
	/* Its black and white */
	bgPixel = white_pixel;
	zeroPixel = black_pixel;
	zeroWidth = 3;
	normPixel = black_pixel;
	/* Initialize set defaults */
	for (idx = 0;  idx < MAXATTR;  idx++) {
	    /* Needs work! */
	    AllAttrs[idx].lineStyleLen =
	      ProcessStyle(defStyle[idx], AllAttrs[idx].lineStyle, MAXLS);
	    AllAttrs[idx].pixelValue = black_pixel;
	}
    } else {
	/* Its color */
	bgPixel = GetColor("LightGray");
	zeroPixel = white_pixel;
	zeroWidth = 1;
	normPixel = black_pixel;
	/* Initalize attribute colors defaults */
	AllAttrs[0].lineStyle[0] = '\0';
	AllAttrs[0].lineStyleLen = 0;
	AllAttrs[0].pixelValue = GetColor(defColors[0]);
	for (idx = 1;  idx < MAXATTR;  idx++) {
	    AllAttrs[idx].lineStyleLen =
	      ProcessStyle(defStyle[idx-1], AllAttrs[idx].lineStyle, MAXLS);
	    AllAttrs[idx].pixelValue = GetColor(defColors[idx]);
	}
    }
    if (!ux11_size_font(disp, screen, 4000, &axisFont, &font_name)) {
	fprintf(stderr, "Can't find an appropriate axis font\n");
	abort();
    }
    if (!ux11_size_font(disp, screen, 6000, &titleFont, &font_name)) {
	fprintf(stderr, "Can't find an appropriate title font\n");
	abort();
    }
    /* Initialize the data sets */
    for (idx = 0;  idx < MAXSETS;  idx++) {
	(void) sprintf(setname, "Set %d", idx);
	AllSets[idx].setName = STRDUP(setname);
	AllSets[idx].numPoints = 0;
	AllSets[idx].allocSize = 0;
	AllSets[idx].xvec = AllSets[idx].yvec = (double *) 0;
    }
    /* Store bitmaps for dots and markers */
    dotMap = XCreateBitmapFromData(disp, temp_win, dot_bits, dot_w, dot_h);

    AllAttrs[0].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark1_bits, mark_w, mark_h);
    AllAttrs[1].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark2_bits, mark_w, mark_h);
    AllAttrs[2].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark3_bits, mark_w, mark_h);
    AllAttrs[3].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark4_bits, mark_w, mark_h);
    AllAttrs[4].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark5_bits, mark_w, mark_h);
    AllAttrs[5].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark6_bits, mark_w, mark_h);
    AllAttrs[6].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark7_bits, mark_w, mark_h);
    AllAttrs[7].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark8_bits, mark_w, mark_h);
    XDestroyWindow(disp, temp_win);
}



static char *def_str;
static Pixel def_pixel;
static int def_int;
static XFontStruct *def_font;
static double def_dbl;

int rd_pix(name)
char *name;
/* Result in def_pixel */
{
    if (def_str = XGetDefault(disp, Prog_Name, name)) {
	def_pixel = GetColor(def_str);
	if (debugFlag) printf("%s (pixel) = %s\n", name, def_str);
	return 1;
    } else {
	return 0;
    }
}

int rd_int(name)
char *name;
/* Result in def_int */
{
    if (def_str = XGetDefault(disp, Prog_Name, name)) {
	if (sscanf(def_str, "%ld", &def_int) == 1) {
	    if (debugFlag) printf("%s (int) = %s\n", name, def_str);
	    return 1;
	} else {
	    fprintf(stderr, "warning: could not read integer value for %s\n", name);
	    return 0;
	}
    } else {
	return 0;
    }
}

int rd_str(name)
char *name;
/* Result in def_str */
{
    if (def_str = XGetDefault(disp, Prog_Name, name)) {
	if (debugFlag) printf("%s (str) = %s\n", name, def_str);
	return 1;
    } else {
	return 0;
    }
}

int rd_font(name)
char *name;
/* Result in def_font */
{
    if (def_str = XGetDefault(disp, Prog_Name, name)) {
	if (def_font = XLoadQueryFont(disp, def_str)) {
	    if (debugFlag) printf("%s (font) = %s\n", name, def_str);
	    return 1;
	} else {
	    fprintf(stderr, "warning: could not load font for %s\n", name);
	    return 0;
	}
    } else {
	return 0;
    }
}

int rd_flag(name)
char *name;
/* Result in def_int */
{
    if (def_str = XGetDefault(disp, Prog_Name, name)) {
	def_int = (stricmp(def_str, "on") == 0) || (stricmp(def_str, "1") == 0);
	if (debugFlag) printf("%s (flag) = %s\n", name, def_str);
	return 1;
    } else {
	return 0;
    }
}
    
int rd_dbl(name)
char *name;
/* Result in def_dbl */
{
    if (def_str = XGetDefault(disp, Prog_Name, name)) {
	if (sscanf(def_str, "%lg", &def_dbl) == 1) {
	    if (debugFlag) printf("%s (dbl) = %s\n", name, def_str);
	    return 1;
	} else {
	    fprintf(stderr, "warning: could not read value of %s\n", name);
	    return 0;
	}
    } else {
	return 0;
    }
}

int ReversePixel(pixValue)
Pixel *pixValue;		/* Pixel value to reverse  */
{
    if (*pixValue == white_pixel)
      *pixValue = black_pixel;
    else if (*pixValue == black_pixel)
      *pixValue = white_pixel;
}

int ProcessStyle(style, buf, maxbuf)
char *style;			/* Textual line style spec */
char *buf;			/* Returned buf            */
int maxbuf;			/* Maximum size of buffer  */
/*
 * Translates a textual specification for a line style into
 * an appropriate dash list for X11.  Returns the length.
 */
{
    int len, i;

    strncpy(buf, style, maxbuf-1);
    buf[maxbuf-1] = '\0';
    len = strlen(buf);
    for (i = 0;  i < len;  i++) {
	if ((buf[i] >= '0') && (buf[i] <= '9')) {
	    buf[i] = buf[i] - '0';
	} else if ((buf[i] >= 'a') && (buf[i] <= 'f')) {
	    buf[i] = buf[i] - 'a' + 10;
	} else if ((buf[i] >= 'A') && (buf[i] <= 'F')) {
	    buf[i] = buf[i] - 'A' + 10;
	} else {
	    return 0;
	}
    }
    return len;
}

int ReadDefaults()
/*
 * Reads X default values which override the hard-coded defaults
 * set up by InitSets.
 */
{
    char newname[100];
    int idx;

    if (rd_pix("Background")) {
	bgPixel = def_pixel;
    }
    if (rd_int("BorderSize")) {
	bdrSize = def_int;
    }
    if (rd_pix("Border")) {
	bdrPixel = def_pixel;
    }
    if (rd_int("GridSize")) {
	axisWidth = def_int;
    }
    if (rd_str("GridStyle")) {
	axisLSLen = ProcessStyle(def_str, axisLS, MAXLS);
    }

    if (rd_pix("Foreground")) {
	normPixel = def_pixel;
    }
    
    if (rd_pix("ZeroColor")) zeroPixel = def_pixel;

    if (rd_str("ZeroStyle")) {
	zeroLSLen = ProcessStyle(def_str, zeroLS, MAXLS);
    }

    if (rd_int("ZeroSize")) zeroWidth = def_int;

    if (rd_font("LabelFont")) {
	axisFont = def_font;
    }

    if (rd_font("TitleFont")) {
	titleFont = def_font;
    }

    if (rd_flag("Ticks")) tickFlag = def_int;

    if (rd_str("Device")) Odevice = def_str;
    if (rd_str("Disposition")) Odisp = def_str;
    if (rd_str("FileOrDev")) OfileDev = def_str;

    /* Read device specific parameters */
    for (idx = 0;  idx < hard_count;  idx++) {
	sprintf(newname, "%s.Dimension", hard_devices[idx].dev_name);
	if (rd_dbl(newname)) hard_devices[idx].dev_max_dim = def_dbl;
	sprintf(newname, "%s.OutputTitleFont", hard_devices[idx].dev_name);
	if (rd_str(newname)) {
	    (void) strncpy(hard_devices[idx].dev_title_font, def_str, MFNAME-1);
	}
	sprintf(newname, "%s.OutputTitleSize", hard_devices[idx].dev_name);
	if (rd_dbl(newname)) hard_devices[idx].dev_title_size = def_dbl;
	sprintf(newname, "%s.OutputAxisFont", hard_devices[idx].dev_name);
	if (rd_str(newname)) {
	    (void) strncpy(hard_devices[idx].dev_axis_font, def_str, MFNAME-1);
	}
	sprintf(newname, "%s.OutputAxisSize", hard_devices[idx].dev_name);
	if (rd_dbl(newname)) hard_devices[idx].dev_axis_size = def_dbl;
    }
    if (rd_flag("SmallPixels")) {
	if (def_int) { noLines = markFlag = 1; pixelMarks = 1; bigPixel = 0; }
    }
    if (rd_flag("LargePixels")) {
	if (def_int) {
	    markFlag = 1; pixelMarks = 1; bigPixel = 1;
	}
    }
    if (rd_flag("Markers")) {
	if (def_int) { markFlag = 1; pixelMarks = 0; colorMark = 0; }
    }
    if (rd_flag("StyleMarkers")) {
	if (def_int) { markFlag = 1; pixelMarks = 0; colorMark = 1; }
    }
    if (rd_flag("BoundBox")) bbFlag = def_int;
    if (rd_flag("NoLines")) noLines = def_int;
    if (rd_flag("PixelMarkers")) pixelMarks = def_int;
    if (rd_int("LineWidth")) lineWidth = def_int;

    /* Read the default line and color attributes */
    for (idx = 0;  idx < MAXATTR;  idx++) {
	(void) sprintf(newname, "%d.Style", idx);
	if (rd_str(newname)) {
	    AllAttrs[idx].lineStyleLen =
	      ProcessStyle(def_str, AllAttrs[idx].lineStyle, MAXLS);
	}
	(void) sprintf(newname, "%d.Color", idx);
	if (rd_pix(newname)) {
	    AllAttrs[idx].pixelValue = def_pixel;
	}
    }

    if (bwFlag) {
	/* Black and white - check for reverse video */
	if (rd_flag("ReverseVideo")) {
	    if (def_int) {
		ReversePixel(&bgPixel);
		ReversePixel(&bdrPixel);
		ReversePixel(&zeroPixel);
		ReversePixel(&normPixel);
		for (idx = 0;  idx < MAXATTR;  idx++) {
		    ReversePixel(&(AllAttrs[idx].pixelValue));
		}
	    }
	}
    }
}


int argerror(err, val)
char *err, *val;
{
    (void) fprintf(stderr, "Error: %s: %s\n\n", val, err);
    (void) fprintf(stderr,
	    "format: xgraph [-bd color] [-bg color] [-fg color] [-zg color]\n");
    (void) fprintf(stderr,
	    "         [-bw bdr_width] [-lf label_font] [-tf title_font]\n");
    (void) fprintf(stderr,
	    "         [-<digit> set_name] [-t title] [-x unitname] [-y unitname]\n");
    (void) fprintf(stderr,
	    "         [-lx x1,x2] [-ly y1,y2] [-bar] [-brb base] [-brw width]\n");
    (void) fprintf(stderr,
	    "         [-bb] [-db] [-lnx] [-lny] [-nl] [-m] [-M] [-p] [-P] [-rv]\n");
    (void) fprintf(stderr,
	    "         [-tk] [-lw linewidth] [-display host:display.screen] [=geospec]\n");
    (void) fprintf(stderr, "\nFonts must be fixed width\n");
    (void) fprintf(stderr, "-bar  Draw bar graph with base -brb and width -brw\n");
    (void) fprintf(stderr, "-bb   Draws bounding box around data\n");
    (void) fprintf(stderr, "-db   Turns on debugging mode\n");
    (void) fprintf(stderr, "-lnx  Logarithmic scale for X axis\n");
    (void) fprintf(stderr, "-lny  Logarithmic scale for Y axis\n");
    (void) fprintf(stderr, "-nl   Don't draw lines (scatter plot)\n");
    (void) fprintf(stderr, "-m -M Mark points distinctively (M varies with color)\n");
    (void) fprintf(stderr, "-p -P Mark points with dot (P means big dot)\n");
    (void) fprintf(stderr, "-rv   Reverse video on black and white displays\n");
    (void) fprintf(stderr, "-tk   Draw tick marks instead of full grid\n");
    exit(1);
}

int ParseArgs(argc, argv)
int argc;
char *argv[];
/*
 * This routine parses the argument list for xgraph.  There are too
 * many to mention here so I won't.
 */
{
    int idx, set, tempPixel;
    XFontStruct *tempFont;

    idx = 1;
    while (idx < argc) {
	if (argv[idx][0] == '-') {
	    /* Check to see if its a data set name */
	    if (sscanf(argv[idx], "-%d", &set) == 1) {
		/* The next string is a set name */
		if (idx+1 >= argc) argerror("missing set name", argv[idx]);
		AllSets[set].setName = argv[idx+1];
		idx += 2;
	    } else {
		/* Some non-dataset option */
		if (strcmp(argv[idx], "-x") == 0) {
		    /* Units for X axis */
		    if (idx+1 >= argc)
		      argerror("missing axis name", argv[idx]);
		    (void) strcpy(XUnits, argv[idx+1]);
		    idx += 2;
		} else if (strcmp(argv[idx], "-y") == 0) {
		    /* Units for Y axis */
		    if (idx+1 >= argc)
		      argerror("missing axis name", argv[idx]);
		    (void) strcpy(YUnits, argv[idx+1]);
		    idx += 2;
		} else if (strcmp(argv[idx], "-t") == 0) {
		    /* Title of plot */
		    if (idx+1 >= argc)
		      argerror("missing plot title", argv[idx]);
		    (void) strcpy(titleText, argv[idx+1]);
		    idx += 2;
		} else if (strcmp(argv[idx], "-fg") == 0) {
		    /* Foreground color */
		    if (idx+1 >= argc) argerror("missing color", argv[idx]);
		    tempPixel = GetColor(argv[idx+1]);
		    normPixel = tempPixel;
		    idx += 2;
		} else if (strcmp(argv[idx], "-bg") == 0) {
		    /* Background color */
		    if (idx+1 >= argc) argerror("missing color", argv[idx]);
		    tempPixel = GetColor(argv[idx+1]);
		    bgPixel = tempPixel;
		    idx += 2;
		} else if (strcmp(argv[idx], "-bd") == 0) {
		    /* Border color */
		    if (idx+1 >= argc) argerror("missing color", argv[idx]);
		    tempPixel = GetColor(argv[idx+1]);
		    bdrPixel = tempPixel;
		    idx += 2;
		} else if (strcmp(argv[idx], "-bw") == 0) {
		    /* Border width */
		    if (idx+1 >= argc) argerror("missing border size",
						  argv[idx]);
		    bdrSize = atoi(argv[idx+1]);
		    idx += 2;
		} else if (strcmp(argv[idx], "-zg") == 0) {
		    /* Zero grid color */
		    if (idx+1 >= argc) argerror("missing color", argv[idx]);
		    tempPixel = GetColor(argv[idx+1]);
		    zeroPixel = tempPixel;
		    idx += 2;
		} else if (strcmp(argv[idx], "-tf") == 0) {
		    /* Title Font */
		    if (idx+1 >= argc) argerror("missing font", argv[idx]);
		    tempFont = XLoadQueryFont(disp, argv[idx+1]);
		    if (!tempFont) argerror("can't get font", argv[idx+1]);
		    titleFont = tempFont;
		    idx += 2;
		} else if (strcmp(argv[idx], "-lf") == 0) {
		    /* Label Font */
		    if (idx+1 >= argc) argerror("missing font", argv[idx]);
		    tempFont = XLoadQueryFont(disp, argv[idx+1]);
		    if (!tempFont) argerror("can't get font", argv[idx+1]);
		    axisFont = tempFont;
		    idx += 2;
		} else if (strcmp(argv[idx], "-rv") == 0) {
		    /* Reverse video option */
		    ReversePixel(&bgPixel);
		    ReversePixel(&bdrPixel);
		    ReversePixel(&zeroPixel);
		    ReversePixel(&normPixel);
		    for (set = 0;  set < MAXATTR;  set++) {
			ReversePixel(&(AllAttrs[set].pixelValue));
		    }
		    idx++;
		} else if (strcmp(argv[idx], "-tk") == 0) {
		    /* Draw tick marks instead of full grid */
		    tickFlag = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-bb") == 0) {
		    /* Draw bounding box around graph region */
		    bbFlag = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-lx") == 0) {
		    /* Limit the X coordinates */
		    if (idx+1 >= argc) argerror("missing coordinate(s)",
						  argv[idx]);
		    (void) sscanf(argv[idx+1], "%lf,%lf", &UsrLX, &UsrRX);
		    idx += 2;
		} else if (strcmp(argv[idx], "-ly") == 0) {
		    /* Limit the Y coordinates */
		    if (idx+1 >= argc) argerror("missing coordinate(s)",
						  argv[idx]);
		    (void) sscanf(argv[idx+1], "%lf,%lf", &UsrLY, &UsrRY);
		    idx += 2;
		} else if (strcmp(argv[idx], "-lw") == 0) {
		    /* Set the line width */
		    if (idx+1 >= argc) argerror("missing line width",
						  argv[idx]);
		    lineWidth = atoi(argv[idx+1]);
		    idx += 2;
		} else if (strcmp(argv[idx], "-nl") == 0) {
		    noLines = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-m") == 0) {
		    /* 
		     * Mark each point with a individual marker 
		     * varying with linestyle
		     */
		    markFlag = 1;  pixelMarks = colorMark = 0;
		    idx++;
		} else if (strcmp(argv[idx], "-M") == 0) {
		    /*
		     * Mark each point with an individual marker
		     * varying with color.
		     */
		    markFlag = colorMark = 1;  pixelMarks = 0;
		    idx++;
		} else if (strcmp(argv[idx], "-p") == 0) {
		    /* Draw small pixel sized markers */
		    noLines = markFlag = pixelMarks = 1;
		    bigPixel = 0;
		    idx++;
		} else if (strcmp(argv[idx], "-P") == 0) {
		    /* Draw large pixel sized markers */
		    markFlag = pixelMarks = bigPixel = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-lnx") == 0) {
		    /* The X axis is logarithmic */
		    logXFlag = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-lny") == 0) {
		    /* The Y axis is logarithmic */
		    logYFlag = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-bar") == 0) {
		    /* Draw bar graph */
		    barFlag = 1;
		    idx++;
		} else if (strcmp(argv[idx], "-brw") == 0) {
		    /* Set width of bar */
		    if (idx+1 >= argc) argerror("missing width", argv[idx]);
		    (void) sscanf(argv[idx+1], "%lf", &barWidth);
		    idx += 2;
		} else if (strcmp(argv[idx], "-brb") == 0) {
		    if (idx+1 >= argc) argerror("missing base", argv[idx]);
		    (void) sscanf(argv[idx+1], "%lf", &barBase);
		    idx += 2;
		} else if (strcmp(argv[idx], "-db") == 0) {
		    /* Debug on */
		    debugFlag = 1;
		    XSynchronize(disp, 1);
		    XFlush(disp);
		    idx++;
		} else if (strcmp(argv[idx], "-display") == 0) {
		    /* Harmless display specification */
		    idx += 2;
		} else {
		    argerror("unknown option", argv[idx]);
		}
	    }
	} else if (argv[idx][0] == '=') {
	    /* Its a geometry specification */
	    geoSpec = &(argv[idx][1]);
	    idx++;
	} else {
	    /* It might be the host:display string */
	    if (rindex(argv[idx], ':') == (char *) 0) {
		/* Should be an input file */
		inFileNames[numFiles] = argv[idx];
		numFiles++;
	    }
	    idx++;
	}
    }
}



/* For reading in the data */
static int setNumber = 0;
static maxSize = 0;

int ReadData(stream, filename)
FILE *stream;
char *filename;
/*
 * Reads in the data sets from the supplied stream.  If the format
 * is correct,  it returns the current maximum number of points across
 * all data sets.  If there is an error,  it returns -1.
 */
{
    char buffer[MAXBUFSIZE];
    char setname[40];
    int spot = 0;
    int line_count = 0;
    
    /* Set name of set to file name if appropriate */
    (void) sprintf(setname, "Set %d", setNumber);
    if ((strcmp(AllSets[setNumber].setName, setname) == 0) && filename) {
	AllSets[setNumber].setName = filename;
    }

    /* Eliminate over-zealous set increments */
    if (setNumber > 0) {
	if (AllSets[setNumber-1].numPoints == 0) {
	    setNumber--;
	    return ReadData(stream, filename);
	}
    }
    if (!filename) filename = "stdin";
    while (setNumber < MAXSETS) {
	if (!fgets(buffer, MAXBUFSIZE, stream)) break;
	line_count++;
	if (buffer[0] == '#') continue;
	if (buffer[0] == '\n') {
	    /* Empty line - increment data set */
	    setNumber++;
	    if (spot > maxSize) maxSize = spot;
	    spot = 0;
	} else if (buffer[0] == '"') {
	    buffer[strlen(buffer)-1] = '\0';
	    AllSets[setNumber].setName = STRDUP(&(buffer[1]));
	} else {
	    if (spot >= AllSets[setNumber].allocSize) {
		/* Time to make the arrays bigger (or initialize them) */
		if (AllSets[setNumber].allocSize == 0) {
		    AllSets[setNumber].allocSize = INITSIZE;
		    AllSets[setNumber].xvec = (double *)
		      malloc((unsigned) (INITSIZE * sizeof(double)));
		    AllSets[setNumber].yvec = (double *)
		      malloc((unsigned) (INITSIZE * sizeof(double)));
		} else {
		    AllSets[setNumber].allocSize *= 2;
		    AllSets[setNumber].xvec = (double *)
		      realloc((char *) AllSets[setNumber].xvec,
			      (unsigned) (AllSets[setNumber].allocSize *
					  sizeof(double)));
		    AllSets[setNumber].yvec = (double *)
		      realloc((char *) AllSets[setNumber].yvec,
			      (unsigned) (AllSets[setNumber].allocSize *
					  sizeof(double)));
		}
	    }
	    if (sscanf(buffer, "%F %F",
		       &(AllSets[setNumber].xvec[spot]),
		       &(AllSets[setNumber].yvec[spot])) != 2)
	      {
		  (void) fprintf(stderr, "file: `%s', line: %d\n",
				 filename, line_count);
		  (void) fprintf(stderr, "Exactly two coordinates per line\n");
		  return -1;
	      }
	    if (logXFlag) {
		if (AllSets[setNumber].xvec[spot] <= 0.0) {
		    (void) fprintf(stderr, "file: `%s', line: %d\n",
				   filename, line_count);
		    (void) fprintf(stderr, "Non-positive X value in logarithmic mode\n");
		    return -1;
		}
		AllSets[setNumber].xvec[spot] =
		  log10(AllSets[setNumber].xvec[spot]);
	    }
	    if (logYFlag) {
		if (AllSets[setNumber].yvec[spot] <= 0.0) {
		    (void) fprintf(stderr, "file: `%s', line: %d\n",
				   filename, line_count);
		    (void) fprintf(stderr, "Non-positive Y value in logarithmic mode\n");
		    return -1;
		}
		AllSets[setNumber].yvec[spot] =
		  log10(AllSets[setNumber].yvec[spot]);
	    }
	    /* Update bounding box */
	    if (AllSets[setNumber].xvec[spot] < llx)
	      llx = AllSets[setNumber].xvec[spot];
	    if (AllSets[setNumber].xvec[spot] > urx)
	      urx = AllSets[setNumber].xvec[spot];
	    if (AllSets[setNumber].yvec[spot] < lly)
	      lly = AllSets[setNumber].yvec[spot];
	    if (AllSets[setNumber].yvec[spot] > ury)
	      ury = AllSets[setNumber].yvec[spot];
	    spot++;
	    AllSets[setNumber].numPoints += 1;
	}
    }
    if (spot > maxSize) maxSize = spot;
    if (line_count <= 0) {
	(void) fprintf(stderr, "No data found\n");
	return -1;
    }
    if (setNumber >= MAXSETS) {
	(void) fprintf(stderr, "Too many data sets\n");
	return -1;
    } else {
	setNumber++;
	return maxSize;
    }
}




int DrawWindow(win_info)
LocalWin *win_info;		/* Window information */
/*
 * Draws the data in the window.  Does not clear the window.
 * The data is scaled so that all of the data will fit.
 * Grid lines are drawn at the nearest power of 10 in engineering
 * notation..  Draws axis numbers along bottom and left hand edges.
 * Centers title at top of window.
 */
{
    /* Figure out the transformation constants */
    if (TransformCompute(win_info)) {
	
	/* Draw the title */
	DrawTitle(win_info);

	/* Draw the legend */
	DrawLegend(win_info);

	/* Draw the axis unit labels,  grid lines,  and grid labels */
	DrawGridAndAxis(win_info);

	/* Draw the data sets themselves */
	DrawData(win_info);
    }
}




DrawTitle(wi)
LocalWin *wi;		/* Window information    */
/*
 * This routine draws the title of the graph centered in
 * the window.  It is spaced down from the top by an amount
 * specified by the constant PADDING.  The font must be
 * fixed width.  The routine returns the height of the
 * title in pixels.
 */
{
    wi->dev_info.xg_text(wi->dev_info.user_state,
			 wi->dev_info.area_w/2,
			 wi->dev_info.axis_pad,
			 titleText, T_TOP, T_TITLE);
}




int TransformCompute(wi)
LocalWin *wi;			/* Window information          */
/*
 * This routine figures out how to draw the axis labels and grid lines.
 * Both linear and logarithmic axes are supported.  Axis labels are
 * drawn in engineering notation.  The power of the axes are labeled
 * in the normal axis labeling spots.  The routine also figures
 * out the necessary transformation information for the display
 * of the points (it touches XOrgX, XOrgY, UsrOrgX, UsrOrgY, and
 * UnitsPerPixel).
 */
{
    double bbCenX, bbCenY, bbHalfWidth, bbHalfHeight;
    int idx, maxName, leftWidth;
    char err[MAXBUFSIZE];

    /*
     * First,  we figure out the origin in the X window.  Above
     * the space we have the title and the Y axis unit label.
     * To the left of the space we have the Y axis grid labels.
     */

    wi->XOrgX = wi->dev_info.bdr_pad + (7 * wi->dev_info.axis_width)
      + wi->dev_info.bdr_pad;
    wi->XOrgY = wi->dev_info.bdr_pad + wi->dev_info.title_height
      + wi->dev_info.bdr_pad + wi->dev_info.axis_height
	+ wi->dev_info.axis_height/2 + wi->dev_info.bdr_pad;

    /*
     * Now we find the lower right corner..  Below the space we
     * have the X axis grid labels.  To the right of the space we
     * have the X axis unit label and the legend.  We assume the 
     * worst case size for the unit label.
     */

    maxName = 0;
    for (idx = 0;  idx < MAXSETS;  idx++) {
	if (AllSets[idx].numPoints > 0) {
	    int tempSize;

	    tempSize = strlen(AllSets[idx].setName);
	    if (tempSize > maxName) maxName = tempSize;
	}
    }
    /* Worst case size of the X axis label: */
    leftWidth = (strlen(XUnits)) * wi->dev_info.axis_width;
    if ((maxName*wi->dev_info.axis_width)+wi->dev_info.bdr_pad > leftWidth)
      leftWidth = maxName * wi->dev_info.axis_width + wi->dev_info.bdr_pad;
    
    wi->XOppX = wi->dev_info.area_w - wi->dev_info.bdr_pad - leftWidth;
    wi->XOppY = wi->dev_info.area_h - wi->dev_info.bdr_pad
      - wi->dev_info.axis_height - wi->dev_info.bdr_pad;

    if ((wi->XOrgX >= wi->XOppX) || (wi->XOrgY >= wi->XOppY)) {
#ifdef TOOLBOX	
	do_error(strcpy(err, "Drawing area is too small\n"));
#else
	(void) fprintf(stderr, "Drawing area is too small\n");
#endif	
	return 0;
    }

    /* 
     * We now have a bounding box for the drawing region.
     * Figure out the units per pixel using the data set bounding box.
     */
    wi->XUnitsPerPixel = (wi->hiX - wi->loX)/((double) (wi->XOppX - wi->XOrgX));
    wi->YUnitsPerPixel = (wi->hiY - wi->loY)/((double) (wi->XOppY - wi->XOrgY));

    /*
     * Find origin in user coordinate space.  We keep the center of
     * the original bounding box in the same place.
     */
    bbCenX = (wi->loX + wi->hiX) / 2.0;
    bbCenY = (wi->loY + wi->hiY) / 2.0;
    bbHalfWidth = ((double) (wi->XOppX - wi->XOrgX))/2.0 * wi->XUnitsPerPixel;
    bbHalfHeight = ((double) (wi->XOppY - wi->XOrgY))/2.0 * wi->YUnitsPerPixel;
    wi->UsrOrgX = bbCenX - bbHalfWidth;
    wi->UsrOrgY = bbCenY - bbHalfHeight;
    wi->UsrOppX = bbCenX + bbHalfWidth;
    wi->UsrOppY = bbCenY + bbHalfHeight;

    /*
     * Everything is defined so we can now use the SCREENX and SCREENY
     * transformations.
     */
    return 1;
}

int DrawGridAndAxis(wi)
LocalWin *wi;			/* Window information         */
/*
 * This routine draws grid line labels in engineering notation,
 * the grid lines themselves,  and unit labels on the axes.
 */
{
    int expX, expY;		/* Engineering powers */
    int startX;
    int Yspot, Xspot;
    char power[10], value[10], final[MAXBUFSIZE+10];
    double Xincr, Yincr, Xstart, Ystart, Yindex, Xindex, larger;
    XSegment segs[2];
    double initGrid(), stepGrid();

    /*
     * Grid display powers are computed by taking the log of
     * the largest numbers and rounding down to the nearest
     * multiple of 3.
     */
    if (logXFlag) {
	expX = 0;
    } else {
	if (fabs(wi->UsrOrgX) > fabs(wi->UsrOppX)) {
	    larger = fabs(wi->UsrOrgX);
	} else {
	    larger = fabs(wi->UsrOppX);
	}
	expX = ((int) floor(nlog10(larger)/3.0)) * 3;
    }
    if (logYFlag) {
	expY = 0;
    } else {
	if (fabs(wi->UsrOrgY) > fabs(wi->UsrOppY)) {
	    larger = fabs(wi->UsrOrgY);
	} else {
	    larger = fabs(wi->UsrOppY);
	}
	expY = ((int) floor(nlog10(larger)/3.0)) * 3;
    }

    /*
     * With the powers computed,  we can draw the axis labels.
     */
    if (expY != 0) {
	(void) strcpy(final, YUnits);
	(void) strcat(final, " x 10");
	Xspot = wi->dev_info.bdr_pad +
	  ((strlen(YUnits)+5) * wi->dev_info.axis_width);
	Yspot = wi->dev_info.bdr_pad * 2 + wi->dev_info.title_height +
	  wi->dev_info.axis_height/2;
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     Xspot, Yspot, final, T_RIGHT, T_AXIS);
	(void) sprintf(power, "%d", expY);
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     Xspot, Yspot, power, T_LOWERLEFT, T_AXIS);
    } else {
	Yspot = wi->dev_info.bdr_pad * 2 + wi->dev_info.title_height;
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     wi->dev_info.bdr_pad, Yspot, YUnits,
			     T_UPPERLEFT, T_AXIS);
    }
	
    startX = wi->dev_info.area_w - wi->dev_info.bdr_pad;
    if (expX != 0) {
	(void) sprintf(power, "%d", expX);
	startX -= (strlen(power) + 5) * wi->dev_info.axis_width;
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     startX, wi->XOppY, power, T_LOWERLEFT, T_AXIS);
	(void) strcpy(final, XUnits);
	(void) strcat(final, " x 10");
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     startX, wi->XOppY, final, T_RIGHT, T_AXIS);
    } else {
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     startX, wi->XOppY, XUnits, T_RIGHT, T_AXIS);
    }

    /* 
     * First,  the grid line labels
     */
    Yincr = (wi->dev_info.axis_pad + wi->dev_info.axis_height) * wi->YUnitsPerPixel;
    Ystart = initGrid(wi->UsrOrgY, Yincr, logYFlag);
    for (Yindex = Ystart;  Yindex < wi->UsrOppY;  Yindex = stepGrid()) {
	Yspot = SCREENY(wi, Yindex);
	/* Write the axis label */
	WriteValue(value, Yindex, expY, logYFlag);
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     wi->dev_info.bdr_pad +
				     (7 * wi->dev_info.axis_width),
			     Yspot, value, T_RIGHT, T_AXIS);
    }

    Xincr = (wi->dev_info.axis_pad + (wi->dev_info.axis_width * 7)) * wi->XUnitsPerPixel;
    Xstart = initGrid(wi->UsrOrgX, Xincr, logXFlag);
    for (Xindex = Xstart;  Xindex < wi->UsrOppX;  Xindex = stepGrid()) {
	Xspot = SCREENX(wi, Xindex);
	/* Write the axis label */
	WriteValue(value, Xindex, expX, logXFlag);
	wi->dev_info.xg_text(wi->dev_info.user_state,
			     Xspot,
			     wi->dev_info.area_h - wi->dev_info.bdr_pad,
			     value, T_BOTTOM, T_AXIS);
    }

    /*
     * Now,  the grid lines or tick marks
     */
    Yincr = (wi->dev_info.axis_pad + wi->dev_info.axis_height) * wi->YUnitsPerPixel;
    Ystart = initGrid(wi->UsrOrgY, Yincr, logYFlag);
    for (Yindex = Ystart;  Yindex < wi->UsrOppY;  Yindex = stepGrid()) {
	Yspot = SCREENY(wi, Yindex);
	/* Draw the grid line or tick mark */
	if (tickFlag) {
	    segs[0].x1 = wi->XOrgX;
	    segs[0].x2 = wi->XOrgX + wi->dev_info.tick_len;
	    segs[1].x1 = wi->XOppX - wi->dev_info.tick_len;
	    segs[1].x2 = wi->XOppX;
	    segs[0].y1 = segs[0].y2 = segs[1].y1 = segs[1].y2 = Yspot;
	} else {
	    segs[0].x1 = wi->XOrgX;  segs[0].x2 = wi->XOppX;
	    segs[0].y1 = segs[0].y2 = Yspot;
	}
	if ((ABS(Yindex) < ZERO_THRES) && !logYFlag) {
	    wi->dev_info.xg_seg(wi->dev_info.user_state,
				 1, segs, zeroWidth,
				 L_ZERO, 0, 0);
	    if (tickFlag) {
		wi->dev_info.xg_seg(wi->dev_info.user_state,
				     1, &(segs[1]), zeroWidth,
				     L_ZERO, 0, 0);
	    }
	} else {
	    wi->dev_info.xg_seg(wi->dev_info.user_state,
				 1, segs, axisWidth,
				 L_AXIS, 0, 0);
	    if (tickFlag) {
		wi->dev_info.xg_seg(wi->dev_info.user_state,
				     1, &(segs[1]), axisWidth,
				     L_AXIS, 0, 0);
	    }
	}
    }

    Xincr = (wi->dev_info.axis_pad + (wi->dev_info.axis_width * 7)) * wi->XUnitsPerPixel;
    Xstart = initGrid(wi->UsrOrgX, Xincr, logXFlag);
    for (Xindex = Xstart;  Xindex < wi->UsrOppX;  Xindex = stepGrid()) {
	Xspot = SCREENX(wi, Xindex);
	/* Draw the grid line or tick marks */
	if (tickFlag) {
	    segs[0].x1 = segs[0].x2 = segs[1].x1 = segs[1].x2 = Xspot;
	    segs[0].y1 = wi->XOrgY;
	    segs[0].y2 = wi->XOrgY + wi->dev_info.tick_len;
	    segs[1].y1 = wi->XOppY - wi->dev_info.tick_len;
	    segs[1].y2 = wi->XOppY;
	} else {
	    segs[0].x1 = segs[0].x2 = Xspot;
	    segs[0].y1 = wi->XOrgY; segs[0].y2 = wi->XOppY;
	}
	if ((ABS(Xindex) < ZERO_THRES) && !logXFlag) {
	    wi->dev_info.xg_seg(wi->dev_info.user_state,
				 1, segs, zeroWidth, L_ZERO, 0, 0);
	    if (tickFlag) {
		wi->dev_info.xg_seg(wi->dev_info.user_state,
				     1, &(segs[1]), zeroWidth, L_ZERO, 0, 0);

	    }
	} else {
	    wi->dev_info.xg_seg(wi->dev_info.user_state,
				 1, segs, axisWidth, L_AXIS, 0, 0);
	    if (tickFlag) {
		wi->dev_info.xg_seg(wi->dev_info.user_state,
				     1, &(segs[1]), axisWidth, L_AXIS, 0, 0);
	    }
	}
    }
    /* Check to see if he wants a bounding box */
    if (bbFlag) {
	XSegment bb[4];

	/* Draw bounding box */
	bb[0].x1 = bb[0].x2 = bb[1].x1 = bb[3].x2 = wi->XOrgX;
	bb[0].y1 = bb[2].y2 = bb[3].y1 = bb[3].y2 = wi->XOrgY;
	bb[1].x2 = bb[2].x1 = bb[2].x2 = bb[3].x1 = wi->XOppX;
	bb[0].y2 = bb[1].y1 = bb[1].y2 = bb[2].y1 = wi->XOppY;
	wi->dev_info.xg_seg(wi->dev_info.user_state,
			     4, bb, axisWidth, L_AXIS, 0, 0);
    }
}

static double gridBase, gridStep, gridJuke[101];
static int gridNJuke, gridCurJuke;

#define ADD_GRID(val)	(gridJuke[gridNJuke++] = log10(val))

double initGrid(low, step, logFlag)
double low;			/* desired low value          */
double step;			/* desired step (user coords) */
int logFlag;			/* is axis logarithmic?       */
{
    double ratio, x;
    double RoundUp(), stepGrid();

    gridNJuke = gridCurJuke = 0;
    gridJuke[gridNJuke++] = 0.0;

    if (logFlag) {
	ratio = pow(10.0, step);
	gridBase = floor(low);
	gridStep = ceil(step);
	if (ratio <= 3.0) {
	    if (ratio > 2.0) {
		ADD_GRID(3.0);
	    } else if (ratio > 1.333) {
		ADD_GRID(2.0);	ADD_GRID(5.0);
	    } else if (ratio > 1.25) {
		ADD_GRID(1.5);	ADD_GRID(2.0);	ADD_GRID(3.0);
		ADD_GRID(5.0);	ADD_GRID(7.0);
	    } else {
		for (x = 1.0; x < 10.0 && (x+.5)/(x+.4) >= ratio; x += .5) {
		    ADD_GRID(x + .1);	ADD_GRID(x + .2);
		    ADD_GRID(x + .3);	ADD_GRID(x + .4);
		    ADD_GRID(x + .5);
		}
		if (floor(x) != x) ADD_GRID(x += .5);
		for ( ; x < 10.0 && (x+1.0)/(x+.5) >= ratio; x += 1.0) {
		    ADD_GRID(x + .5);	ADD_GRID(x + 1.0);
		}
		for ( ; x < 10.0 && (x+1.0)/x >= ratio; x += 1.0) {
		    ADD_GRID(x + 1.0);
		}
		if (x == 7.0) {
		    gridNJuke--;
		    x = 6.0;
		}
		if (x < 7.0) {
		    ADD_GRID(x + 2.0);
		}
		if (x == 10.0) gridNJuke--;
	    }
	    x = low - gridBase;
	    for (gridCurJuke = -1; x >= gridJuke[gridCurJuke+1]; gridCurJuke++){
	    }
	}
    } else {
	gridStep = RoundUp(step);
	gridBase = floor(low / gridStep) * gridStep;
    }
    return(stepGrid());
}

double stepGrid()
{
    if (++gridCurJuke >= gridNJuke) {
	gridCurJuke = 0;
	gridBase += gridStep;
    }
    return(gridBase + gridJuke[gridCurJuke]);
}

double RoundUp(val)
double val;			/* Value */
/*
 * This routine rounds up the given positive number such that
 * it is some power of ten times either 1, 2, or 5.  It is
 * used to find increments for grid lines.
 */
{
    int exponent, idx;

    exponent = (int) floor(nlog10(val));
    if (exponent < 0) {
	for (idx = exponent;  idx < 0; idx++) {
	    val *= 10.0;
	}
    } else {
	for (idx = 0;  idx < exponent; idx++) {
	    val /= 10.0;
	}
    }
    if (val > 5.0) val = 10.0;
    else if (val > 2.0) val = 5.0;
    else if (val > 1.0) val = 2.0;
    else val = 1.0;
    if (exponent < 0) {
	for (idx = exponent;  idx < 0;  idx++) {
	    val /= 10.0;
	}
    } else {
	for (idx = 0;  idx < exponent;  idx++) {
	    val *= 10.0;
	}
    }
    return val;
}

int WriteValue(str, val, exp, logFlag)
char *str;			/* String to write into */
double val;			/* Value to print       */
int exp;			/* Exponent             */
int logFlag;			/* Is this a log axis?  */
/*
 * Writes the value provided into the string in a fixed format
 * consisting of seven characters.  The format is:
 *   -ddd.dd
 */
{
    int idx;

    if (logFlag) {
	if (val == floor(val)) {
	    (void) sprintf(str, "%.0e", pow(10.0, val));
	} else {
	    (void) sprintf(str, "%.2g", pow(10.0, val - floor(val)));
	}
    } else {
	if (exp < 0) {
	    for (idx = exp;  idx < 0;  idx++) {
		val *= 10.0;
	    }
	} else {
	    for (idx = 0;  idx < exp;  idx++) {
		val /= 10.0;
	    }
	}
	(void) sprintf(str, "%.2f", val);
    }
}
	    
    

#define LEFT_CODE	0x01
#define RIGHT_CODE	0x02
#define BOTTOM_CODE	0x04
#define TOP_CODE	0x08

/* Clipping algorithm from Neumann and Sproull by Cohen and Sutherland */
#define C_CODE(xval, yval, rtn) \
rtn = 0; \
if ((xval) < wi->UsrOrgX) rtn = LEFT_CODE; \
else if ((xval) > wi->UsrOppX) rtn = RIGHT_CODE; \
if ((yval) < wi->UsrOrgY) rtn |= BOTTOM_CODE; \
else if ((yval) > wi->UsrOppY) rtn |= TOP_CODE

int DrawData(wi)
LocalWin *wi;
/*
 * This routine draws the data sets themselves using the macros
 * for translating coordinates.
 */
{
    double sx1, sy1, sx2, sy2, tx, ty;
    int idx, subindex;
    int code1, code2, cd, mark_inside;
    int X_idx;
    XSegment *ptr;

    for (idx = 0;  idx < MAXSETS;  idx++) {
	X_idx = 0;
	for (subindex = 0;  subindex < AllSets[idx].numPoints-1;  subindex++) {
	    /* Put segment in (sx1,sy1) (sx2,sy2) */
	    sx1 = AllSets[idx].xvec[subindex];
	    sy1 = AllSets[idx].yvec[subindex];
	    sx2 = AllSets[idx].xvec[subindex+1];
	    sy2 = AllSets[idx].yvec[subindex+1];
	    /* Now clip to current window boundary */
	    C_CODE(sx1, sy1, code1);
	    C_CODE(sx2, sy2, code2);
	    mark_inside = (code1 == 0);
	    while (code1 || code2) {
		if (code1 & code2) break;
		cd = (code1 ? code1 : code2);
		if (cd & LEFT_CODE) {	/* Crosses left edge */
		    ty = sy1 + (sy2 - sy1) * (wi->UsrOrgX - sx1) / (sx2 - sx1);
		    tx = wi->UsrOrgX;
		} else if (cd & RIGHT_CODE) { /* Crosses right edge */
		    ty = sy1 + (sy2 - sy1) * (wi->UsrOppX - sx1) / (sx2 - sx1);
		    tx = wi->UsrOppX;
		} else if (cd & BOTTOM_CODE) { /* Crosses bottom edge */
		    tx = sx1 + (sx2 - sx1) * (wi->UsrOrgY - sy1) / (sy2 - sy1);
		    ty = wi->UsrOrgY;
		} else if (cd & TOP_CODE) { /* Crosses top edge */
		    tx = sx1 + (sx2 - sx1) * (wi->UsrOppY - sy1) / (sy2 - sy1);
		    ty = wi->UsrOppY;
		}
		if (cd == code1) {
		    sx1 = tx;  sy1 = ty;
		    C_CODE(sx1, sy1, code1);
		} else {
		    sx2 = tx;  sy2 = ty;
		    C_CODE(sx2, sy2, code2);
		}
	    }
	    if (!code1 && !code2) {
		/* Add segment to list */
		Xsegs[X_idx].x1 = SCREENX(wi, sx1);
		Xsegs[X_idx].y1 = SCREENY(wi, sy1);
		Xsegs[X_idx].x2 = SCREENX(wi, sx2);
		Xsegs[X_idx].y2 = SCREENY(wi, sy2);
		X_idx++;
	    }

	    /* Draw markers if requested and they are in drawing region */
	    if (markFlag && mark_inside) {
		if (pixelMarks) {
		    if (bigPixel) {
			wi->dev_info.xg_dot(wi->dev_info.user_state,
					    Xsegs[X_idx-1].x1, Xsegs[X_idx-1].y1,
					    P_DOT, 0, idx % MAXATTR);
		    } else {
			wi->dev_info.xg_dot(wi->dev_info.user_state,
					    Xsegs[X_idx-1].x1, Xsegs[X_idx-1].y1,
					    P_PIXEL, 0, PIXVALUE(idx));
		    }
		} else {
		    /* Distinctive markers */
		    wi->dev_info.xg_dot(wi->dev_info.user_state,
					Xsegs[X_idx-1].x1, Xsegs[X_idx-1].y1,
					P_MARK, MARKSTYLE(idx),
					PIXVALUE(idx));
		}
	    }

	    /* Draw bar elements if requested */
	    if (barFlag) {
		int barPixels, baseSpot;
		XSegment line;

		barPixels = (int) ((barWidth / wi->XUnitsPerPixel) + 0.5);
		if (barPixels <= 0) barPixels = 1;
		baseSpot = SCREENY(wi, barBase);
		line.x1 = line.x2 = Xsegs[X_idx-1].x1;
		line.y1 = baseSpot;  line.y2 = Xsegs[X_idx-1].y1;
		wi->dev_info.xg_seg(wi->dev_info.user_state,
				    1, &line, barPixels, L_VAR,
				    LINESTYLE(idx), PIXVALUE(idx));
	    }
	}
	/* Handle last marker */
	if (markFlag && (AllSets[idx].numPoints > 0)) {
	    C_CODE(AllSets[idx].xvec[AllSets[idx].numPoints-1],
		   AllSets[idx].yvec[AllSets[idx].numPoints-1],
		   mark_inside);
	    if (mark_inside == 0) {
		if (pixelMarks) {
		    if (bigPixel) {
			wi->dev_info.xg_dot(wi->dev_info.user_state,
					    Xsegs[X_idx-1].x2, Xsegs[X_idx-1].y2,
					    P_DOT, 0, idx % MAXATTR);
		    } else {
			wi->dev_info.xg_dot(wi->dev_info.user_state,
					    Xsegs[X_idx-1].x2, Xsegs[X_idx-1].y2,
					    P_PIXEL, 0, PIXVALUE(idx));
		    }
		} else {
		    /* Distinctive markers */
		    wi->dev_info.xg_dot(wi->dev_info.user_state,
					Xsegs[X_idx-1].x2, Xsegs[X_idx-1].y2,
					P_MARK, MARKSTYLE(idx),
					PIXVALUE(idx));
		}
	    }
	}
	/* Handle last bar */
	if ((AllSets[idx].numPoints > 0) && barFlag) {
	    int barPixels, baseSpot;
	    XSegment line;

	    barPixels = (int) ((barWidth / wi->XUnitsPerPixel) + 0.5);
	    if (barPixels <= 0) barPixels = 1;
	    baseSpot = SCREENY(wi, barBase);
	    line.x1 = line.x2 = Xsegs[X_idx-1].x2;
	    line.y1 = baseSpot;  line.y2 = Xsegs[X_idx-1].y2;
	    wi->dev_info.xg_seg(wi->dev_info.user_state,
				1, &line, barPixels, L_VAR,
				LINESTYLE(idx), PIXVALUE(idx));
	}

	/* Draw segments */
	if (AllSets[idx].numPoints > 0 && (!noLines) && (X_idx > 0)) {
	    ptr = Xsegs;
	    while (X_idx > wi->dev_info.max_segs) {
		wi->dev_info.xg_seg(wi->dev_info.user_state,
				    wi->dev_info.max_segs, ptr,
				    lineWidth, L_VAR,
				    LINESTYLE(idx), PIXVALUE(idx));
		ptr += wi->dev_info.max_segs;
		X_idx -= wi->dev_info.max_segs;
	    }
	    wi->dev_info.xg_seg(wi->dev_info.user_state,
				 X_idx, ptr,
				 lineWidth, L_VAR,
				 LINESTYLE(idx), PIXVALUE(idx));
	}
    }
}
	    


int DrawLegend(wi)
LocalWin *wi;
/*
 * This draws a legend of the data sets displayed.  Only those that
 * will fit are drawn.
 */
{
    int idx, spot, lineLen, oneLen;
    XSegment leg_line;

    spot = wi->XOrgY;
    lineLen = 0;
    /* First pass draws the text */
    for (idx = 0;  idx < MAXSETS;  idx++) {
	if ((AllSets[idx].numPoints > 0) &&
	    (spot + wi->dev_info.axis_height + 2 < wi->XOppY))
	  {
	      /* Meets the criteria */
	      oneLen = strlen(AllSets[idx].setName);
	      if (oneLen > lineLen) lineLen = oneLen;
	      wi->dev_info.xg_text(wi->dev_info.user_state,
				   wi->XOppX + wi->dev_info.bdr_pad,
				   spot+2,
				   AllSets[idx].setName,
				   T_UPPERLEFT, T_AXIS);
	      spot += 2 + wi->dev_info.axis_height + wi->dev_info.bdr_pad;
	  }
    }
    lineLen = lineLen * wi->dev_info.axis_width;
    leg_line.x1 = wi->XOppX + wi->dev_info.bdr_pad;
    leg_line.x2 = leg_line.x1 + lineLen;
    spot = wi->XOrgY;
    /* second pass draws the lines */
    for (idx = 0;  idx < MAXSETS;  idx++) {
	if ((AllSets[idx].numPoints > 0) &&
	    (spot + wi->dev_info.axis_height + 2 < wi->XOppY))
	  {
	      leg_line.y1 = leg_line.y2 = spot - wi->dev_info.legend_pad;
	      wi->dev_info.xg_seg(wi->dev_info.user_state,
				  1, &leg_line, 1, L_VAR,
				  LINESTYLE(idx), PIXVALUE(idx));
	      if (markFlag && !pixelMarks) {
		  wi->dev_info.xg_dot(wi->dev_info.user_state,
				      leg_line.x1, leg_line.y1,
				      P_MARK, MARKSTYLE(idx), PIXVALUE(idx));
		  
	      }
	      spot += 2 + wi->dev_info.axis_height + wi->dev_info.bdr_pad;
	  }
    }
}



#define RND(val)	((int) ((val) + 0.5))

#ifdef TOOLBOX
/*ARGSUSED*/
void do_hardcopy(prog, info, init_fun, dev_spec, file_or_dev, maxdim,
		 ti_fam, ti_size, ax_fam, ax_size)
char *prog;			/* Program name for Xdefaults    */
char *info;			/* Some state information        */
int (*init_fun)();		/* Hardcopy init function        */
char *dev_spec;			/* Device specification (if any) */
char *file_or_dev;		/* Filename or device spec       */
double maxdim;			/* Maximum dimension in cm       */
char *ti_fam, *ax_fam;		/* Font family names             */
double ti_size, ax_size;	/* Font sizes in points          */
/*
 * This routine resets the function pointers to those specified
 * by `init_fun' and causes a screen redisplay.  If `dev_spec'
 * is non-zero,  it will be considered a sprintf string with
 * one %s which will be filled in with `file_or_dev' and fed
 * to popen(3) to obtain a stream.  Otherwise,  `file_or_dev'
 * is considered to be a file and is opened for writing.  The
 * resulting stream is fed to the initialization routine for
 * the device.
 */
{
    LocalWin *curWin = (LocalWin *) info;
    LocalWin thisWin;
    FILE *out_stream;
    char buf[MAXBUFSIZE], err[MAXBUFSIZE], ierr[ERRBUFSIZE];
    char tilde[MAXBUFSIZE*10];
    int final_w, final_h;
    double ratio;

    if (dev_spec) {
	(void) sprintf(buf, dev_spec, file_or_dev);
	out_stream = popen(buf, "w");
	if (!out_stream) {
#ifdef TOOLBOX	    
	    do_error(sprintf(err, "Unable to issue command:\n  %s\n", buf));
#else
	    (void) fprintf(stderr, "Unable to issue command:\n  %s\n", buf));
#endif
	    return;
	}
    } else {
	tildeExpand(tilde, file_or_dev);
	out_stream = fopen(tilde, "w");
	if (!out_stream) {
#ifdef TOOLBOX	    
	    do_error(sprintf(err, "Unable to open file `%s'\n", tilde));
#else
	    (void) fprintf(stderr, "Unable to open file `%s'\n", tilde);
#endif
	    return;
	}
    }
    thisWin = *curWin;
    ratio = ((double) thisWin.dev_info.area_w) /
      ((double) thisWin.dev_info.area_h);
    if (thisWin.dev_info.area_w > thisWin.dev_info.area_h) {
	final_w = RND(maxdim * 10000.0);
	final_h = RND(maxdim/ratio * 10000.0);
    } else {
	final_w = RND(maxdim * ratio * 10000.0);
	final_h = RND(maxdim * 10000.0);
    }
    ierr[0] = '\0';
    if ((*init_fun)(out_stream, final_w, final_h, ti_fam, ti_size,
		    ax_fam, ax_size, &(thisWin.dev_info), ierr)) {
	DrawWindow(&thisWin);
	if (thisWin.dev_info.xg_end) {
	    thisWin.dev_info.xg_end(thisWin.dev_info.user_state);
	}
    } else {
#ifdef TOOLBOX
	do_error(ierr);
#else
	(void) fprintf(stderr, "%s\n", ierr);
#endif
    }
    if (dev_spec) {
	(void) pclose(out_stream);
    } else {
	(void) fclose(out_stream);
    }
}
#endif


static char *tildeExpand(out, in)
char *out;			/* Output space for expanded file name */
char *in;			/* Filename with tilde                 */
/*
 * This routine expands out a file name passed in `in' and places
 * the expanded version in `out'.  It returns `out'.
 */
{
    char username[50], *userPntr;
    struct passwd *userRecord;

    out[0] = '\0';

    /* Skip over the white space in the initial path */
    while ((*in == ' ') || (*in == '\t')) in++;

    /* Tilde? */
    if (in[0] == TILDE) {
	/* Copy user name into 'username' */
	in++;  userPntr = &(username[0]);
	while ((*in != '\0') && (*in != '/')) {
	    *(userPntr++) = *(in++);
	}
	*(userPntr) = '\0';
	/* Look up user directory in passwd file */
	if ((userRecord = getpwnam(username)) != (struct passwd *) 0) {
	    /* Found user in passwd file.  Concatenate user directory */
	    strcat(out, userRecord->pw_dir);
	}
    }

    /* Concantenate remaining portion of file name */
    strcat(out, in);
    return out;
}



/*ARGSUSED*/
static int XErrHandler(disp, evt)
Display *disp;
XErrorEvent *evt;
/*
 * Displays a nicely formatted message and core dumps.
 */
{
    fprintf(stderr, "Fatal X Error: %s", ux11_error(evt));
    abort();
}



int stricmp(a, b)
register char *a, *b;
/*
 * This routine compares two strings disregarding case.
 */
{
    register int value;

    if ((a == (char *) 0) || (b == (char *) 0)) {
	return a - b;
    }

    for ( /* nothing */;
	 ((*a | *b) &&
	  !(value = ((isupper(*a) ? *a - 'A' + 'a' : *a) -
		     (isupper(*b) ? *b - 'A' + 'a' : *b))));
	 a++, b++)
      /* Empty Body */;

    return value;
}
