From: raines@bohr.physics.upenn.edu (Paul Raines)
Newsgroups: comp.lang.tcl
Subject: Addition xygraph features (long)
Keywords: graph
Date: 24 Nov 92 01:45:52 GMT

I have added a few addition features to xygraph:
	- a complete cross hair cursor
	- a ghost box for efficient zoom box making or
		object selection
	- an added option to the insert procedure to read
		data from a file

I have also changed xygraph so that it does not ignore points
outside the limits but draws as far as the border toward the 
point when in line mode.

Three files are included below:
	graph.diff - the diff file to patch graph.c
	test.tk	   - a simple tk program to test the new features
	test.spe   - a sample data file of short values

test.tk and test.spe must be in the same directory to work.  You
can change the location of pish at the top of test.tk for your site.

CROSS HAIR:
	pathName crosshair x-coord y-coord
   This procedure places a cross hair with the intersection at the
given screen coordinates.  Calling it with coordinates off the graph
window will erase the crosshair (preferably use -10 -10 or at least
negative numbers).  Using "crosshair off" will also work.  There are
still some cases where on a graph redraw, a crosshair will get left
on the screen. I am working on it.

GHOST BOX:
	pathname ghostbox x-coord1 y-coord1 x-coord2 y-coord2
   This procedure draws a dotted line box with the two given
points as opposite corners.  The box will stay there until the
graph is redrawn or ghostbox is called again with the same points.
No internal variables are kept as to the last position of the ghost
box so you have to explicitly erase it.  This allows your to draw
as many as you like in case you are using it to mark several selections
on the graph.

READ FILE:
	-bfile "filename datatype offset numpoints pttype ?xstart xstep?"
   This option to the insert procedure tells xygraph to get its values
from filenamea file with the specified structure.
	filename  - name of file to open
	datatype  - one of hist, xdata, ydata, xydata
	offset    - number of bytes to skip as a header
	numpoints - number of points to read from file
	pttype	  - one of short, long, float, double (how points stored)
	xstart	  - starting value for x (hist only)
	xstep	  - stepping value for x (hist only)

xstart and xstep must be specified for hist data and must not be for
the others.  The program will complain if xstep is zero.  The hist
data type reads the y-values from the file and creates the x-values
knowing xstart, xstep, and numpoints.  The actual storage is double
that of numpoints since it creates a stepping graph (see example).

The changes to graph.c were written on a SGI running IRIX 4.0.5 which
is SYS5.  I haven't made any attempts at portability so if you have
a change, send it to me.  I would also appreciate suggestions.

