home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #27 / NN_1992_27.iso / spool / comp / lang / tcl / 1930 < prev    next >
Encoding:
Internet Message Format  |  1992-11-23  |  32.0 KB

  1. Path: sparky!uunet!cs.utexas.edu!zaphod.mps.ohio-state.edu!darwin.sura.net!jvnc.net!netnews.upenn.edu!bohr.physics.upenn.edu!raines
  2. From: raines@bohr.physics.upenn.edu (Paul Raines)
  3. Newsgroups: comp.lang.tcl
  4. Subject: Addition xygraph features (long)
  5. Keywords: graph
  6. Message-ID: <98997@netnews.upenn.edu>
  7. Date: 24 Nov 92 01:45:52 GMT
  8. Sender: news@netnews.upenn.edu
  9. Lines: 898
  10. Nntp-Posting-Host: bohr.physics.upenn.edu
  11.  
  12. I have added a few addition features to xygraph:
  13.     - a complete cross hair cursor
  14.     - a ghost box for efficient zoom box making or
  15.         object selection
  16.     - an added option to the insert procedure to read
  17.         data from a file
  18.  
  19. I have also changed xygraph so that it does not ignore points
  20. outside the limits but draws as far as the border toward the 
  21. point when in line mode.
  22.  
  23. Three files are included below:
  24.     graph.diff - the diff file to patch graph.c
  25.     test.tk       - a simple tk program to test the new features
  26.     test.spe   - a sample data file of short values
  27.  
  28. test.tk and test.spe must be in the same directory to work.  You
  29. can change the location of pish at the top of test.tk for your site.
  30.  
  31. CROSS HAIR:
  32.     pathName crosshair x-coord y-coord
  33.    This procedure places a cross hair with the intersection at the
  34. given screen coordinates.  Calling it with coordinates off the graph
  35. window will erase the crosshair (preferably use -10 -10 or at least
  36. negative numbers).  Using "crosshair off" will also work.  There are
  37. still some cases where on a graph redraw, a crosshair will get left
  38. on the screen. I am working on it.
  39.  
  40. GHOST BOX:
  41.     pathname ghostbox x-coord1 y-coord1 x-coord2 y-coord2
  42.    This procedure draws a dotted line box with the two given
  43. points as opposite corners.  The box will stay there until the
  44. graph is redrawn or ghostbox is called again with the same points.
  45. No internal variables are kept as to the last position of the ghost
  46. box so you have to explicitly erase it.  This allows your to draw
  47. as many as you like in case you are using it to mark several selections
  48. on the graph.
  49.  
  50. READ FILE:
  51.     -bfile "filename datatype offset numpoints pttype ?xstart xstep?"
  52.    This option to the insert procedure tells xygraph to get its values
  53. from filenamea file with the specified structure.
  54.     filename  - name of file to open
  55.     datatype  - one of hist, xdata, ydata, xydata
  56.     offset    - number of bytes to skip as a header
  57.     numpoints - number of points to read from file
  58.     pttype      - one of short, long, float, double (how points stored)
  59.     xstart      - starting value for x (hist only)
  60.     xstep      - stepping value for x (hist only)
  61.  
  62. xstart and xstep must be specified for hist data and must not be for
  63. the others.  The program will complain if xstep is zero.  The hist
  64. data type reads the y-values from the file and creates the x-values
  65. knowing xstart, xstep, and numpoints.  The actual storage is double
  66. that of numpoints since it creates a stepping graph (see example).
  67.  
  68. The changes to graph.c were written on a SGI running IRIX 4.0.5 which
  69. is SYS5.  I haven't made any attempts at portability so if you have
  70. a change, send it to me.  I would also appreciate suggestions.
  71.  
  72. ################################ graph.diff #################################
  73. 67,71d66
  74. < #include <sys/types.h>    /* PAUL: needed for file I/O */
  75. < #include <sys/stat.h>
  76. < #include <fcntl.h>
  77. < #include <unistd.h>
  78. 92,102d86
  79. < #define XDATA    0   /* PAUL: file read data type*/
  80. < #define YDATA    1
  81. < #define XYDATA    2
  82. < #define HIST    3
  83. < #define D_SHORT    0   /* PAUL: file read point C-type*/
  84. < #define D_LONG    1
  85. < #define D_FLOAT    2
  86. < #define D_DOUBLE 3
  87. 214,216d197
  88. < /* PAUL: procedure to read data from file into widget*/
  89. < static int FileVectorParseProc _ANSI_ARGS_((ClientData clientData, 
  90. <  Tcl_Interp *interp, Tk_Window tkwin, char *value, char *widgRec, int offset));
  91. 218d198
  92. 309,314d288
  93. < /* PAUL: */
  94. < static Tk_CustomOption FileVectorOption =
  95. < {
  96. <     FileVectorParseProc, TwinVectorPrintProc, NULL
  97. < };
  98. 370,372d343
  99. < /* PAUL: */
  100. <     {TK_CONFIG_CUSTOM, "-bfile", "bFile", (char *)NULL,
  101. <      NULL, 0, XYGRAPH_MASK, &FileVectorOption},
  102. 537,539d507
  103. <     GC compgc;             /* PAUL: complement gc for crosshair */
  104. <     int chx, chy;         /* PAUL: old crosshair coordinates */
  105. <     GC boxgc;             /* PAUL: complement gc for box */
  106. 805,916d772
  107. <  * Additional routines written by Paul Raines
  108. <  * ---------------------------------------------------------------------- */
  109. < /* PAUL -----------------------------------------------------------------
  110. <  * move crosshair cursor
  111. <  */
  112. < static int
  113. < MoveCrossHair(interp, graphPtr, screenX, screenY)
  114. <     Tcl_Interp *interp;         /* Interpreter */
  115. <     Graph *graphPtr;         /* Graph widget record */
  116. <     char *screenX;         /* String representing screen x coordinate */
  117. <     char *screenY;         /* String representing Screen y coordinate */
  118. < {
  119. <     Tk_Window tkwin = graphPtr->tkwin;
  120. <     Drawable draw;
  121. <     int sx, sy;             /* Convert integer screen coordinates */
  122. <     if ((tkwin == NULL) || !Tk_IsMapped(tkwin))
  123. <     return;
  124. <     
  125. <     /* Reset width/height */
  126. <     graphPtr->width = Tk_Width(graphPtr->tkwin);
  127. <     graphPtr->height = Tk_Height(graphPtr->tkwin);
  128. <     draw = (Drawable) Tk_WindowId(tkwin);
  129. <     
  130. <     /* Get screen coords from string arguments and check */
  131. <     if (Tcl_GetInt(interp, screenX, &sx) != TCL_OK ||
  132. <     Tcl_GetInt(interp, screenY, &sy) != TCL_OK) {
  133. <     return TCL_ERROR;
  134. <     }
  135. <     if (sx < 0 || sx > graphPtr->width ||
  136. <     sy < 0 || sy > graphPtr->height) {
  137. <         sx = sy = -10;
  138. <     }
  139. < /*
  140. <  * remove old cross hairs (complement)....
  141. <  */
  142. <     if (graphPtr->chx != -10) {
  143. <          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
  144. <         graphPtr->chx,0,graphPtr->chx,graphPtr->height);
  145. <          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
  146. <         0,graphPtr->chy,graphPtr->width,graphPtr->chy);
  147. <      }
  148. < /*
  149. <  * get new position of mouse pointer and save it in global variables ....
  150. <  */
  151. <          graphPtr->chx = sx;
  152. <      graphPtr->chy = sy;
  153. < /*
  154. <  * draw new cross hairs (complement)....
  155. <  */
  156. <     if (graphPtr->chx != -10) {
  157. <          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
  158. <         graphPtr->chx,0,graphPtr->chx,graphPtr->height);
  159. <          XDrawLine(Tk_Display(tkwin),draw,graphPtr->compgc,
  160. <         0,graphPtr->chy,graphPtr->width,graphPtr->chy);
  161. <      }
  162. <     return TCL_OK;
  163. < }
  164. < /* PAUL -----------------------------------------------------------------
  165. <  * draw ghost box
  166. <  */
  167. < static int
  168. < DrawGhostBox(interp, graphPtr, screenX1, screenY1, screenX2, screenY2)
  169. <     Tcl_Interp *interp;         /* Interpreter to report results back to */
  170. <     Graph *graphPtr;         /* Graph widget record */
  171. <     char *screenX1;         /* String representing screen x coordinate */
  172. <     char *screenY1;         /* String representing Screen y coordinate */
  173. <     char *screenX2;         /* String representing screen x coordinate */
  174. <     char *screenY2;         /* String representing Screen y coordinate */
  175. < {
  176. <     Tk_Window tkwin = graphPtr->tkwin;
  177. <     Drawable draw;
  178. <     int sx1, sy1, sx2, sy2;  /* Convert integer screen coordinates */
  179. <     if ((tkwin == NULL) || !Tk_IsMapped(tkwin))
  180. <     return;
  181. <     
  182. <     /* Reset width/height */
  183. <     graphPtr->width = Tk_Width(graphPtr->tkwin);
  184. <     graphPtr->height = Tk_Height(graphPtr->tkwin);
  185. <     draw = (Drawable) Tk_WindowId(tkwin);
  186. <     
  187. <     if (Tcl_GetInt(interp, screenX1, &sx1) != TCL_OK ||
  188. <     Tcl_GetInt(interp, screenY1, &sy1) != TCL_OK ||
  189. <     Tcl_GetInt(interp, screenX2, &sx2) != TCL_OK ||
  190. <     Tcl_GetInt(interp, screenY2, &sy2) != TCL_OK) {
  191. <     return TCL_ERROR;
  192. <     }
  193. <     /* don't draw outside window */
  194. <     if (sx1 < 0) sx1 = 0;
  195. <     else if (sx1 > graphPtr->width) sx1 = graphPtr->width;
  196. <     if (sy1 < 0) sy1 = 0;
  197. <     else if (sy1 > graphPtr->height) sy1 = graphPtr->height;
  198. <     if (sx2 < 0) sx2 = 0;
  199. <     else if (sx2 > graphPtr->width) sx2 = graphPtr->width;
  200. <     if (sy2 < 0) sy2 = 0;
  201. <     else if (sy2 > graphPtr->height) sy2 = graphPtr->height;
  202. < /*
  203. <  * draw box (complement)....
  204. <  */
  205. <     XDrawRectangle(Tk_Display(tkwin),draw,graphPtr->boxgc, 
  206. <        MIN(sx1, sx2), MIN(sy1, sy2),
  207. <        (unsigned int) abs(sx2-sx1),(unsigned int) abs(sy2-sy1));
  208. <     return TCL_OK;
  209. < }
  210. < /* ----------------------------------------------------------------------
  211. 1769,1781d1624
  212. <  * PAUL-----------------------------------------------------------------
  213. <  *
  214. <  * FileVectorParseProc --
  215. <  *
  216. <  *    This procedure is like VectorParseProc except that it
  217. <  *    interprets the list as a filename with type and data storage
  218. <  *    info.  The minimum and maximum for both the X and Y vectors are
  219. <  *    determined if present.
  220. <  *
  221. <  * Results:
  222. <  *    The return value is a standard Tcl result.  The vectors are passed
  223. <  *    back via the widget record (linePtr).
  224. <  *
  225. 1783,2108d1625
  226. <  */
  227. < static int
  228. < FileVectorParseProc(clientData, interp, tkwin, value, widgRec, offset)
  229. <     ClientData clientData;   /* not used */
  230. <     Tcl_Interp *interp;         /* Interpreter to send results back to */
  231. <     Tk_Window tkwin;         /* not used */
  232. <     char *value;         /* Tcl list of expressions */
  233. <     char *widgRec;         /* Line information record */
  234. <     int offset;             /* not used */
  235. < {
  236. <     register int i;
  237. <     register double *valuePtr;
  238. <     register char *fvalPtr; /* used for accessing file buffer */
  239. <     Line *linePtr = (Line *) widgRec;
  240. <     int numExpr;
  241. <     char **exprArr = NULL;
  242. <     double *xValueArr = NULL, *yValueArr = NULL;
  243. <     double min, max;
  244. <     int firstMin, firstMax;
  245. <     int arraySize;
  246. <     int  d_fid = 0; /* file handle */
  247. <     char d_type;    /* type of data: hist, xdata, ydata, xydata */
  248. <     long d_offset;  /* byte offset from start of file to data */
  249. <     long d_numpts;  /* number of points to be read */
  250. <     int d_bufsize;  /* buffer size in bytes to read from file */
  251. <     char d_ptsize;  /* C-type for point: short,long,float,double */
  252. <     char d_ptrstep = 1;    /* number of bytes for stepping through buffer */
  253. <     double d_xstart, d_xstep; /* for hist, how x values chosen */
  254. <     int length;        /* length of string */
  255. <     char *fValueArr = NULL; /* pointer to start of file buffer */
  256. <     /* Split the bfile list and check the values */
  257. <     if (Tcl_SplitList(interp, value, &numExpr, &exprArr) != TCL_OK) {
  258. <     return TCL_ERROR;
  259. <     }
  260. <     /* need either 5 or 7 (for hist) arguments */
  261. <     if (numExpr < 5) {
  262. <     interp->result = "Bad bfile list. Need { filename type offset numpts \
  263. < ptsize ?xstart? ?xstep? }";
  264. <     goto error;
  265. <     } else if (numExpr > 7) {
  266. <     interp->result = "Bad bfile list. Need { filename type offset numpts \
  267. < ptsize ?xstart? ?xstep? }";
  268. <     goto error;
  269. <     }
  270. <     /* get the byte offset and number of points */
  271. <     if (Tcl_ExprLong(interp, exprArr[2], &d_offset) == TCL_ERROR ||
  272. <     d_offset < 0 ) {
  273. <     interp->result = "Bad byte offset in bfile list";
  274. <     goto error;
  275. <     }
  276. <     if (Tcl_ExprLong(interp, exprArr[3], &d_numpts) == TCL_ERROR ||
  277. <     d_numpts < 1) {
  278. <     interp->result = "Bad point number in bfile list";
  279. <     goto error;
  280. <     }
  281. <     d_bufsize = (int) d_numpts; /* file buffer size set to number points */
  282. <     
  283. <     /* get type of data */
  284. <     length = strlen(exprArr[1]);
  285. <     if ((strncmp(exprArr[1], "xdata", length) == 0) &&
  286. <     (numExpr == 5)) d_type = XDATA;
  287. <     else if ((strncmp(exprArr[1], "ydata", length) == 0) &&
  288. <     (numExpr == 5)) d_type = YDATA;
  289. <     else if ((strncmp(exprArr[1], "xydata", length) == 0) &&
  290. <     (numExpr == 5)) {
  291. <     d_type = XYDATA;
  292. <     d_bufsize *= 2;    /* need to read in double number of points */
  293. <     d_ptrstep = 2; /* need to skip over x's to get y's, vice versa */
  294. <     }
  295. <     else if ((strncmp(exprArr[1], "hist", length) == 0) &&
  296. <     (numExpr == 7)) d_type = HIST;
  297. <     else {
  298. <     interp->result = "Bad data type in bfile list or wrong number \
  299. < of arguments for specified type";
  300. <     goto error;
  301. <     }
  302. <     /* get x-value start and step if hist type */
  303. <     if (d_type == HIST) {
  304. <     if (Tcl_ExprDouble(interp, exprArr[5], &d_xstart) == TCL_ERROR) {
  305. <         interp->result = "Bad xstart in bfile list";
  306. <         goto error;
  307. <     }
  308. <     if (Tcl_ExprDouble(interp, exprArr[6], &d_xstep) == TCL_ERROR || 
  309. <         d_xstep == 0.0) {
  310. <         interp->result = "Bad xstep in bfile list";
  311. <         goto error;
  312. <     }
  313. <     /* need to store each point twice for histogram stepping */
  314. <     arraySize = (int) d_numpts * 2; 
  315. <     } else
  316. <     arraySize = (int) d_numpts;
  317. <     
  318. <     /* get C-type of points to be read and set pointer stepping, buffer */
  319. <     length = strlen(exprArr[4]);
  320. <     if (strncmp(exprArr[4], "short", length) == 0) {
  321. <      d_ptsize = D_SHORT;
  322. <      d_ptrstep *= sizeof(short);
  323. <      d_bufsize *= sizeof(short);
  324. <      }
  325. <     else if (strncmp(exprArr[4], "long", length) == 0) {
  326. <      d_ptsize = D_LONG;
  327. <      d_ptrstep *= sizeof(long);
  328. <      d_bufsize *= sizeof(long);
  329. <      }
  330. <     else if (strncmp(exprArr[4], "float", length) == 0) {
  331. <      d_ptsize = D_FLOAT;
  332. <      d_ptrstep *= sizeof(float);
  333. <      d_bufsize *= sizeof(float);
  334. <      }
  335. <     else if (strncmp(exprArr[4], "double", length) == 0) {
  336. <      d_ptsize = D_DOUBLE;
  337. <      d_ptrstep *= sizeof(double);
  338. <      d_bufsize *= sizeof(double);
  339. <      }
  340. <     else {
  341. <     interp->result = "Bad data point size in bfile list";
  342. <     goto error;
  343. <     }
  344. <     
  345. <     /* Allocate an array of char to hold the values from file */
  346. <     fValueArr = (char *)ckalloc(d_bufsize);
  347. <     if (fValueArr == NULL) {
  348. <     Tcl_AppendResult(interp, "Can't allocate bfile buffer for ",
  349. <              linePtr->name, (char *)NULL);
  350. <     Tcl_SetErrorCode(interp, "UNIX", "malloc", sys_errlist[errno], NULL);
  351. <     goto error;
  352. <     }
  353. <     /* Open file for reading */
  354. <     if ((d_fid = open(exprArr[0], O_RDONLY)) == -1) {
  355. <     perror(interp->result) ;
  356. <     interp->result = "Error opening in -bfile";
  357. <     goto error;
  358. <     }
  359. <     /* Read past header to data */
  360. <     if((length = read(d_fid, fValueArr, (unsigned) d_offset)) == -1) {
  361. <     perror(interp->result);
  362. <     interp->result = "Error reading in -bfile";
  363. <     goto error;
  364. <     }
  365. <     /* Check if at end of file */
  366. <     if ( length < (int) d_offset) {
  367. <     interp->result = "Offset past end of file in -bfile";
  368. <     goto error;
  369. <     } 
  370. <     /* Read data */
  371. <     if((length = read(d_fid, fValueArr, (unsigned) d_bufsize)) == -1) {
  372. <     perror(interp->result);
  373. <     interp->result = "Error reading data of -bfile";
  374. <     goto error;
  375. <     }
  376. <     /* Check if past end of file */
  377. <     if ( length < d_bufsize) {
  378. <     interp->result = "Read past end of file for data in -bfile";
  379. <     goto error;
  380. <     } 
  381. <     close(d_fid);
  382. <     
  383. <     /* get the x values from the file */
  384. <     if (d_type != YDATA && d_type != HIST) {
  385. <     /* Allocate an array of doubles to hold the values */
  386. <     xValueArr = (double *)ckalloc(arraySize * sizeof(double));
  387. <     
  388. <     if (xValueArr == NULL) {
  389. <         Tcl_AppendResult(interp, "Can't allocate X vector for ",
  390. <                  linePtr->name, (char *)NULL);
  391. <         Tcl_SetErrorCode(interp, "UNIX", "malloc", sys_errlist[errno], NULL);
  392. <         goto error;
  393. <     }
  394. <     if (d_type != HIST) {
  395. <         
  396. <         /* translate file values to doubles and store in graph record */
  397. <         firstMin = firstMax = TRUE;
  398. <         min = max = 0.0;
  399. <         fvalPtr = fValueArr;
  400. <         for (valuePtr = xValueArr, i = 0; i < d_numpts; i++, valuePtr++) {
  401. <         
  402. <         /* convert to double */
  403. <         switch(d_ptsize) {
  404. <         case D_SHORT:
  405. <             *valuePtr = (double) ( *( (short *)fvalPtr ) );
  406. <             break;
  407. <         case D_LONG:
  408. <             *valuePtr = (double) ( *( (long *)fvalPtr ) );
  409. <             break;
  410. <         case D_FLOAT:
  411. <             *valuePtr = (double) ( *( (float *)fvalPtr ) );
  412. <             break;
  413. <         case D_DOUBLE:
  414. <             /* don't real have to do this, but ... */
  415. <             *valuePtr =  *( (double *)fvalPtr );
  416. <             break;
  417. <         default:
  418. <             interp->result = "SERIOUS error: ptsize lost in -bfile";
  419. <             goto error;
  420. <         }
  421. <         fvalPtr += d_ptrstep; /* go to next x value */
  422. <             
  423. <         if (*valuePtr != NegativeInfinity && *valuePtr != PositiveInfinity) {
  424. <             if (firstMin) {
  425. <             min = *valuePtr;
  426. <             firstMin = FALSE;
  427. <             } else if (*valuePtr < min) {
  428. <             min = *valuePtr;
  429. <             }
  430. <             if (firstMax) {
  431. <             max = *valuePtr;
  432. <             firstMax = FALSE;
  433. <             } else if (*valuePtr > max) {
  434. <             max = *valuePtr;
  435. <             }
  436. <         }
  437. <         }
  438. <     }
  439. <     /* else if d_type == HIST */    
  440. <     else {
  441. <         /* create x-values from xstart and xstep */
  442. <         valuePtr = xValueArr;
  443. <         for (i = 0; i < d_numpts; i++) {
  444. <         *valuePtr = d_xstart + i * d_xstep;
  445. <         valuePtr++;
  446. <         *valuePtr = d_xstart + (i+1) * d_xstep;
  447. <         valuePtr++;
  448. <         }    
  449. <         min = d_xstart;
  450. <         max = d_xstart + (i-1) * d_xstep;
  451. <     }
  452. <     
  453. <     /* Save array and limits */
  454. <     linePtr->x.minValue = min;
  455. <     linePtr->x.maxValue = max;
  456. <     linePtr->x.numValues = arraySize;
  457. <     if (linePtr->x.valueArr != NULL)
  458. <         free((char *)linePtr->x.valueArr);
  459. <     linePtr->x.valueArr = xValueArr;
  460. <     }
  461. <     
  462. <     /* get the y values from the file */
  463. <     if (d_type != XDATA) {
  464. <     yValueArr = (double *)ckalloc(arraySize * sizeof(double));
  465. <     
  466. <     if (yValueArr == NULL) {
  467. <         Tcl_AppendResult(interp, "Can't allocate Y vector for ",
  468. <                  linePtr->name, (char *)NULL);
  469. <         Tcl_SetErrorCode(interp, "UNIX", "malloc", sys_errlist[errno], NULL);
  470. <         goto error;
  471. <     }
  472. <     
  473. <     /* Now translate the Y values to doubles and store */
  474. <     firstMin = firstMax = TRUE;
  475. <     min = max = 0.0;
  476. <     fvalPtr = fValueArr;
  477. <     if (d_type == XYDATA) fvalPtr += (d_ptrstep/2); /* skip past first x value */
  478. <     for (valuePtr = yValueArr, i = 0; i < d_numpts; i++, valuePtr++) {
  479. <     
  480. <         
  481. <         if (d_ptsize == D_SHORT) {
  482. <         *valuePtr = (double) ( *( (short *)fvalPtr ) );
  483. <         } else if (d_ptsize == D_LONG) {
  484. <         *valuePtr = (double) ( *( (long *)fvalPtr ) );
  485. <         } else if (d_ptsize == D_FLOAT) {
  486. <         *valuePtr = (double) ( *( (float *)fvalPtr ) );
  487. <         } else if (d_ptsize == D_DOUBLE) {
  488. <         *valuePtr =  *( (double *)fvalPtr );
  489. <         } else {
  490. <         interp->result = "SERIOUS error: ptsize lost";
  491. <         goto error;
  492. <         }
  493. <         fvalPtr += d_ptrstep;
  494. <         if (d_type == HIST) { /* need to double points for histogram */
  495. <         d_xstep = *valuePtr;
  496. <         valuePtr++;
  497. <         *valuePtr = d_xstep;
  498. <         }
  499. <         
  500. <         if (*valuePtr != NegativeInfinity && *valuePtr != PositiveInfinity) {
  501. <         if (firstMin) {
  502. <             min = *valuePtr;
  503. <             firstMin = FALSE;
  504. <         } else if (*valuePtr < min) {
  505. <             min = *valuePtr;
  506. <         }
  507. <         if (firstMax) {
  508. <             max = *valuePtr;
  509. <             firstMax = FALSE;
  510. <         } else if (*valuePtr > max) {
  511. <             max = *valuePtr;
  512. <         }
  513. <         }
  514. <     }
  515. <     
  516. <     /* Save array and limits */
  517. <     linePtr->y.minValue = min;
  518. <     linePtr->y.maxValue = max;
  519. <     linePtr->y.numValues = arraySize;
  520. <     if (linePtr->y.valueArr != NULL)
  521. <         free((char *)linePtr->y.valueArr);
  522. <     linePtr->y.valueArr = yValueArr;
  523. <     }
  524. <   
  525. <     interp->result = "";
  526. <     ckfree((char *)exprArr);
  527. <     ckfree((char *)fValueArr);
  528. <     return TCL_OK;
  529. <   error:
  530. <     /* Clean up, release allocated storage */
  531. <     if (exprArr)
  532. <     ckfree((char *)exprArr);
  533. <     if (xValueArr)
  534. <     ckfree((char *)xValueArr);
  535. <     if (yValueArr)
  536. <     ckfree((char *)yValueArr);
  537. <     if (fValueArr)
  538. <     ckfree((char *)fValueArr);
  539. <     if (d_fid != 0) close(d_fid);
  540. <     return TCL_ERROR;
  541. < }
  542. < /*
  543. <  *----------------------------------------------------------------------
  544. 2893,2894c2410
  545. <     graphPtr->chx = -10; /* PAUL: signal that crosshair cursor */
  546. <     }                 /*       has been erased from screen  */
  547. ---
  548. >     }
  549. 4127,4132c3643
  550. <     
  551. < /* need to keep track of last y position and will calculate slope */
  552. <     double lastx, lasty, slope, sx, sy;
  553. < /* need to no if last point was out of bounds */
  554. <     char outbounds;
  555. <     
  556. ---
  557. >     double lastx;
  558. 4158,4159d3668
  559. <     lasty = 0.0;
  560. <     outbounds = FALSE;
  561. 4168,4199c3677,3679
  562. <         if (y < 0.0 || y > 1.0) {
  563. <         /* Point is out-of-bounds, check if in line mode */
  564. <         if (LINESTYLE(linePtr->symbol)) {
  565. <             /* In line mode, check if already out of bounds */
  566. <             if (!(n == 0 || outbounds)) {
  567. <             /* not already out of bounds, draw to edge */
  568. <             slope = (y - lasty) / (x - lastx) ;
  569. <             if ( y < 0.0 ) sy = 0.0;
  570. <             else sy = 1.0;
  571. <             sx = (sy - lasty)/slope + lastx ;
  572. <             ptArr[numPoints++] = Gr_Point(graphPtr, sx, sy);
  573. <             XDrawLines(Tk_Display(tkwin), draw, linePtr->gc,
  574. <                    ptArr, numPoints, CoordModeOrigin);
  575. <             numPoints = 0;
  576. <             }
  577. <             outbounds = TRUE;
  578. <             lastx = x;
  579. <             lasty = y;
  580. <             continue;
  581. <         } else
  582. <             /* not in line mode so skip it */
  583. <             continue;
  584. <         } else if (outbounds && (LINESTYLE(linePtr->symbol)) ) {
  585. <         /* Point isn't out of bounds, but last one was */
  586. <         slope = (y - lasty) / (x - lastx) ;
  587. <         if ( lasty < 0.0 ) lasty = 0.0;
  588. <         else lasty = 1.0;
  589. <         lastx = x - (y - lasty)/slope ;
  590. <         ptArr[numPoints++] = Gr_Point(graphPtr, lastx, lasty);
  591. <         outbounds = FALSE;
  592. <         }
  593. <         
  594. ---
  595. >         if (y < 0.0 || y > 1.0)
  596. >         continue;
  597. 4209d3688
  598. <         lasty = y;
  599. 5073,5076d4551
  600. < /* PAUL: needed for clipping fix*/    
  601. <     double slope, sx, sy;
  602. <     char outbounds;
  603. <     
  604. 5089c4564
  605. <     double lastx, lasty; /* PAUL: need lasty also for clipping fix */
  606. ---
  607. >     double lastx;
  608. 5095,5096d4569
  609. <     lasty = 0.0; /* PAUL: */
  610. <     outbounds = FALSE;
  611. 5113,5147c4586,4587
  612. <         if (y < 0.0 || y > 1.0) {
  613. <         /* PAUL: Point is out-of-bounds, check if in line mode */
  614. <         if (LINESTYLE(linePtr->symbol)) {
  615. <             /* In line mode, check if already out of bounds */
  616. <             if (!(n == 0 || outbounds)) {
  617. <             /* not already out of bounds, draw to edge */
  618. <             slope = (y - lasty) / (x - lastx) ;
  619. <             if ( y < 0.0 ) sy = 0.0;
  620. <             else sy = 1.0;
  621. <             sx = (sy - lasty)/slope + lastx ;
  622. <             x1 = GX(graphPtr, sx), y1 = GY(graphPtr, sy);
  623. <             fprintf(f, "%d %d lineto\n", x1, y1);
  624. <             fprintf(f, "stroke\n%d %d moveto\n", x1, y1);
  625. <             numPoints = 0;
  626. <             }
  627. <             outbounds = TRUE;
  628. <             lastx = x;
  629. <             lasty = y;
  630. <             continue;
  631. <         } else
  632. <             /* not in line mode so skip it */
  633. <             continue;
  634. <         } else if (outbounds && (LINESTYLE(linePtr->symbol)) ) {
  635. <         /* Point isn't out of bounds, but last one was */
  636. <         slope = (y - lasty) / (x - lastx) ;
  637. <         if ( lasty < 0.0 ) lasty = 0.0;
  638. <         else lasty = 1.0;
  639. <         lastx = x - (y - lasty)/slope ;
  640. <         x1 = GX(graphPtr, lastx), y1 = GY(graphPtr, lasty);
  641. <         fprintf(f, "%d %d moveto\n", x1, y1);
  642. <         fprintf(f, "%d %d lineto\n", x1, y1);
  643. <         numPoints++;
  644. <         outbounds = FALSE;
  645. <         }
  646. <         
  647. ---
  648. >         if (y < 0.0 || y > 1.0)
  649. >         continue;
  650. 5161d4600
  651. <         lasty = y;
  652. 5880d5318
  653. <     unsigned char dotted[2];
  654. 5933,5959d5370
  655. <     /* PAUL: Create graph crosshair GC */
  656. <     gcValues.function = GXinvert;
  657. <     gcValues.line_width = 1;
  658. <     gcValues.line_style = LineSolid;
  659. <     gcValues.cap_style = CapButt;
  660. <     gcValues.join_style = JoinMiter;
  661. <     valueMask = (GCFunction | GCLineWidth | GCLineStyle);
  662. <     newGC = Tk_GetGC(graphPtr->tkwin, valueMask, &gcValues);
  663. <     if (graphPtr->compgc != None)
  664. <     Tk_FreeGC(graphPtr->compgc);
  665. <     graphPtr->compgc = newGC;
  666. <     graphPtr->chx = graphPtr->chy = -10;
  667. <     /* PAUL: Create zoom box GC */
  668. <     dotted[0] = dotted[1] = 2;
  669. <     gcValues.function = GXinvert;
  670. <     gcValues.line_width = 1;
  671. <     gcValues.line_style = LineOnOffDash;
  672. <     gcValues.cap_style = CapButt;
  673. <     gcValues.join_style = JoinMiter;
  674. <     valueMask = (GCFunction | GCLineStyle | GCLineWidth);
  675. <     newGC = Tk_GetGC(graphPtr->tkwin, valueMask, &gcValues);
  676. <     XSetDashes(Tk_Display(graphPtr->tkwin), newGC, 0, dotted, 2);
  677. <     if (graphPtr->boxgc != None)
  678. <     Tk_FreeGC(graphPtr->boxgc);
  679. <     graphPtr->boxgc = newGC;
  680. 6199,6216d5609
  681. <     } else if ((c == 'c') && (strncmp(argv[1], "crosshair", length) == 0)) {
  682. <     if (argc == 3 && (strncmp(argv[2], "off", length) == 0)) {
  683. <         result = MoveCrossHair(interp, graphPtr, "-10", "-10");
  684. <     } else if (argc == 4) {
  685. <         result = MoveCrossHair(interp, graphPtr, argv[2], argv[3]);
  686. <     } else {
  687. <         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  688. <                  " crosshair x y\"", NULL);
  689. <         goto error;
  690. <     }
  691. <     } else if ((c == 'g') && (strncmp(argv[1], "ghostbox", length) == 0)) {
  692. <     if (argc != 6) {
  693. <         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  694. <                  " ghostbox x1 y1 x2 y2\"", NULL);
  695. <         goto error;
  696. <     }
  697. <     result = DrawGhostBox(interp, graphPtr, argv[2], argv[3], 
  698. <             argv[4], argv[5]);
  699. 6220,6221c5613
  700. < insert, limits, locate, newtag, postscript, show, crosshair, ghostbox, \
  701. < tagconfigure, or untag ", 
  702. ---
  703. > insert, limits, locate, newtag, postscript, show, tagconfigure, or untag ", 
  704. 6326d5717
  705.  
  706. ############################## test.tk ################################
  707. #!./pish -f
  708. option add *XYGraph.Geometry 700x400
  709.  
  710. ######################################################################
  711. # zoom utilities
  712. ######################################################################
  713. global zx; global zy
  714. global oldx; global oldy
  715. proc zoomstart { w sx sy } {
  716.     global zx; global zy
  717.     global oldx; global oldy
  718.  
  719.     $w crosshair off
  720.     set oldx $sx
  721.     set oldy $sy
  722.     set zx $sx
  723.     set zy $sy
  724.     $w ghostbox $zx $zy $oldx $oldy
  725.  
  726. }
  727. proc zoomchange { w sx sy } {
  728.     global zx; global zy
  729.     global oldx; global oldy
  730.  
  731.     $w ghostbox $zx $zy $oldx $oldy
  732.     $w ghostbox $zx $zy $sx $sy
  733.     set oldx $sx
  734.     set oldy $sy
  735. }
  736. proc zoomstop { w sx sy} {
  737.     global zx; global zy
  738.     global oldx; global oldy
  739.     $w ghostbox $zx $zy $oldx $oldy
  740.     scan [$w locate $sx $sy ] "%s %s" oldx oldy
  741.     scan [$w locate $zx $zy ] "%s %s" zx zy
  742.     scan [$w limits ] "%s %s %s %s" xmin xmax ymin ymax
  743.     if { $zx < $oldx } { swap zx oldx }
  744.     if { $zy < $oldy } { swap zy oldy }
  745.     if { $zx > $xmax } { set zx $xmax }
  746.     if { $oldx < $xmin } { set oldx $xmin }
  747.     if { $zy > $ymax } { set zy $ymax }
  748.     if { $oldy < $ymin } { set oldy $ymin }
  749.     $w configure -xmin $oldx -xmax $zx -ymin $oldy -ymax $zy
  750. }
  751. proc swap { var1 var2 } {
  752.   global $var1 $var2
  753.   set hold [set $var1]
  754.   set $var1 [set $var2]
  755.   set $var2 $hold
  756. }
  757.  
  758. frame .top
  759. xygraph .top.graph -title "Another XY Graph" \
  760.         -xlabel "X Axis Label" -ylabel "Y Axis Label" \
  761.         -showlegend false
  762. .top.graph insert line1 -bfile {test.spe hist 36 2000 short 0 1} -color blue
  763.  
  764. label .top.info -anchor w -width 10 -relief raised -background bisque3
  765. .top.info configure -text "BUTTON: (1) Zoom  (2) See all  (3) Quit"
  766.  
  767. pack append .top .top.info {bottom fillx} .top.graph {top fill}
  768. pack append . .top {top fill}
  769.  .top.graph configure -xmin 100 -xmax 500
  770.  .top.graph configure -ymin 0 -ymax 1000
  771.  
  772. bind .top.graph <Leave> { %W crosshair -10 -10 }
  773. bind .top.graph <Motion> { %W crosshair %x %y }
  774.  
  775. bind .top.graph <ButtonPress-2> { 
  776.   %W config -xmin {} -ymin {} -xmax {} -ymax {}
  777. }
  778.  
  779. bind .top.graph <ButtonPress-3> {exit}
  780.  
  781. bind .top.graph <ButtonPress-1> {zoomstart %W %x %y}
  782. bind .top.graph <B1-Motion> {zoomchange %W %x %y}
  783. bind .top.graph <ButtonRelease-1> {zoomstop %W %x %y}
  784.  
  785. ########################### test.spe.uu ###############################
  786. begin 644 test.spe
  787. M````&$=-050@,2XP,"``````````````````````````````````````````
  788. M``````````````````````````````````````````````````````$````%
  789. M``$``0````(`!0`,``D`!@`'``@`"P`*`!,`"P`%``\`&``0`!0`&@`5`!0`
  790. M#P`2`!<`"@`@`!(`%0`0`!,`#0`>``H`%0`2`"(`%P`0`!``)@`4`!L`$P`2
  791. M``T`'0`*`#,`+0`L`"L`&P`.`!0`&0`<`!4`(0`;`!X`+0`C`"L`,P`2`"(`
  792. M*P`0`"0`00!8`&D`+@`A`"0`(@`T`"$`*0`D`"$`)``R`"D`-P`S`#P`*0`J
  793. M`"\`-0`L`"D`)P`N`#@`00!J`(T`5@!"`!L`)P!#`#H`0@`U`"\`+@`O`"4`
  794. M10`]`#4`*P`^`#X`.P!$`#P`-``]`$<`,`!2`$L`J0'#`I(`W@!H`$(`-P!#
  795. M`#@`20!(`#P`-@!+`$H`.0!)`"P`.P!7`&D`9`!@`%(`0@!)`%\`2P`Z`#4`
  796. M0P!2`$8`.@`V`#X`20!\`(\`:0"C`)X`=@!(`$X`0@!.`#\`0`!&`$``80!>
  797. M`#D`0@`]`$@`40`^`$H`/@!(`%L`.`!*`%(`10!+`%H`70!,`#``5@!E`$(`
  798. M0P!H`&,`:0!M`&``90!R`&D`C@%:`T@#<`%L`((`=`"W`-<`S0&%`ND"\P%4
  799. M`&4`/P!-`$$`.0!$`"T`9P`\`&$`60!^`%H`:@`K`#\`4``]`$D`,@![`-T`
  800. M_P"L`&$`5`!#`$H`4``Y`$P`1@`X`$4`/P!``$@`50!4`%<`6P!6`&``80"0
  801. M`1<!U`)K`8,`A0!P`&X`:P!,`$L`2``W`$@`-P`S`%,`5``\`#P`6@`X`%,`
  802. MD`!Y`&\`7@!'`$0`.``U`%@`+P!"`$8`3`!7`.\"#0)>`6L`@@!!`%T`6@!7
  803. M`#P`,P!``$``=0!N`#T`.P!7`%L`.P`V`$0`<@!A`$4`4`!$`$<`-@`_`%$`
  804. M/0`Y`$<`4@!1`"P`,P`Y`#``)``P`#T`.P!;`&(`90!+`$@`2@`\`$$`1P!_
  805. M`(T`>0""`%8`3@"I`/4!1`#8`'``/@!!`#\`-P!#`$P`5`!1`'4`MP#9`.(`
  806. MA0!2`#D`+@!&`$(`7`#3`6`!;0#>`'``.0`[`#L`,@!!`$H`,P!7`%0`40!5
  807. M`'D`Z0%/`04`A@!(`#8`-@!+`$\`90!1`$(`5@!3`$``1P!5`&$`@`!:`#T`
  808. M20`X`$(`(``J`"X`.0`V`%,`3`!7`$$`+0!"`$X`1@!%`#4`2@!.`%(`0@`C
  809. M`%``6`!``#8`3@`_`#4`*@`N`"@`-@`E`"<`1``R`"D`(``Q`"L`,P!%`$``
  810. M/@`H`#T`*0`F`$$`-`!#`#X`9P!7`+$`I`"_`*<`Q0"<`&H`40!``#8`-``6
  811. M`#,`0``O`%0`-0!E`'@`B0!M`$T`-0`I`#L`/P`Z`"4`.``L`"L`,``J`#D`
  812. M+0!#`#P`0`!2`#\`,``K`"P`+P`O`"H`'@`;`!@`(@`P`#8`(P!$`%(`6`!R
  813. M`%@`4P!O`)H`DP""`%4`,P!!`#T`*P`L`$D`0@`S`#L`-0`_`!X`,`!2`#\`
  814. M0@`M`#H`00`Z`"$`,`!!`$``3P`\`$P`2@!/`%@`9`!9`$H`,``W`$@`+0!!
  815. M`#8`1P!V`,X`RP"(`%4`0``Y`"\`)P`K`"4`*``S`#,`10`[`#``,P`K`"@`
  816. M,P`B`"4`(0`^`"T`7`!%`$,`,@`P`#@`,P`8`!<`)P`?`"``,``F`"$`(``;
  817. M`"D`)0`G`!@`&``:`!T`.0`[`"$`.P!-`%$`40`P`#@`4`!7`"T`,0`F`"\`
  818. M+0`M`"P`+@`T`"L`*@`?`"@`$@`N`"X`+@`J`"D`(0`E`"(`)@`2`"L`%@`7
  819. M`"``(``B`"P`'``;`"@`)``@``T`&P`>`"D`)0`2`!8`+@`<`"4`+P`X`#,`
  820. M,P!&`$4`1``Z`"<`)@`@`"4`'P`>`"P`(P`?`"4`(``M`"0`,P`K`"@`+``K
  821. M`"P`-P`H`"T`*P!;`%``.``F`"0`+P`6`"0`&P`6`#T`-@!#`$@`+P`S`!$`
  822. M(@`B`"P`)P`Q`#<`&0`W`!X`(@`9`"``$0`M`!4`&P`;`!0`'``B`!,`$@`E
  823. M`",`&0`I`"@`8@#"`-4`MP"5`%4`30`N`!T`&0`?`"D`$P`L`!$`'P`7`!P`
  824. M*``>`"L`+@`C`"``)P`4`"$`(P`;`!P`(``7`!@`%@`?`"4`'``>`",`&``?
  825. M`",`-P`7`#``'@`H`!L`)``E`!H`+@`H`#4`*@`G`#(`,0`5`!``(0`1`"$`
  826. M(0`I`$P`;0#*`.4`R0"V`$X`/P`P`"<`%``5`"<`%@`-`!0`%``<`!(`%P`A
  827. M``T`"``6``(`"P`2`!@`'@`F`!<`#@`?`!@`"P`@`!$`%``=`!4`(``-`!T`
  828. M$0`<`!(`%@`3`!P`'@`M`",`-``Y`$@`+0`E`"0`(P`>`"T`&P`8`!<`$@`0
  829. M`!X`%``3`!<`$@`E`"(`$``@``L`&P`:`!4`"P`;`!4`+``Z`$<`:0![`(``
  830. M70`V`"L`(``8`!``$``6`!@`%``I`"P`.0!M`(L`E`!J`%(`.0`1`!X`&``<
  831. M``@`"@`/`!8`&0`.`"0`%0`A`!<`(``J`!4`'@`6`!0`$0`0`!(`'@`9`!0`
  832. M#P`5``P`&P`.`!8`$``2`!\`&``C`"``%``:``H`#@`8`"P`+``D`!L`&``>
  833. M`!<`%0`3``\`%``.``D``@`5`!0`%@`-`!``"@`'``X`$@`6`!H`%``'``\`
  834. M'P`<`!4`%P`4`!@`&@`B`!(`%0`7``X`"@`(`!(`#P`0`!<`&0`9``\`#``@
  835. M`!T`$``,`!T`(@`/`!X`'P`4``@`#@`9``D`%P`7`!T`%P`9`!T`'``A`"D`
  836. M+0`F`!L`$P`4`!\`$P`-``T`#``2``L`&P`2`!0`%@`3`!``#0`*``X`"@`+
  837. M``P`#@`4`!4`$0`8`!8`%P`5``T`$@`4`!0`#@`0``T`!``1``\`$P`(`!,`
  838. M%@`(``8`$P`!``H`!``&``0`"@`'``0`#P`1``@`%``2`!8`#P`%``T`%``1
  839. M`!,`%P`%`!$`!``!`!(`#0`.`!4`$P`6`!0`"0`0`!4`$0`,``X`$``#``T`
  840. M%@`0`!(`"P`5`!$`#P`:`"$`$P`3``\`#``4``L`#@`/`!$``@`*`!$`$``0
  841. M`!$`$P`)`!<`$``0`!,`&``,``D`%``/````#@`/`!(`%``*``@`#``(``H`
  842. M#P`+``@`!0`'``<`#0`*``8`#0`,`!<`$0`2``P`"0`&``T`"``'``\`"P`9
  843. M``\`&0`)``\`!@`=`!0`#``/``0`#P`1``0`#0`,`!$`$@`*``D`"``5``8`
  844. M"@`*``D``P`-``H`#P`8`!<`(0`+`!@`%0`+``L`!P`,``L`$@`(`!D`#P`4
  845. M``P`&0`:`!``#0`-``T`"P`&`!,``P`0``T`#P`*``P`"``.``T`%``-``(`
  846. M#@`*`!0`%``(`!0`#``4`!@`$@`!``T`#0`(`!,`"0`+`!$`"``2``P`"P`2
  847. M``T`$``)``@`%P`)`!``$@`)`!``"@`'``T`"0`)``H`#0`,``8`#0`.``\`
  848. M'0`/``@`#P`0`!,`$0`*``H`#P`'``@`#@`$`!``#@`!``D`!@`,``D`!@`/
  849. M`!0`#`````4`&``)`!$`$P`)``4`"0`+``<`"``%``P`"0`)``L`!@`$``<`
  850. M%0`$`!0`"``&``$``@`,``8`"P`:``T`&@`3``P`%``0``D`!P`*`!``"0`#
  851. M``D`"``/``<`!``.``X`%P`'``L`$``)``@`"``"``L`"@````D`%@`(``4`
  852. M!``+``P`!0`(`!```P`%``P`"0`,``D`"0`%``L`"P`-``@`"0`)``8`#``$
  853. M``H`!@`,``@`!``*``@`!@`*``,`!0`(``T`#0`(``P````)``D`#0`!`!$`
  854. M$0`&``D`#0`+``P``@`'``@`!0`(``(``0````<`!``'``H`!0````4````#
  855. M``\`"``"``H`#0`%``L`"``'``8`!@`#``0`!``)__T``@`&``8`#0`-``<`
  856. M"``.``8`"P`)``(````,``H`"0`(``,`#0`0``P`"0`4`!<`'P`,`!8`'P`*
  857. M``D``@`#``H`!P`#``0`!0`'``4`!0`&``4``P`,``<`!0`8`!$`%``C`",`
  858. M)0`D`"(`&P`.`!D``P`*``0`!@`+``4`$0`'``@`$0`,``4`!P`&``@`!@`'
  859. M``$`"0`)``,``0`#``4``@`,``L````)``D`"``"`````@`"``$`!``'``8`
  860. M`P`%``4``P````@`!@`'``@`"0`&``4``P`'``@`!0`)``(``P`%``D`"``'
  861. M``<``@`'``$``0`&````!@`&``4`!P`#``4``@`&``@`!``"``<`!@`"``H`
  862. M`0`(``8`!0`$``+__P`#``4`!``!``(`"@`+``,``0`$``$``0`$``D`"``#
  863. M``8`!P`#``0``P`"``$`!0`#````"`````(``@`(``0`!@`$``$`!0`&``$`
  864. M!``$``8`"0`$``$`!@`(``<``P````(`"@````+__P`(``(`!``!```````&
  865. M``,`!P`&``(`!``#__X`!@`'``8``0`#``0`!``#``(``___````!``$``@`
  866. M!P`"``0`!P````4``@`%``$`!``"``$``@`+``<`!`````,````%``0`!@`&
  867. M``0``0`&``4`!`````$````#``<`!0`#``(`!``"``(``P``````!P`$``0`
  868. M`0`$````!P`$````!P`!``(`!P`$``,``0````$`!P`#``0``0`%``,`"/__
  869. M``(`!P`!``8`!``$``0``@`"`````0`#``$````!``$``@`$``@`!P`#``$`
  870. M`P`#``8``@`"``,`!0`%``4`!P`(``8````#``4``O_^``,`!0`#``$````"
  871. M``(`!``!``,`!@`!``(``0`#``(`!``'`````0`"`````@`"``,`!``#``0`
  872. M`0`"``#__P`!``(````!``(``P`#``,````%``,`"``#``<``0`"``,``P`!
  873. M``+__@`"``(````#``,````"````!``'``,`!``$``(`"/_^``(``0`#``,`
  874. M`0```````0`"``(`!O__``0`!0`#```````!``$``@`!``(``P`!`````0`!
  875. M``,````!``(``0`$``,````'``0```````$``@`$``8``0`!``$``@`#``0`
  876. C```%````!``"``(``0`"``0`!`````,````$``$`````#\@!
  877. `
  878. end
  879.  
  880. ____________________________________________________________________
  881. Paul Raines    raines@bohr.physics.upenn.edu    215-898-8832
  882. Dept. of Physics
  883. University of Pennsylvania
  884.