################################ graph.diff #################################
67,71d66
< #include <sys/types.h>	/* PAUL: needed for file I/O */
< #include <sys/stat.h>
< #include <fcntl.h>
< #include <unistd.h>
< 
92,102d86
< #define XDATA	0   /* PAUL: file read data type*/
< #define YDATA	1
< #define XYDATA	2
< #define HIST	3
< 
< #define D_SHORT	0   /* PAUL: file read point C-type*/
< #define D_LONG	1
< #define D_FLOAT	2
< #define D_DOUBLE 3
< 
< 
214,216d197
< /* PAUL: procedure to read data from file into widget*/
< static int FileVectorParseProc _ANSI_ARGS_((ClientData clientData, 
<  Tcl_Interp *interp, Tk_Window tkwin, char *value, char *widgRec, int offset));
218d198
< 
309,314d288
< /* PAUL: */
< static Tk_CustomOption FileVectorOption =
< {
<     FileVectorParseProc, TwinVectorPrintProc, NULL
< };
< 
370,372d343
< /* PAUL: */
<     {TK_CONFIG_CUSTOM, "-bfile", "bFile", (char *)NULL,
<      NULL, 0, XYGRAPH_MASK, &FileVectorOption},
537,539d507
<     GC compgc;		     /* PAUL: complement gc for crosshair */
<     int chx, chy;	     /* PAUL: old crosshair coordinates */
<     GC boxgc;		     /* PAUL: complement gc for box */
805,916d772
<  * Additional routines written by Paul Raines
<  * ---------------------------------------------------------------------- */
< /* PAUL -----------------------------------------------------------------
<  * move crosshair cursor
<  */
< static int
< MoveCrossHair(interp, graphPtr, screenX, screenY)
<     Tcl_Interp *interp;	     /* Interpreter */
<     Graph *graphPtr;	     /* Graph widget record */
<     char *screenX;	     /* String representing screen x coordinate */
<     char *screenY;	     /* String representing Screen y coordinate */
< {
<     Tk_Window tkwin = graphPtr->tkwin;
<     Drawable draw;
<     int sx, sy;		     /* Convert integer screen coordinates */
< 
<     if ((tkwin == NULL) || !Tk_IsMapped(tkwin))
< 	return;
< 	
<     /* Reset width/height */
<     graphPtr->width = Tk_Width(graphPtr->tkwin);
<     graphPtr->height = Tk_Height(graphPtr->tkwin);
<     draw = (Drawable) Tk_WindowId(tkwin);
<     
<     /* Get screen coords from string arguments and check */
<     if (Tcl_GetInt(interp, screenX, &sx) != TCL_OK ||
< 	Tcl_GetInt(interp, screenY, &sy) != TCL_OK) {
< 	return TCL_ERROR;
<     }
<     if (sx < 0 || sx > graphPtr->width ||
< 	sy < 0 || sy > graphPtr->height) {
< 	    sx = sy = -10;
<     }
< 
< /*
<  * remove old cross hairs (complement)....
<  */
<     if (graphPtr->chx != -10) {
<          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
< 	    graphPtr->chx,0,graphPtr->chx,graphPtr->height);
<          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
< 	    0,graphPtr->chy,graphPtr->width,graphPtr->chy);
< 	 }
< /*
<  * get new position of mouse pointer and save it in global variables ....
<  */
<          graphPtr->chx = sx;
< 	 graphPtr->chy = sy;
< /*
<  * draw new cross hairs (complement)....
<  */
<     if (graphPtr->chx != -10) {
<          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
< 	    graphPtr->chx,0,graphPtr->chx,graphPtr->height);
<          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
< 	    0,graphPtr->chy,graphPtr->width,graphPtr->chy);
< 	 }
<     return TCL_OK;
< }
< 
< /* PAUL -----------------------------------------------------------------
<  * draw ghost box
<  */
< static int
< DrawGhostBox(interp, graphPtr, screenX1, screenY1, screenX2, screenY2)
<     Tcl_Interp *interp;	     /* Interpreter to report results back to */
<     Graph *graphPtr;	     /* Graph widget record */
<     char *screenX1;	     /* String representing screen x coordinate */
<     char *screenY1;	     /* String representing Screen y coordinate */
<     char *screenX2;	     /* String representing screen x coordinate */
<     char *screenY2;	     /* String representing Screen y coordinate */
< {
<     Tk_Window tkwin = graphPtr->tkwin;
<     Drawable draw;
<     int sx1, sy1, sx2, sy2;  /* Convert integer screen coordinates */
< 
<     if ((tkwin == NULL) || !Tk_IsMapped(tkwin))
< 	return;
< 	
<     /* Reset width/height */
<     graphPtr->width = Tk_Width(graphPtr->tkwin);
<     graphPtr->height = Tk_Height(graphPtr->tkwin);
<     draw = (Drawable) Tk_WindowId(tkwin);
<     
<     if (Tcl_GetInt(interp, screenX1, &sx1) != TCL_OK ||
< 	Tcl_GetInt(interp, screenY1, &sy1) != TCL_OK ||
< 	Tcl_GetInt(interp, screenX2, &sx2) != TCL_OK ||
< 	Tcl_GetInt(interp, screenY2, &sy2) != TCL_OK) {
< 	return TCL_ERROR;
<     }
< 
<     /* don't draw outside window */
<     if (sx1 < 0) sx1 = 0;
<     else if (sx1 > graphPtr->width) sx1 = graphPtr->width;
<     if (sy1 < 0) sy1 = 0;
<     else if (sy1 > graphPtr->height) sy1 = graphPtr->height;
<     if (sx2 < 0) sx2 = 0;
<     else if (sx2 > graphPtr->width) sx2 = graphPtr->width;
<     if (sy2 < 0) sy2 = 0;
<     else if (sy2 > graphPtr->height) sy2 = graphPtr->height;
< 
< /*
<  * draw box (complement)....
<  */
<     XDrawRectangle(Tk_Display(tkwin),draw,graphPtr->boxgc, 
< 	   MIN(sx1, sx2), MIN(sy1, sy2),
< 	   (unsigned int) abs(sx2-sx1),(unsigned int) abs(sy2-sy1));
< 
<     return TCL_OK;
< }
< 
< /* ----------------------------------------------------------------------
1769,1781d1624
<  * PAUL-----------------------------------------------------------------
<  *
<  * FileVectorParseProc --
<  *
<  *	This procedure is like VectorParseProc except that it
<  *	interprets the list as a filename with type and data storage
<  *	info.  The minimum and maximum for both the X and Y vectors are
<  *	determined if present.
<  *
<  * Results:
<  *	The return value is a standard Tcl result.  The vectors are passed
<  *	back via the widget record (linePtr).
<  *
1783,2108d1625
<  */
< static int
< FileVectorParseProc(clientData, interp, tkwin, value, widgRec, offset)
<     ClientData clientData;   /* not used */
<     Tcl_Interp *interp;	     /* Interpreter to send results back to */
<     Tk_Window tkwin;	     /* not used */
<     char *value;	     /* Tcl list of expressions */
<     char *widgRec;	     /* Line information record */
<     int offset;		     /* not used */
< {
<     register int i;
<     register double *valuePtr;
<     register char *fvalPtr; /* used for accessing file buffer */
<     Line *linePtr = (Line *) widgRec;
<     int numExpr;
<     char **exprArr = NULL;
<     double *xValueArr = NULL, *yValueArr = NULL;
<     double min, max;
<     int firstMin, firstMax;
<     int arraySize;
< 
<     int  d_fid = 0; /* file handle */
<     char d_type;    /* type of data: hist, xdata, ydata, xydata */
<     long d_offset;  /* byte offset from start of file to data */
<     long d_numpts;  /* number of points to be read */
<     int d_bufsize;  /* buffer size in bytes to read from file */
<     char d_ptsize;  /* C-type for point: short,long,float,double */
<     char d_ptrstep = 1;	/* number of bytes for stepping through buffer */
<     double d_xstart, d_xstep; /* for hist, how x values chosen */
<     int length;	    /* length of string */
<     char *fValueArr = NULL; /* pointer to start of file buffer */
< 
<     /* Split the bfile list and check the values */
<     if (Tcl_SplitList(interp, value, &numExpr, &exprArr) != TCL_OK) {
< 	return TCL_ERROR;
<     }
<     /* need either 5 or 7 (for hist) arguments */
<     if (numExpr < 5) {
< 	interp->result = "Bad bfile list. Need { filename type offset numpts \
< ptsize ?xstart? ?xstep? }";
< 	goto error;
<     } else if (numExpr > 7) {
< 	interp->result = "Bad bfile list. Need { filename type offset numpts \
< ptsize ?xstart? ?xstep? }";
< 	goto error;
<     }
< 
<     /* get the byte offset and number of points */
<     if (Tcl_ExprLong(interp, exprArr[2], &d_offset) == TCL_ERROR ||
< 	d_offset < 0 ) {
< 	interp->result = "Bad byte offset in bfile list";
< 	goto error;
<     }
<     if (Tcl_ExprLong(interp, exprArr[3], &d_numpts) == TCL_ERROR ||
< 	d_numpts < 1) {
< 	interp->result = "Bad point number in bfile list";
< 	goto error;
<     }
<     d_bufsize = (int) d_numpts; /* file buffer size set to number points */
<     
<     /* get type of data */
<     length = strlen(exprArr[1]);
<     if ((strncmp(exprArr[1], "xdata", length) == 0) &&
< 	(numExpr == 5)) d_type = XDATA;
<     else if ((strncmp(exprArr[1], "ydata", length) == 0) &&
< 	(numExpr == 5)) d_type = YDATA;
<     else if ((strncmp(exprArr[1], "xydata", length) == 0) &&
< 	(numExpr == 5)) {
< 	d_type = XYDATA;
< 	d_bufsize *= 2;	/* need to read in double number of points */
< 	d_ptrstep = 2; /* need to skip over x's to get y's, vice versa */
< 	}
<     else if ((strncmp(exprArr[1], "hist", length) == 0) &&
< 	(numExpr == 7)) d_type = HIST;
<     else {
< 	interp->result = "Bad data type in bfile list or wrong number \
< of arguments for specified type";
< 	goto error;
<     }
< 
<     /* get x-value start and step if hist type */
<     if (d_type == HIST) {
< 	if (Tcl_ExprDouble(interp, exprArr[5], &d_xstart) == TCL_ERROR) {
< 	    interp->result = "Bad xstart in bfile list";
< 	    goto error;
< 	}
< 	if (Tcl_ExprDouble(interp, exprArr[6], &d_xstep) == TCL_ERROR || 
< 	    d_xstep == 0.0) {
< 	    interp->result = "Bad xstep in bfile list";
< 	    goto error;
< 	}
< 	/* need to store each point twice for histogram stepping */
< 	arraySize = (int) d_numpts * 2; 
<     } else
< 	arraySize = (int) d_numpts;
< 	
<     /* get C-type of points to be read and set pointer stepping, buffer */
<     length = strlen(exprArr[4]);
< 
<     if (strncmp(exprArr[4], "short", length) == 0) {
< 	 d_ptsize = D_SHORT;
< 	 d_ptrstep *= sizeof(short);
< 	 d_bufsize *= sizeof(short);
< 	 }
<     else if (strncmp(exprArr[4], "long", length) == 0) {
< 	 d_ptsize = D_LONG;
< 	 d_ptrstep *= sizeof(long);
< 	 d_bufsize *= sizeof(long);
< 	 }
<     else if (strncmp(exprArr[4], "float", length) == 0) {
< 	 d_ptsize = D_FLOAT;
< 	 d_ptrstep *= sizeof(float);
< 	 d_bufsize *= sizeof(float);
< 	 }
<     else if (strncmp(exprArr[4], "double", length) == 0) {
< 	 d_ptsize = D_DOUBLE;
< 	 d_ptrstep *= sizeof(double);
< 	 d_bufsize *= sizeof(double);
< 	 }
<     else {
< 	interp->result = "Bad data point size in bfile list";
< 	goto error;
<     }
<     
<     /* Allocate an array of char to hold the values from file */
<     fValueArr = (char *)ckalloc(d_bufsize);
<     if (fValueArr == NULL) {
< 	Tcl_AppendResult(interp, "Can't allocate bfile buffer for ",
< 			 linePtr->name, (char *)NULL);
< 	Tcl_SetErrorCode(interp, "UNIX", "malloc", sys_errlist[errno], NULL);
< 	goto error;
<     }
< 
<     /* Open file for reading */
<     if ((d_fid = open(exprArr[0], O_RDONLY)) == -1) {
< 	perror(interp->result) ;
< 	interp->result = "Error opening in -bfile";
< 	goto error;
< 	}
<     /* Read past header to data */
<     if((length = read(d_fid, fValueArr, (unsigned) d_offset)) == -1) {
< 	perror(interp->result);
< 	interp->result = "Error reading in -bfile";
< 	goto error;
< 	}
<     /* Check if at end of file */
<     if ( length < (int) d_offset) {
< 	interp->result = "Offset past end of file in -bfile";
< 	goto error;
< 	} 
<     /* Read data */
<     if((length = read(d_fid, fValueArr, (unsigned) d_bufsize)) == -1) {
< 	perror(interp->result);
< 	interp->result = "Error reading data of -bfile";
< 	goto error;
< 	}
<     /* Check if past end of file */
<     if ( length < d_bufsize) {
< 	interp->result = "Read past end of file for data in -bfile";
< 	goto error;
< 	} 
<     close(d_fid);
<     
<     /* get the x values from the file */
<     if (d_type != YDATA && d_type != HIST) {
< 	/* Allocate an array of doubles to hold the values */
< 	xValueArr = (double *)ckalloc(arraySize * sizeof(double));
<     
< 	if (xValueArr == NULL) {
< 	    Tcl_AppendResult(interp, "Can't allocate X vector for ",
< 			     linePtr->name, (char *)NULL);
< 	    Tcl_SetErrorCode(interp, "UNIX", "malloc", sys_errlist[errno], NULL);
< 	    goto error;
< 	}
< 	if (d_type != HIST) {
< 	    
< 	    /* translate file values to doubles and store in graph record */
< 	    firstMin = firstMax = TRUE;
< 	    min = max = 0.0;
< 	    fvalPtr = fValueArr;
< 	    for (valuePtr = xValueArr, i = 0; i < d_numpts; i++, valuePtr++) {
< 		
< 		/* convert to double */
< 		switch(d_ptsize) {
< 		case D_SHORT:
< 		    *valuePtr = (double) ( *( (short *)fvalPtr ) );
< 		    break;
< 		case D_LONG:
< 		    *valuePtr = (double) ( *( (long *)fvalPtr ) );
< 		    break;
< 		case D_FLOAT:
< 		    *valuePtr = (double) ( *( (float *)fvalPtr ) );
< 		    break;
< 		case D_DOUBLE:
< 		    /* don't real have to do this, but ... */
< 		    *valuePtr =  *( (double *)fvalPtr );
< 		    break;
< 		default:
< 		    interp->result = "SERIOUS error: ptsize lost in -bfile";
< 		    goto error;
< 		}
< 		fvalPtr += d_ptrstep; /* go to next x value */
< 		    
< 		if (*valuePtr != NegativeInfinity && *valuePtr != PositiveInfinity) {
< 		    if (firstMin) {
< 			min = *valuePtr;
< 			firstMin = FALSE;
< 		    } else if (*valuePtr < min) {
< 			min = *valuePtr;
< 		    }
< 		    if (firstMax) {
< 			max = *valuePtr;
< 			firstMax = FALSE;
< 		    } else if (*valuePtr > max) {
< 			max = *valuePtr;
< 		    }
< 		}
< 	    }
< 	}
< 	/* else if d_type == HIST */    
< 	else {
< 	    /* create x-values from xstart and xstep */
< 	    valuePtr = xValueArr;
< 	    for (i = 0; i < d_numpts; i++) {
< 		*valuePtr = d_xstart + i * d_xstep;
< 		valuePtr++;
< 		*valuePtr = d_xstart + (i+1) * d_xstep;
< 		valuePtr++;
< 	    }    
< 	    min = d_xstart;
< 	    max = d_xstart + (i-1) * d_xstep;
< 	}
< 	
< 	/* Save array and limits */
< 	linePtr->x.minValue = min;
< 	linePtr->x.maxValue = max;
< 	linePtr->x.numValues = arraySize;
< 	if (linePtr->x.valueArr != NULL)
< 	    free((char *)linePtr->x.valueArr);
< 	linePtr->x.valueArr = xValueArr;
<     }
<     
<     /* get the y values from the file */
<     if (d_type != XDATA) {
< 	yValueArr = (double *)ckalloc(arraySize * sizeof(double));
<     
< 	if (yValueArr == NULL) {
< 	    Tcl_AppendResult(interp, "Can't allocate Y vector for ",
< 			     linePtr->name, (char *)NULL);
< 	    Tcl_SetErrorCode(interp, "UNIX", "malloc", sys_errlist[errno], NULL);
< 	    goto error;
< 	}
< 	
< 	/* Now translate the Y values to doubles and store */
< 	firstMin = firstMax = TRUE;
< 	min = max = 0.0;
< 	fvalPtr = fValueArr;
< 	if (d_type == XYDATA) fvalPtr += (d_ptrstep/2); /* skip past first x value */
< 	for (valuePtr = yValueArr, i = 0; i < d_numpts; i++, valuePtr++) {
<     
< 	    
< 	    if (d_ptsize == D_SHORT) {
< 		*valuePtr = (double) ( *( (short *)fvalPtr ) );
< 	    } else if (d_ptsize == D_LONG) {
< 		*valuePtr = (double) ( *( (long *)fvalPtr ) );
< 	    } else if (d_ptsize == D_FLOAT) {
< 		*valuePtr = (double) ( *( (float *)fvalPtr ) );
< 	    } else if (d_ptsize == D_DOUBLE) {
< 		*valuePtr =  *( (double *)fvalPtr );
< 	    } else {
< 		interp->result = "SERIOUS error: ptsize lost";
< 		goto error;
< 	    }
< 	    fvalPtr += d_ptrstep;
< 	    if (d_type == HIST) { /* need to double points for histogram */
< 		d_xstep = *valuePtr;
< 		valuePtr++;
< 		*valuePtr = d_xstep;
< 	    }
< 	    
< 	    if (*valuePtr != NegativeInfinity && *valuePtr != PositiveInfinity) {
< 		if (firstMin) {
< 		    min = *valuePtr;
< 		    firstMin = FALSE;
< 		} else if (*valuePtr < min) {
< 		    min = *valuePtr;
< 		}
< 		if (firstMax) {
< 		    max = *valuePtr;
< 		    firstMax = FALSE;
< 		} else if (*valuePtr > max) {
< 		    max = *valuePtr;
< 		}
< 	    }
< 	}
< 	
< 	/* Save array and limits */
< 	linePtr->y.minValue = min;
< 	linePtr->y.maxValue = max;
< 	linePtr->y.numValues = arraySize;
< 	if (linePtr->y.valueArr != NULL)
< 	    free((char *)linePtr->y.valueArr);
< 	linePtr->y.valueArr = yValueArr;
<     }
<   
< 
<     interp->result = "";
<     ckfree((char *)exprArr);
<     ckfree((char *)fValueArr);
<     return TCL_OK;
<   error:
<     /* Clean up, release allocated storage */
<     if (exprArr)
< 	ckfree((char *)exprArr);
<     if (xValueArr)
< 	ckfree((char *)xValueArr);
<     if (yValueArr)
< 	ckfree((char *)yValueArr);
<     if (fValueArr)
< 	ckfree((char *)fValueArr);
<     if (d_fid != 0) close(d_fid);
<     return TCL_ERROR;
< }
< 
< /*
<  *----------------------------------------------------------------------
2893,2894c2410
< 	graphPtr->chx = -10; /* PAUL: signal that crosshair cursor */
<     }			     /*       has been erased from screen  */
---
>     }
4127,4132c3643
<     
< /* need to keep track of last y position and will calculate slope */
<     double lastx, lasty, slope, sx, sy;
< /* need to no if last point was out of bounds */
<     char outbounds;
<     
---
>     double lastx;
4158,4159d3668
< 	lasty = 0.0;
< 	outbounds = FALSE;
4168,4199c3677,3679
< 	    if (y < 0.0 || y > 1.0) {
< 		/* Point is out-of-bounds, check if in line mode */
< 		if (LINESTYLE(linePtr->symbol)) {
< 		    /* In line mode, check if already out of bounds */
< 		    if (!(n == 0 || outbounds)) {
< 		    /* not already out of bounds, draw to edge */
< 			slope = (y - lasty) / (x - lastx) ;
< 			if ( y < 0.0 ) sy = 0.0;
< 			else sy = 1.0;
< 			sx = (sy - lasty)/slope + lastx ;
< 			ptArr[numPoints++] = Gr_Point(graphPtr, sx, sy);
< 			XDrawLines(Tk_Display(tkwin), draw, linePtr->gc,
< 			       ptArr, numPoints, CoordModeOrigin);
< 			numPoints = 0;
< 		    }
< 		    outbounds = TRUE;
< 		    lastx = x;
< 		    lasty = y;
< 		    continue;
< 		} else
< 		    /* not in line mode so skip it */
< 		    continue;
< 	    } else if (outbounds && (LINESTYLE(linePtr->symbol)) ) {
< 		/* Point isn't out of bounds, but last one was */
< 		slope = (y - lasty) / (x - lastx) ;
< 		if ( lasty < 0.0 ) lasty = 0.0;
< 		else lasty = 1.0;
< 		lastx = x - (y - lasty)/slope ;
< 		ptArr[numPoints++] = Gr_Point(graphPtr, lastx, lasty);
< 		outbounds = FALSE;
< 	    }
< 	    
---
> 	    if (y < 0.0 || y > 1.0)
> 		continue;
> 
4209d3688
< 		lasty = y;
5073,5076d4551
< /* PAUL: needed for clipping fix*/    
<     double slope, sx, sy;
<     char outbounds;
<     
5089c4564
< 	double lastx, lasty; /* PAUL: need lasty also for clipping fix */
---
> 	double lastx;
5095,5096d4569
< 	lasty = 0.0; /* PAUL: */
< 	outbounds = FALSE;
5113,5147c4586,4587
< 	    if (y < 0.0 || y > 1.0) {
< 		/* PAUL: Point is out-of-bounds, check if in line mode */
< 		if (LINESTYLE(linePtr->symbol)) {
< 		    /* In line mode, check if already out of bounds */
< 		    if (!(n == 0 || outbounds)) {
< 		    /* not already out of bounds, draw to edge */
< 			slope = (y - lasty) / (x - lastx) ;
< 			if ( y < 0.0 ) sy = 0.0;
< 			else sy = 1.0;
< 			sx = (sy - lasty)/slope + lastx ;
< 			x1 = GX(graphPtr, sx), y1 = GY(graphPtr, sy);
< 			fprintf(f, "%d %d lineto\n", x1, y1);
< 			fprintf(f, "stroke\n%d %d moveto\n", x1, y1);
< 			numPoints = 0;
< 		    }
< 		    outbounds = TRUE;
< 		    lastx = x;
< 		    lasty = y;
< 		    continue;
< 		} else
< 		    /* not in line mode so skip it */
< 		    continue;
< 	    } else if (outbounds && (LINESTYLE(linePtr->symbol)) ) {
< 		/* Point isn't out of bounds, but last one was */
< 		slope = (y - lasty) / (x - lastx) ;
< 		if ( lasty < 0.0 ) lasty = 0.0;
< 		else lasty = 1.0;
< 		lastx = x - (y - lasty)/slope ;
< 		x1 = GX(graphPtr, lastx), y1 = GY(graphPtr, lasty);
< 		fprintf(f, "%d %d moveto\n", x1, y1);
< 		fprintf(f, "%d %d lineto\n", x1, y1);
< 		numPoints++;
< 		outbounds = FALSE;
< 	    }
< 	    
---
> 	    if (y < 0.0 || y > 1.0)
> 		continue;
5161d4600
< 		lasty = y;
5880d5318
<     unsigned char dotted[2];
5933,5959d5370
<     /* PAUL: Create graph crosshair GC */
<     gcValues.function = GXinvert;
<     gcValues.line_width = 1;
<     gcValues.line_style = LineSolid;
<     gcValues.cap_style = CapButt;
<     gcValues.join_style = JoinMiter;
<     valueMask = (GCFunction | GCLineWidth | GCLineStyle);
<     newGC = Tk_GetGC(graphPtr->tkwin, valueMask, &gcValues);
<     if (graphPtr->compgc != None)
< 	Tk_FreeGC(graphPtr->compgc);
<     graphPtr->compgc = newGC;
<     graphPtr->chx = graphPtr->chy = -10;
< 
<     /* PAUL: Create zoom box GC */
<     dotted[0] = dotted[1] = 2;
<     gcValues.function = GXinvert;
<     gcValues.line_width = 1;
<     gcValues.line_style = LineOnOffDash;
<     gcValues.cap_style = CapButt;
<     gcValues.join_style = JoinMiter;
<     valueMask = (GCFunction | GCLineStyle | GCLineWidth);
<     newGC = Tk_GetGC(graphPtr->tkwin, valueMask, &gcValues);
<     XSetDashes(Tk_Display(graphPtr->tkwin), newGC, 0, dotted, 2);
<     if (graphPtr->boxgc != None)
< 	Tk_FreeGC(graphPtr->boxgc);
<     graphPtr->boxgc = newGC;
< 
6199,6216d5609
<     } else if ((c == 'c') && (strncmp(argv[1], "crosshair", length) == 0)) {
< 	if (argc == 3 && (strncmp(argv[2], "off", length) == 0)) {
< 	    result = MoveCrossHair(interp, graphPtr, "-10", "-10");
< 	} else if (argc == 4) {
< 	    result = MoveCrossHair(interp, graphPtr, argv[2], argv[3]);
< 	} else {
< 	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
< 			     " crosshair x y\"", NULL);
< 	    goto error;
< 	}
<     } else if ((c == 'g') && (strncmp(argv[1], "ghostbox", length) == 0)) {
< 	if (argc != 6) {
< 	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
< 			     " ghostbox x1 y1 x2 y2\"", NULL);
< 	    goto error;
< 	}
< 	result = DrawGhostBox(interp, graphPtr, argv[2], argv[3], 
< 		    argv[4], argv[5]);
6220,6221c5613
< insert, limits, locate, newtag, postscript, show, crosshair, ghostbox, \
< tagconfigure, or untag ", 
---
> insert, limits, locate, newtag, postscript, show, tagconfigure, or untag ", 
6326d5717
< 
 
############################## test.tk ################################
#!./pish -f
option add *XYGraph.Geometry 700x400

######################################################################
# zoom utilities
######################################################################
global zx; global zy
global oldx; global oldy
proc zoomstart { w sx sy } {
    global zx; global zy
    global oldx; global oldy

    $w crosshair off
    set oldx $sx
    set oldy $sy
    set zx $sx
    set zy $sy
    $w ghostbox $zx $zy $oldx $oldy

}
proc zoomchange { w sx sy } {
    global zx; global zy
    global oldx; global oldy

    $w ghostbox $zx $zy $oldx $oldy
    $w ghostbox $zx $zy $sx $sy
    set oldx $sx
    set oldy $sy
}
proc zoomstop { w sx sy} {
    global zx; global zy
    global oldx; global oldy
    $w ghostbox $zx $zy $oldx $oldy
    scan [$w locate $sx $sy ] "%s %s" oldx oldy
    scan [$w locate $zx $zy ] "%s %s" zx zy
    scan [$w limits ] "%s %s %s %s" xmin xmax ymin ymax
    if { $zx < $oldx } { swap zx oldx }
    if { $zy < $oldy } { swap zy oldy }
    if { $zx > $xmax } { set zx $xmax }
    if { $oldx < $xmin } { set oldx $xmin }
    if { $zy > $ymax } { set zy $ymax }
    if { $oldy < $ymin } { set oldy $ymin }
    $w configure -xmin $oldx -xmax $zx -ymin $oldy -ymax $zy
}
proc swap { var1 var2 } {
  global $var1 $var2
  set hold [set $var1]
  set $var1 [set $var2]
  set $var2 $hold
}

frame .top
xygraph .top.graph -title "Another XY Graph" \
        -xlabel "X Axis Label" -ylabel "Y Axis Label" \
        -showlegend false
.top.graph insert line1 -bfile {test.spe hist 36 2000 short 0 1} -color blue

label .top.info -anchor w -width 10 -relief raised -background bisque3
.top.info configure -text "BUTTON: (1) Zoom  (2) See all  (3) Quit"

pack append .top .top.info {bottom fillx} .top.graph {top fill}
pack append . .top {top fill}
 .top.graph configure -xmin 100 -xmax 500
 .top.graph configure -ymin 0 -ymax 1000

bind .top.graph <Leave> { %W crosshair -10 -10 }
bind .top.graph <Motion> { %W crosshair %x %y }

bind .top.graph <ButtonPress-2> { 
  %W config -xmin {} -ymin {} -xmax {} -ymax {}
}

bind .top.graph <ButtonPress-3> {exit}

bind .top.graph <ButtonPress-1> {zoomstart %W %x %y}
bind .top.graph <B1-Motion> {zoomchange %W %x %y}
bind .top.graph <ButtonRelease-1> {zoomstop %W %x %y}

########################### test.spe.uu ###############################
begin 644 test.spe
M````&$=-050@,2XP,"``````````````````````````````````````````
M``````````````````````````````````````````````````````$````%
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
M`!X`%``3`!<`$@`E`"(`$``@``L`&P`:`!4`"P`;`!4`+``Z`$<`:0![`(``
M70`V`"L`(``8`!``$``6`!@`%``I`"P`.0!M`(L`E`!J`%(`.0`1`!X`&``<
M``@`"@`/`!8`&0`.`"0`%0`A`!<`(``J`!4`'@`6`!0`$0`0`!(`'@`9`!0`
M#P`5``P`&P`.`!8`$``2`!\`&``C`"``%``:``H`#@`8`"P`+``D`!L`&``>
M`!<`%0`3``\`%``.``D``@`5`!0`%@`-`!``"@`'``X`$@`6`!H`%``'``\`
M'P`<`!4`%P`4`!@`&@`B`!(`%0`7``X`"@`(`!(`#P`0`!<`&0`9``\`#``@
M`!T`$``,`!T`(@`/`!X`'P`4``@`#@`9``D`%P`7`!T`%P`9`!T`'``A`"D`
M+0`F`!L`$P`4`!\`$P`-``T`#``2``L`&P`2`!0`%@`3`!``#0`*``X`"@`+
M``P`#@`4`!4`$0`8`!8`%P`5``T`$@`4`!0`#@`0``T`!``1``\`$P`(`!,`
M%@`(``8`$P`!``H`!``&``0`"@`'``0`#P`1``@`%``2`!8`#P`%``T`%``1
M`!,`%P`%`!$`!``!`!(`#0`.`!4`$P`6`!0`"0`0`!4`$0`,``X`$``#``T`
M%@`0`!(`"P`5`!$`#P`:`"$`$P`3``\`#``4``L`#@`/`!$``@`*`!$`$``0
M`!$`$P`)`!<`$``0`!,`&``,``D`%``/````#@`/`!(`%``*``@`#``(``H`
M#P`+``@`!0`'``<`#0`*``8`#0`,`!<`$0`2``P`"0`&``T`"``'``\`"P`9
M``\`&0`)``\`!@`=`!0`#``/``0`#P`1``0`#0`,`!$`$@`*``D`"``5``8`
M"@`*``D``P`-``H`#P`8`!<`(0`+`!@`%0`+``L`!P`,``L`$@`(`!D`#P`4
M``P`&0`:`!``#0`-``T`"P`&`!,``P`0``T`#P`*``P`"``.``T`%``-``(`
M#@`*`!0`%``(`!0`#``4`!@`$@`!``T`#0`(`!,`"0`+`!$`"``2``P`"P`2
M``T`$``)``@`%P`)`!``$@`)`!``"@`'``T`"0`)``H`#0`,``8`#0`.``\`
M'0`/``@`#P`0`!,`$0`*``H`#P`'``@`#@`$`!``#@`!``D`!@`,``D`!@`/
M`!0`#`````4`&``)`!$`$P`)``4`"0`+``<`"``%``P`"0`)``L`!@`$``<`
M%0`$`!0`"``&``$``@`,``8`"P`:``T`&@`3``P`%``0``D`!P`*`!``"0`#
M``D`"``/``<`!``.``X`%P`'``L`$``)``@`"``"``L`"@````D`%@`(``4`
M!``+``P`!0`(`!```P`%``P`"0`,``D`"0`%``L`"P`-``@`"0`)``8`#``$
M``H`!@`,``@`!``*``@`!@`*``,`!0`(``T`#0`(``P````)``D`#0`!`!$`
M$0`&``D`#0`+``P``@`'``@`!0`(``(``0````<`!``'``H`!0````4````#
M``\`"``"``H`#0`%``L`"``'``8`!@`#``0`!``)__T``@`&``8`#0`-``<`
M"``.``8`"P`)``(````,``H`"0`(``,`#0`0``P`"0`4`!<`'P`,`!8`'P`*
M``D``@`#``H`!P`#``0`!0`'``4`!0`&``4``P`,``<`!0`8`!$`%``C`",`
M)0`D`"(`&P`.`!D``P`*``0`!@`+``4`$0`'``@`$0`,``4`!P`&``@`!@`'
M``$`"0`)``,``0`#``4``@`,``L````)``D`"``"`````@`"``$`!``'``8`
M`P`%``4``P````@`!@`'``@`"0`&``4``P`'``@`!0`)``(``P`%``D`"``'
M``<``@`'``$``0`&````!@`&``4`!P`#``4``@`&``@`!``"``<`!@`"``H`
M`0`(``8`!0`$``+__P`#``4`!``!``(`"@`+``,``0`$``$``0`$``D`"``#
M``8`!P`#``0``P`"``$`!0`#````"`````(``@`(``0`!@`$``$`!0`&``$`
M!``$``8`"0`$``$`!@`(``<``P````(`"@````+__P`(``(`!``!```````&
M``,`!P`&``(`!``#__X`!@`'``8``0`#``0`!``#``(``___````!``$``@`
M!P`"``0`!P````4``@`%``$`!``"``$``@`+``<`!`````,````%``0`!@`&
M``0``0`&``4`!`````$````#``<`!0`#``(`!``"``(``P``````!P`$``0`
M`0`$````!P`$````!P`!``(`!P`$``,``0````$`!P`#``0``0`%``,`"/__
M``(`!P`!``8`!``$``0``@`"`````0`#``$````!``$``@`$``@`!P`#``$`
M`P`#``8``@`"``,`!0`%``4`!P`(``8````#``4``O_^``,`!0`#``$````"
M``(`!``!``,`!@`!``(``0`#``(`!``'`````0`"`````@`"``,`!``#``0`
M`0`"``#__P`!``(````!``(``P`#``,````%``,`"``#``<``0`"``,``P`!
M``+__@`"``(````#``,````"````!``'``,`!``$``(`"/_^``(``0`#``,`
M`0```````0`"``(`!O__``0`!0`#```````!``$``@`!``(``P`!`````0`!
M``,````!``(``0`$``,````'``0```````$``@`$``8``0`!``$``@`#``0`
C```%````!``"``(``0`"``0`!`````,````$``$`````#\@!
`
end

____________________________________________________________________
Paul Raines	raines@bohr.physics.upenn.edu	215-898-8832
Dept. of Physics
University of Pennsylvania