ftp.nice.ch/pub/next/science/mathematics/nxyplot.NIHS.bs.tar.gz#/nxyplot/Source/Plot.m

This is Plot.m in view mode; [Download] [Up]

/* Generated by Interface Builder */

#import "defs.h"
#import "Plot.h"
#import <appkit/appkit.h>
#import <objc/Storage.h>
#import <math.h>		/* for MAXFLOAT, etc. */
#import <strings.h>
#import <streams/streams.h>
#import "ColumnSelectionHandler.h"
#import "ErrorBarHandler.h"
#import <defaults/defaults.h>

/* The following routines are in auxil.m: */
extern void computeNiceLinInc(float *, float *, float *);
extern void computeNiceLogInc(float *, float *, float *);

@implementation Plot

- makeSomeScrollWindows
{
  // the following lines exist only to initialize the ScrollWindows.....pdhowell
  NXSize minsize = {0.0,0.0};
  
  [lineMatrixWindow   becomeScrollWindow];
  [symbolMatrixWindow becomeScrollWindow];
  [legendFormWindow   becomeScrollWindow];

  [lineMatrixWindow   setMinFrameSize:minsize];
  [symbolMatrixWindow setMinFrameSize:minsize];
  [legendFormWindow   setMinFrameSize:minsize];

  return self;
}

+ initialize
{
  const NXDefaultsVector nxyplotDefaults = {
    { "colorOption", "NO"},
    { "colorPrinting", "NO"},
    { "cycleLineStyles", "NO"},
    { "opaqueBackground", "YES"},
    { NULL, NULL}
  };

  self = [[Object alloc] init];
  NXRegisterDefaults("nxyplot", nxyplotDefaults);
  return self;
}

- init
{
  // Initialize variables here:
  nfilestotal = 0;
  ncurvestotal = 0;
  globaldatamin.x = MAXFLOAT;
  globaldatamin.y = MAXFLOAT;
  globaldatamax.x = -MAXFLOAT;
  globaldatamax.y = -MAXFLOAT;
  beepError = 0;
  backgroundcolor = NX_COLORWHITE;
  textcolor = NX_COLORBLACK;
  srandom(10);			/* initialize for color selection  */
  oldMin.x = 0.0;
  oldMin.y = 0.0;
  oldMax.x = 0.0;
  oldMax.y = 0.0;
  oldInc.x = 0.0;
  oldInc.y = 0.0;
  currentMin.x = 0.0;
  currentMin.y = 0.0;
  currentMax.x = 0.0;
  currentMax.y = 0.0;
  currentInc.x = 0.0;
  currentInc.y = 0.0;

  return self;
}

// Delete all data (free up the space that was malloc'ed)
- removeAllFiles:sender
{
  int n, j;
  datahunk *pdh;
  const char * generictitle = "NXYPLOT";

  if (nfilestotal == 0) {
    return self;
  }

  // Put up an alert panel.  This method is called by a menu item and also
  // by the removeAndOpen method; only if it's called by the menu item
  // do we want to put up the alert panel.
  if (sender != self) {
    if (NXRunAlertPanel("Remove all", "Remove all files and clear plot",
			"OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
      return self;
    }
  }

  for (n=nfilestotal-1; n>=0; n--) {
    pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
    for (j = 0; j < pdh->ncurves; j++) {
      free( (void *)*(pdh->y+j) );
    }
    free( (void *)(pdh->y) );
    if (pdh->has_eybars) {
      for (j = 0; j < pdh->ncurves; j++) {
        free( (void *)*(pdh->ey+j) );
      }
      free( (void *)(pdh->ey));
    }
    free( (void *)(pdh->x) );
    if (pdh->has_exbars) {
      free( (void *)(pdh->ex));
    }
    free( (void *)(pdh->filename) );
  }
  [datahunkArray empty];
  [self adjustPanels:ncurvestotal :-1]; /* -1 is a special signal */
  nfilestotal = 0;

  [columnSelectionHandler removeAll:self];

  [errorBarHandler removeAll:self];

  [canvas display];		/* clear the canvas */

  [[canvas window] setTitle:generictitle];

  ncurvestotal = 0;

  // reset globaldatamin/max
  globaldatamin.x = MAXFLOAT;
  globaldatamin.y = MAXFLOAT;
  globaldatamax.x = -MAXFLOAT;
  globaldatamax.y = -MAXFLOAT;

  // clear xMin/Max/Inc and yMin/Max/Inc windows:
  [xMin setStringValue:"" at:0];
  [xMax setStringValue:"" at:0];
  [xInc setStringValue:"" at:0];
  [yMin setStringValue:"" at:0];
  [yMax setStringValue:"" at:0];
  [yInc setStringValue:"" at:0];
  srandom(10);			/* initialize for color selection  */

  oldMin.x = 0.0;
  oldMin.y = 0.0;
  oldMax.x = 0.0;
  oldMax.y = 0.0;
  oldInc.x = 0.0;
  oldInc.y = 0.0;
  currentMin.x = 0.0;
  currentMin.y = 0.0;
  currentMax.x = 0.0;
  currentMax.y = 0.0;
  currentInc.x = 0.0;
  currentInc.y = 0.0;

  return self;
}

// Remove all existing files and open a new one
- removeAndOpen:sender
{
  if (NXRunAlertPanel("New",
		      "Remove all files, clear plot\nand open new file",
		      "OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
    return self;
  }

  [self removeAllFiles:self];
  [self open:self];
  return self;
}


- fixFileRemovalPanel:sender
{
  int n;
  char title[80];
  NXCoord dy;
  int numrows, numcols;
  NXSize cellsize, intercell;

  // Fix up the filename matrix
  [fileRemovalMatrix getNumRows:&numrows numCols:&numcols];
  [fileRemovalMatrix getCellSize:&cellsize];
  [fileRemovalMatrix getIntercell:&intercell];

  [fileRemovalMatrix renewRows:nfilestotal cols:1];
  dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  for (n=0; n<nfilestotal; n++) {
    if (!strncmp([self filename:(unsigned)n], "pasteboard", 10))
      sprintf(title, "pasteboard");
    else
      sprintf(title, strrchr([self filename:(unsigned)n], '/') + 1);
    [[fileRemovalMatrix cellAt:n :0] setStringValue:title];
  }
  [fileRemovalMatrix sizeToCells];
  [fileRemovalMatrix moveBy:0.0 :-dy];

  // Fix up the buttons that go with the matrix of names
  [fileRemovalButtons getCellSize:&cellsize];
  [fileRemovalButtons getIntercell:&intercell];
  dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
  [fileRemovalButtons renewRows:nfilestotal cols:1];
  [fileRemovalButtons sizeToCells];
  [fileRemovalButtons moveBy:0.0 :-dy];
  for (n=0; n<nfilestotal; n++) {
    [ [fileRemovalButtons cellAt:n :0] setState:0]; /* a safety play */
  }

  [fileRemovalPanel display];

  if (sender != self) {
    /* If this method is called from the menu, make the window key. */
    [fileRemovalPanel makeKeyAndOrderFront:self];
  }

  return self;
}


// Remove some existing files; the file removal is not hard, but correctly
// updating the linestyle, symbolstyle, and legend matrices is harder.
// Correctly handling the ColumnSelection and ErrorBar panels is even harder.
- removeSomeFiles:sender
{
  int n, i, j;
  int old_index = 0, current_index = 0;
  /* These integers point to the columns of the linestyle and symbolstyle
   * matrices as we run along updating the matrices.  They also serve in
   * updating the curvecolors array.
   */
  datahunk *pdh;
  int *newlinestyles, *newsymbolstyles;

  if (nfilestotal == 0) {
    return self;
  }
  /*
   * If all files are marked for deletion, we will call the "removeAllFiles"
   * method.  So check here to see if all files are marked for deletion.
   */
  i = 1;
  for (n=0; n < nfilestotal; n++) {
    if ([[fileRemovalButtons cellAt:n :0] state] == 0) {
      i = 0;			// there is a file not being deleted
      break;
    }
  }
  if (i == 1) {			// all files are to be deleted
    [self removeAllFiles:self];
    return self;
  }


  /* Figure out what the linestyle and symbolstyle matrices should look
   * like after the file removal process.  We do it this way because simply
   * copying the columns of the matrices fails after the renewRows:cols:
   * message is sent to them.
   */
  newlinestyles = (int *)malloc(ncurvestotal * sizeof(int));
  newsymbolstyles = (int *)malloc(ncurvestotal * sizeof(int));
  for (n=0; n < nfilestotal; n++) {
    if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
      /* Just bump the index if the file is to be deleted.  */
      old_index += [self nCurves:n];
    }
    else {
      for (i=0; i < [self nCurves:n]; i++) {
	for (j=0; j < N_LINE_STYLES; j++) {
	  if ([ [lineMatrix cellAt:j :old_index] state] == 1) {
	    newlinestyles[current_index] = j;
	    break;
	  }
	}
	for (j=0; j < N_SYMBOL_STYLES; j++) {
	  if ([ [symbolMatrix cellAt:j :old_index] state] == 1) {
	    newsymbolstyles[current_index] = j;
	    break;
	  }
	}
	/* Update the curvecolors array: */
	curvecolors[current_index] = curvecolors[old_index];
	old_index++;
	current_index++;
      }
    }
  }
  /* We could do a realloc on curvecolors here, but not much would be saved. */

  /* Reset the running indices. */
  old_index = 0;
  current_index = 0;

  for (n=0; n < nfilestotal; n++) {
    /* Is the nth file marked for deletion?  */
    if ([[fileRemovalButtons cellAt:n :0] state] == 1) {	/* yes it is */
      pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
      for (j = 0; j < pdh->ncurves; j++) {
	free( (void *)*(pdh->y+j) );
      }
      free( (void *)(pdh->y) );
      if (pdh->has_eybars) {
	for (j = 0; j < pdh->ncurves; j++) {
	  free( (void *)*(pdh->ey+j) );
	}
	free( (void *)(pdh->ey));
      }
      free( (void *)(pdh->x) );
      if (pdh->has_exbars) {
	free( (void *)(pdh->ex));
      }
      free( (void *)(pdh->filename) );
      old_index += [self nCurves:n];
    }
    else {
      /* Column copying of the linestyle and symbolstyle matrices
       * is handled later.  Do the legend form here.
       */
      for (j=0; j < [self nCurves:n]; j++) {
	[legendForm setStringValue:[legendForm stringValueAt:old_index]
                    at:current_index];
	[legendForm drawCellAt:current_index];
	old_index++;
	current_index++;
      }
    }
  }

  /* Get rid of extraneous legendForm entries: */
  for (j=current_index; j<ncurvestotal; j++) {
    [legendForm removeEntryAt:j];
  }
  [legendForm sizeToFit];
  [ [legendForm window] display];

  /*
   * Do the ColumnSelectionHandler manipulation here, before nfilestotal
   * gets reset.
   */
  [columnSelectionHandler update:self];
  [errorBarHandler update:self];

  /* We put the datahunkArray manipulation here because the removeAt method
   * shifts the elements of the datahunkArray to close the gap created by
   * removing one element; for this reason we count down rather than up.
   */
  j = nfilestotal;
  for (n=nfilestotal-1; n >= 0; n--) {
    if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
      [datahunkArray removeElementAt:(unsigned)n];
      j--;
    }
  }
  nfilestotal = j;
  ncurvestotal = current_index;

  /* Now resize and display the linestyle and symbolstyle matrices */
  [lineMatrix renewRows:N_LINE_STYLES cols:ncurvestotal];
  [lineMatrix sizeToCells];
  for (i=0; i<ncurvestotal; i++) {
    for (j=0; j<N_LINE_STYLES; j++) {
      [ [lineMatrix cellAt:j :i] setState:0];
    }
    [ [lineMatrix cellAt:newlinestyles[i] :i] setState:1];
  }

  [lineText renewRows:1 cols:ncurvestotal];
  [ [lineMatrix window] display];

  [symbolMatrix renewRows:N_SYMBOL_STYLES cols:ncurvestotal];
  [symbolMatrix sizeToCells];
  for (i=0; i<ncurvestotal; i++) {
    for (j=0; j<N_SYMBOL_STYLES; j++) {
      [ [symbolMatrix cellAt:j :i] setState:0];
    }
    [ [symbolMatrix cellAt:newsymbolstyles[i] :i] setState:1];
  }
  [symbolText renewRows:1 cols:ncurvestotal];
  [ [symbolMatrix window] display];

  [self adjustScrollWindows];

  /* must also fix up appearence of the file removal window */
  [self fixFileRemovalPanel:self];
  [fileRemovalPanel performClose:self];
  free((void *)newlinestyles);
  free((void *)newsymbolstyles);

  /* and replot automatically */
  if (nfilestotal == 0) {
    [canvas display];
  }
  else {
    [self drawPlot:self];
  }

  return self;
}


/* return the x data for the nth file */
- (NXCoord *)xdata:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->x;
}

/* return the y data for the nth file */
- (NXCoord **)ydata:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->y;
}

/* return the y error data for the nth file */
- (NXCoord **)eydata:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->ey;
}

/* return the x error data for the nth file */
- (NXCoord *)exdata:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->ex;
}

/* return the number of x-points in the nth file */
- (int)nPoints:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->npoints;
}

/* return the number of curves in the nth file */
- (int)nCurves:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->ncurves;
}

/* return the name of the nth file */
- (char *)filename:(unsigned)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:n];
  return pdh->filename;
}

/* Does the nth file have error bars in y? */
- (BOOL) has_eybars:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->has_eybars;
}

/* Does the nth file have error bars in x? */
- (BOOL) has_exbars:(int)n
{
  datahunk *pdh;

  pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
  return pdh->has_exbars;
}

- (int)nCurvesTotal     { return ncurvestotal;}

- (int)nFiles           { return nfilestotal;}

- makeLineStyle:(int)aCurve :(int)lineStyle
{
  int   row;

  for (row = 0; row < N_LINE_STYLES; row++)
    [[lineMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
  [[lineMatrix cellAt:lineStyle :aCurve] setState:1];
  return self;
}

- makeSymbolType:(int)aCurve :(int)symType;
{
  int   row;

  for (row = 0; row < N_SYMBOL_STYLES; row++)
    [[symbolMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
  [[symbolMatrix cellAt:symType :aCurve] setState:1];
  return self;
}

- (BOOL) xaxisLog
{
  if ( [xLinLog state] ) return YES;
  else return NO;
}

- forceXaxisLinear
{
  [xLinLog setState:0];
  [xLinLog display];
  return self;
}

- forceXaxisLog
{
  [xLinLog setState:1];
  [xLinLog display];
  return self;
}

- (BOOL) yaxisLog
{
  if ( [yLinLog state] ) return YES;
  else return NO;
}

- forceYaxisLinear
{
  [yLinLog setState:0];
  [yLinLog display];
  return self;
}

- forceYaxisLog
{
  [yLinLog setState:1];
  [yLinLog display];
  return self;
}

- (BOOL) shouldChangeLegendFont
{
  if ( [changeLegendFont state] ) return YES;
  else return NO;
}

- (BOOL) shouldChangeLegendTitleFont
{
  if ( [changeLegendTitleFont state] ) return YES;
  else return NO;
}

- (BOOL) shouldChangeMainTitleFont
{
  if ( [changeMainTitleFont state] ) return YES;
  else return NO;
}

- (BOOL) shouldChangeYTitleFont
{
  if ( [changeYTitleFont state] ) return YES;
  else return NO;
}

- (BOOL) shouldChangeXTitleFont
{
  if ( [changeXTitleFont state] ) return YES;
  else return NO;
}

- (BOOL) shouldChangeTicLabelFont
{
  if ( [changeTicLabelFont state] ) return YES;
  else return NO;
}

- (int)providelinestyle:(int)aCurve
{
  int   row, cellstate;

  /* First, if line is turned off, return that */
  if ([ [lineMatrix cellAt:N_LINE_STYLES-1 :aCurve] state] == 1) {
    return N_LINE_STYLES-1;
  }
  /*
   * Next, check if we are printing or previewing and if we should
   * cycle the line styles
   */
  if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
      && ([accPrintLineStyleButton state] == 1) ) {
    return aCurve % (N_LINE_STYLES - 1);
  }
  /*
   * Here we can just look at the linestyle matrix.
   */
  else {
    for (row = 0; row < N_LINE_STYLES; row++) {
      cellstate = [ [lineMatrix cellAt:row :aCurve] state];
      if (cellstate == 1) return row;
    }
  }
  return 0;			/* for safety */
}

- (int)providesymbolstyle:(int)aCurve
{
  int   row, cellstate;

  for (row = 0; row < N_SYMBOL_STYLES; row++) {
    cellstate = [ [symbolMatrix cellAt:row :aCurve] state];
    if (cellstate == 1) return row;
  }
  return 0;			/* for safety */
}

/*
 * Changed floats to doubles in the following group.  With floats, when
 * the value was 250.4, the value returned by floatValueAt: was
 * 250.399994 (for example).  (This appears to be a problem with the
 * atof function on Unix 32-bit systems.)  This gave troubles in the
 * tic mark routine in PlotView.m.  
 */
- (double)provideXmin  {return [xMin doubleValueAt:0];}
- (double)provideXmax  {return [xMax doubleValueAt:0];}
- (double)provideXinc  {return [xInc doubleValueAt:0];}
- (double)provideYmin  {return [yMin doubleValueAt:0];}
- (double)provideYmax  {return [yMax doubleValueAt:0];}
- (double)provideYinc  {return [yInc doubleValueAt:0];}

- resetXmin:(double)aNum { [xMin setDoubleValue:aNum at:0]; return self; }
- resetXmax:(double)aNum { [xMax setDoubleValue:aNum at:0]; return self; }
- resetXinc:(double)aNum { [xInc setDoubleValue:aNum at:0]; return self; }
- resetYmin:(double)aNum { [yMin setDoubleValue:aNum at:0]; return self; }
- resetYmax:(double)aNum { [yMax setDoubleValue:aNum at:0]; return self; }
- resetYinc:(double)aNum { [yInc setDoubleValue:aNum at:0]; return self; }

- (float)provideGlobalXmin {return globaldatamin.x;}
- (float)provideGlobalYmin {return globaldatamin.y;}

- resetMinMax:sender
{
  int n;
  datahunk * pdh;

  // We have to go through all the data and recalculate min/max since
  // some data curves may have been "turned off" (by setting their linestyle
  // to none and symbolstyle to none).

  for (n=0; n<nfilestotal; n++) {
    pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
    [self findMinMax:pdh];
  }
  [self findGlobalMinMax];

  [self niceMinMaxInc];
  [self drawPlot:self];		/* redraw plot automatically */
  return self;
}

- drawPlotButton:(int)state
{
  if (state==0) {
    [plotButton highlight:NO];	/* will display normal title */
  }
  if (state==1) {
    [plotButton highlight:YES];	/* will display alternate title */
  }
  return self;
}

// We make the plotParam object responsible for checking parameters
// before the PlotView object is called.  Thus the PlotView object can
// assume the parameters are OK, and it doesn't have to do any checking.
// Things to check: xinc has the same sign as xmax-xmin (same for y);
// no negative data if log plot requested on x or y axis; there won't be
// too many tic marks requested.
- sanityCheck
{
  float xinc = [self provideXinc];
  float xmax = [self provideXmax], xmin = [self provideXmin];
  float yinc = [self provideYinc];
  float ymax = [self provideYmax], ymin = [self provideYmin];
  int   nticmarks;

  /* First check: no nonpositive data if logarithmic axis */
  /* Also check that increment is > 5 (need rint(log10(increment)) >=1 ) */
  if ( [self xaxisLog] ) {
    if (globaldatamin.x <= 0.0 || xmin <= 0.0 || xmax <= 0.0) {
      [xLinLog setState:0];	/* back to linear */
      NXBeep();			/* audible alert */
      beepError = 1;
    }
    if (xinc < 5.0) {
      [self resetXinc:(double)10.0];
      NXBeep();
      beepError = 12;
    }
  }
  if ( [self yaxisLog] ) {
    if (globaldatamin.y <= 0.0 || ymin <= 0.0 || ymax <= 0.0) {
      [yLinLog setState:0];	/* back to linear */
      NXBeep();			/* audible alert */
      beepError = 2;
    }
    if (yinc < 5.0) {
      [self resetYinc:(double)10.0];
      NXBeep();
      beepError = 12;
    }
  }
  /* Second check: xinc has same sign as xmax and xmin */
  if (xinc*(xmax-xmin) <= 0.0) { /* the bad case - avoid infinite loop */
    if (xinc < 0.0) {		/*     in PlotView:drawSelf           */
      xinc = -xinc;
      [self resetXinc:xinc];
      NXBeep();
      beepError = 3;
    }
    if (xmax <= xmin) {
      [self niceMinMaxInc];
      NXBeep();			/* alert */
      beepError = 4;
    }
  }
  /* And similarly for yinc */
  if (yinc*(ymax-ymin) <= 0.0) { /* the bad case - avoid infinite loop */
    if (yinc < 0.0) {
      yinc = -yinc;
      [self resetYinc:yinc];
      NXBeep();			/* alert */
      beepError = 5;
    }
    if (ymax <= ymin) {
      [self niceMinMaxInc];
      NXBeep();			/* alert */
      beepError = 6;
    }
  }
  /* Third check: no more than 100 (say) tic marks on either axis */
  if ( ![self xaxisLog] ) {	/*  linear axis */
    nticmarks = (int) ((xmax - xmin) / xinc) ;
    if (nticmarks > 100) {
      computeNiceLinInc(&xmin, &xmax, &xinc);
      [self resetXmin:xmin];
      [self resetXmax:xmax];
      [self resetXinc:xinc];
      NXBeep();			/* alert */
      beepError = 7;
    }
  }
  if ( ![self yaxisLog] ) {	/*  linear axis */
    nticmarks = (int) ((ymax - ymin) / yinc) ;
    if (nticmarks > 100) {
      computeNiceLinInc(&ymin, &ymax, &yinc);
      [self resetYmin:ymin];
      [self resetYmax:ymax];
      [self resetYinc:yinc];
      NXBeep();			/* alert */
      beepError = 8;
    }
  }

  return self;
}

- drawPlot:sender
{
  char c_xmin[20], c_xmax[20], c_ymin[20], c_ymax[20], c_xinc[20], c_yinc[20];
  float xmin, xmax, ymin, ymax, xinc, yinc;

  if (nfilestotal==0) return self;

  [self drawPlotButton:1];	/* display "Plotting" */
  [self sanityCheck];		/* disallow various bad parameters */

  /* maybe save min/max/inc */
  /* This really gets crufty: when we look at the TextField xMin and
   * get its float value via [xMin floatValueAt:0], we get
   * a float which is the result of applying atof() to a string.  The
   * float which results may not be the same as the float that was originally
   * written into the TextField.  So we apply the process ourselves:
   * float --> string (via sprintf) --> float (via atof).  Ugly, but necessary
   * (otherwise, if you zoom, then hit the Plot button twice, then hit
   * the Previous View button, you might not get back to where you want).
   */
  sprintf(c_xmin,"%g",currentMin.x);
  sprintf(c_xmax,"%g",currentMax.x);
  sprintf(c_xinc,"%g",currentInc.x);
  sprintf(c_ymin,"%g",currentMin.y);
  sprintf(c_ymax,"%g",currentMax.y);
  sprintf(c_yinc,"%g",currentInc.y);
  xmin = (float)atof(c_xmin);
  xmax = (float)atof(c_xmax);
  xinc = (float)atof(c_xinc);
  ymin = (float)atof(c_ymin);
  ymax = (float)atof(c_ymax);
  yinc = (float)atof(c_yinc);
  if ( [xMin floatValueAt:0] != xmin
      || [xMax floatValueAt:0] != xmax
      || [xInc floatValueAt:0] != xinc
      || [yMin floatValueAt:0] != ymin
      || [yMax floatValueAt:0] != ymax
      || [yInc floatValueAt:0] != yinc ) {
    oldMin.x = currentMin.x;
    currentMin.x = [xMin floatValueAt:0];
    oldMax.x = currentMax.x;
    currentMax.x = [xMax floatValueAt:0];
    oldInc.x = currentInc.x;
    currentInc.x = [xInc floatValueAt:0];
    oldMin.y = currentMin.y;
    currentMin.y = [yMin floatValueAt:0];
    oldMax.y = currentMax.y;
    currentMax.y = [yMax floatValueAt:0];
    oldInc.y = currentInc.y;
    currentInc.y = [yInc floatValueAt:0];
  }

  [canvas display];
  [self drawPlotButton:0];	/* display "Plot" */
  return self;
}

// Allocate enough memory and read the data points
/*
 * This code makes the following assumptions:
 * 1. Any data on a line following the character "!" is to be discarded.
 * 2. We can determine the number of curves by looking at the first
 * line of data, which should be of the form
 *  x  y1  y2    ...    yn
 * (possibly separated by commas, with possible trailing comment).
 * 3. Other lines of the file may contain arbitrary text, but contain no
 * numerals or periods (these get interpreted as floating point numbers
 * when the file is scanned); also, anything after a "!" is discarded.
 *
 * It is not easy to make a completely general and bullet-proof scanning
 * routine.  This code is fairly robust and was easy to write.
 */
- (int) readData:(NXStream *)aDataStream :(char *)fname
{
  BOOL    inword = NO;
  char    c;
  int     j, size = ALLOCSIZE;
  int     tmpint = 0;		/* tmpint initialized to avoid compiler warning */
  int     oldncurves = ncurvestotal;
  datahunk *pdh = (void *)NULL;
  BOOL    noxdata = NO;
  float   tmpfloat = 0.0;	/* initialized to avoid compiler warning */
  
  [self preludeToReading:fname :&pdh];	/* take care of some housekeeping */
  
  /* Figure out the number of curves in the file by reading characters  */
  /* until a newline is encountered; the number of curves is one less   */
  /* than the number of words found.                                    */
  /* We assume the input file is an ascii file; a compressed file will  */
  /* have been pumped through zcat and written to a temporary file.     */
  pdh->ncurves = -1;
top_of_file: ;
  while (1) {
    c = (char)NXGetc(aDataStream);
    if (c == (char)EOF) {
      break;			/* EOF, break out of while loop */
    }
    if (c == '\n') {
      if (pdh->ncurves == -1) {
	goto top_of_file;	// ugh, but there may be a blank line
      }
      else {
	break;			/* we have found some data, break from while loop */
      }
    }
    if (c == '!') {		/* comment signal: start discarding characters */
      while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /*  do nothing */
      if (pdh->ncurves == -1)	/* any data found yet? */
	goto top_of_file;		/* ugh */
      else
	break;
    }
    else if ((inword==NO) && !(c==' ' || c=='\t')) {
      pdh->ncurves++;
      inword = YES;
    }
    else if ((inword==YES) && (c==' ' || c=='\t')) {
      inword = NO;
    }
  }
  if (pdh->ncurves == -1) {	/* couldn't find "\n", give up (after cleanup) */
    [plotButton setAltTitle:"Plotting"]; /* reset plot button */
    [plotButton highlight:NO];
    NXPing();			           /* force redraw */
    free( (void *)(pdh->filename) );
    nfilestotal--;
    return 0;
  }
  if (pdh->ncurves == 0) {	/* only one column, assume x data are integers */
    noxdata = YES;
    tmpfloat = 1.0;
    pdh->ncurves = 1;
    if (pdh->has_exbars || pdh->has_eybars) {
      NXRunAlertPanel("Read Data (with error bars)",
		      "Error bars expected, only one curve found\n"
		      "Unsetting error bar button and continuing",
		      "OK", NULL, NULL);
      [errorBars setTitle:"No error bars"];
      pdh->has_exbars = NO;
      pdh->has_eybars = NO;
    }
  }

  /*
   * We read more than one column; if there are error bars we must adjust ncurves.
   *    case            pdh->ncurves        true no. of curves
   *   y only            2n                    n
   *   x only            n (>=2)               n-1
   *   y and x          2n+1 (>=3)             n
   */
  if (pdh->has_eybars && !pdh->has_exbars) {
    if ( pdh->ncurves % 2  !=  0 ) {
      NXRunAlertPanel("Read Data (with error bars)",
		      "Strange number of curves found\n"
		      "Unsetting error bar button and continuing",
		      "OK", NULL, NULL);
      [errorBars setTitle:"No error bars"];
      pdh->has_eybars = NO;
    }
    else {
      pdh->ncurves = pdh->ncurves / 2;
    }
  }
  else if (pdh->has_exbars && !pdh->has_eybars) {
    if ( pdh->ncurves < 2 ) {
      NXRunAlertPanel("Read Data (with error bars)",
		      "Too few curves found\n"
		      "Unsetting error bar button and continuing",
		      "OK", NULL, NULL);
      [errorBars setTitle:"No error bars"];
      pdh->has_exbars = NO;
    }
    else {
      pdh->ncurves--;
    }
  }
  else if (pdh->has_exbars && pdh->has_eybars) {
    if ( pdh->ncurves < 3  ||  pdh->ncurves % 2 == 0 ) {
      NXRunAlertPanel("Read Data (with error bars)",
		      "Bad number of curves found\n"
		      "Unsetting error bar button and continuing",
		      "OK", NULL, NULL);
      [errorBars setTitle:"No error bars"];
      pdh->has_exbars = NO;
      pdh->has_eybars = NO;
    }
    else {
      pdh->ncurves = (pdh->ncurves - 1) / 2;
    }
  }

  /* Now read the data into memory */
  NXSeek(aDataStream, 0L, NX_FROMSTART);
    
  pdh->x = (NXCoord *)malloc( size * sizeof(NXCoord) );
  if (pdh->has_exbars) {
    pdh->ex = (NXCoord *)malloc( size * sizeof(NXCoord) );
  }
  pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  for (j = 0; j < pdh->ncurves; j++) {
    *(pdh->y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
  }
  if (pdh->has_eybars) {
    pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
    for (j = 0; j < pdh->ncurves; j++) {
      *(pdh->ey+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
    }
  }
  pdh->npoints = 0;
  while(1) {
    if (noxdata) {
      *(pdh->x+pdh->npoints) = tmpfloat++;
    }
    else {
      if (pdh->has_exbars) {	/* x error bars, read two items*/
	while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
	  if (c == '!') {
	    while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
	    goto skipline;	/* ugh */
	  }
	}
	while ( (tmpint=NXScanf(aDataStream, "%f", pdh->ex+pdh->npoints)) == 0 ) {
	  if (c == '!') {
	    while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
	    goto skipline;	/* ugh */
	  }
	}
      }
      else {			/* no x error bars, read one item */
	while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
	  c = (char)NXGetc(aDataStream);	/* throw away extraneous characters */
	  if (c == '!') {
	    while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
	    goto skipline;	/* ugh */
	  }
	}
      }
      if (tmpint == EOF) break;	/* break out of the while(1) loop */
    }

    if (pdh->has_eybars) {	/* y error bars, read two items */
      for (j = 0; j < 2 * pdh->ncurves; j++) {
	if (j%2 == 0) {		/* j is even, reading y value */
	  while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+(j/2))+ pdh->npoints)) == 0 ) {
	    c = (char)NXGetc(aDataStream);	/* throw away the next character */
	    if (c == '!') {		/* comment signal; start discarding characters */
	      while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
	      goto skipline;	/* ugh */
	    }
	  }
	}
	else {			/* j is odd, reading error */
	  while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->ey+(j-1)/2)+ pdh->npoints)) == 0 ) {
	    c = (char)NXGetc(aDataStream);
	    if (c == '!') {
	      while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ;
	      goto skipline;
	    }
	  }
	}
      }
    }
    else {			/* no error bars, read one item */
      for (j = 0; j < pdh->ncurves; j++) {
/*
 * Try to allow extraneous characters here (if scanf returns 0, which means
 * it didn't find a number, just do a getc on the input stream to throw that
 * character away.  This will allow commas and alphabetic characters in the
 * middle of an input line (no digits or periods, though!).
 */
	while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+j)+ pdh->npoints)) == 0 ) {
	  c = (char)NXGetc(aDataStream);	/* throw away the next character */
	  if (c == '!') {		/* comment signal; start discarding characters */
	    while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
	    goto skipline;	/* ugh */
	  }
	}
      }
    }
    if (tmpint == EOF) break;	/* could get this if noxdata==YES */
    pdh->npoints++;
    if (pdh->npoints == size) {		/* get more memory */
      size += ALLOCSIZE;
      pdh->x = (NXCoord *)realloc(pdh->x, size * sizeof(NXCoord));
      if (pdh->has_exbars) {
	pdh->ex = (NXCoord *) realloc(pdh->ex, size * sizeof(NXCoord));
      }
      for (j = 0; j < pdh->ncurves; j++) {
	*(pdh->y+j) = (NXCoord *)realloc( *(pdh->y+j), size * sizeof(NXCoord) );
      }
      if (pdh->has_eybars) {
	for (j = 0; j < pdh->ncurves; j++) {
	  *(pdh->ey+j) = (NXCoord *)realloc( *(pdh->ey+j), size * sizeof(NXCoord) );
	}
      }
    }
skipline: ;
  }

  [self postludeToReading:fname :oldncurves :pdh];

  ncurvestotal += pdh->ncurves;

  return pdh->npoints;
}

/* Might want to make sure INLINE_MATH is defined when math.h is included
 * (for guaranteed fast logarithms?)
 */
- checkLinLog:(datahunk *)pdh
{
  /* Check x and y axes -- do a heuristic test for log/lin.
   * The test employed comes from M. Merriam and xyplot.
   */
  double    scale, linsum, logsum;
  register  double tmp;
  int       i, j;

  /* First test x axis.  */
  if (pdh->datamax.x > 0.0  &&  pdh->datamin.x > 0.0
      && pdh->datamax.x != pdh->datamin.x) {
    scale = fabs( (double)pdh->datamax.x - (double)pdh->datamin.x );
    linsum = 0.0;
    for (j=1; j<pdh->npoints; j++) {
      tmp = ( (double)pdh->x[j]-(double)pdh->x[j-1] )/(double)scale;
      linsum += tmp*tmp;
    }
    scale = log10( (double)pdh->datamax.x/(double)pdh->datamin.x );
    /* what if datamax.x<datamin.x? */
    logsum = 0.0;
    for (i=1; i<pdh->npoints; i++) {
      tmp = log10( (double)pdh->x[i]/(double)pdh->x[i-1] ) / scale;
      logsum += tmp*tmp;
    }
    if (linsum < logsum) {
      pdh->xaxislin = YES;	/* linear axis */
    }
    else {
      pdh->xaxislin = NO;	/* logarithmic axis */
    }
  }
  else {
    pdh->xaxislin = YES;	/* linear */
  }
  /* Now test y axis */
  if (pdh->datamax.y > 0.0  &&  pdh->datamin.y > 0.0
      && pdh->datamax.y != pdh->datamin.y) {
    scale = fabs( (double)pdh->datamax.y - (double)pdh->datamin.y );
    linsum = 0.0;
    for (j=0; j<pdh->ncurves; j++) {
      for (i=1; i<pdh->npoints; i++) {
	tmp = ( (double)*(*(pdh->y+j)+i) - (double)*(*(pdh->y+j)+i-1) )
	  / scale;		/* avoid overflow */
	linsum += tmp*tmp;
      }
    }
    scale = log10((double)pdh->datamax.y/(double)pdh->datamin.y);
    logsum = 0.0;
    for (j=0; j<pdh->ncurves; j++) {
      for (i=1; i<pdh->npoints;i++) {
	tmp = log10( (double)*(*(pdh->y+j)+i)/(double)*(*(pdh->y+j)+i-1) ) / scale;
	logsum += tmp*tmp;
      }
    }
    if (linsum < logsum) {
      pdh->yaxislin = YES;	/* linear axis */
    }
    else {
      pdh->yaxislin = NO;	/* logarithmic axis */
    }
  }
  else {
    pdh->yaxislin = YES;	/* linear */
  }
  return self;
}

// This routine works as follows:
// If these is just one file, we set the x and y axes to linear or logarithmic
// depending on our heuristic.
// If there is more than one file, we don't change the axes unless there
// would be an illegal result (trying to plot a negative number on a
// logarithmic axis).
- checkGlobalLinLog
{
  datahunk *pdh;

  if (nfilestotal == 1) {
    pdh = (datahunk *)[datahunkArray elementAt:0];
    if ( pdh->xaxislin )
      [xLinLog setState:0]; /* linear */
    else
      [xLinLog setState:1]; /* logarithmic */ 
    if ( pdh->yaxislin )
      [yLinLog setState:0]; /* linear */
    else
      [yLinLog setState:1]; /* logarithmic */
  }
  else {
    if ( [self xaxisLog] && globaldatamin.x <= 0.0) {
      [xLinLog setState:0];	/* back to linear */
      NXBeep();			/* audible alert */
      beepError = 9;
    }
    if ( [self yaxisLog] && globaldatamin.y <= 0.0) {
      [yLinLog setState:0];	/* back to linear */
      NXBeep();			/* audible alert */
      beepError = 10;
    }
  }
  [xLinLog display];
  [yLinLog display];

  return self;
}

- adjustLineStyleMatrix:(int)column :(int)row
{
  /* Adjust the given column of the lineMatrix object, turning off all
   * buttons except for the given row.
   */
  int i;

  for (i = 0; i < N_LINE_STYLES; i++) {
    [ [lineMatrix cellAt:i :column] setState:0];
  }
  [ [lineMatrix cellAt:row :column] setState:1];  

  return self;
}

- redisplayLineStyleMatrix
{
  [lineMatrix display];
  return self;
}

- adjustSymbolTypeMatrix:(int)column :(int)row
{
  /* Adjust the given column of the symbolMatrix object, turning off all
   * buttons except for the given row.
   */
  int i;

  for (i = 0; i < N_SYMBOL_STYLES; i++) {
    [ [symbolMatrix cellAt:i :column] setState:0];
  }
  [ [symbolMatrix cellAt:row :column] setState:1];  
  return self;
}

- redisplaySymbolTypeMatrix
{
  [symbolMatrix display];
  return self;
}

- adjustPanels:(int)oldn :(int)newn
{
  /*
   * Resize the linestyle matrix, the symbolstyle matrix, and
   * the legend form.
   * Also arrange to select and highlight only the top button in
   * each column of the linestyle and symbolstyle matrices.
   * This code could stand to be cleaned up.
   */
  char formtitle[80];
  int j, k;
  datahunk *pdh;

  if (newn == -1) {		/* flag to delete all and start over */
    for (j=oldn-1; j>=1; j--) {
      [lineText removeColAt:j andFree:YES];
      [symbolText removeColAt:j andFree:YES];
      [lineMatrix removeColAt:j andFree:YES];
      [symbolMatrix removeColAt:j andFree:YES];
      [legendForm removeEntryAt:j];
    }
    for (j=0; j<1; j++) {
      if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [lineMatrix cellAt:0 :j] setState:1];
      if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [symbolMatrix cellAt:0 :j] setState:1];
      for (k=1; k<N_LINE_STYLES; k++) {
	[ [lineMatrix cellAt:k :j] setState:0];
      }
      for (k=1; k<N_SYMBOL_STYLES; k++) {
	[ [symbolMatrix cellAt:k :j] setState:0];
      }
      sprintf(formtitle, "Curve %d", j+1);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
      [[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
    }
    //    [legendTitle setStringValue:"Legend" at:0];  should we uncomment this?
    //                                          (would need to add an outlet in IB)
    [lineText sizeToCells];
    [symbolText sizeToCells];
    [lineMatrix sizeToCells];
    [symbolMatrix sizeToCells];
    [legendForm sizeToFit];
    [ [lineMatrix window] display];   /* redraw the whole window so extra */
    [ [symbolMatrix window] display]; /* columns get erased               */
    [ [legendForm window] display];
    [curveNumber setIntValue:1]; /* update curve no. on color panel */
    [curveColorWell setColor:NX_COLORBLACK]; /* and update the color */
    /* (the background and text color wells are not reset) */
  }

  if ( (oldn == 0) && (newn > 1) ) { /* can only happen first time */
    pdh = [datahunkArray elementAt:0];
    for (j=1; j<newn; j++) {
      [lineText addCol];
      [symbolText addCol];
      [lineMatrix addCol];
      if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [lineMatrix cellAt:0 :j] setState:1];
      /* highlight 1st row new column -- this occurs automatically */
      [symbolMatrix addCol];
      if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
	[ [symbolMatrix cellAt:0 :j] setState:1];
      /* highlighting of 1st row new column occurs automatically */
      sprintf(formtitle, "Curve %d:", j+1);
      [legendForm addEntry:formtitle];
      sprintf(formtitle, "%s, Curve %d",
	      strrchr(pdh->filename,'/')==NULL ? pdh->filename
	                                       : strrchr(pdh->filename,'/') + 1,
	      j+1);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
      [[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
    }
    sprintf(formtitle, "%s, Curve 1",
	    strrchr(pdh->filename,'/')==NULL ? pdh->filename
	                                     : strrchr(pdh->filename,'/') + 1);
    [legendForm setStringValue:formtitle at:0];
    [legendForm drawCellAt:0];
    [lineText sizeToCells];
    [symbolText sizeToCells];
    [lineMatrix sizeToCells];
    [symbolMatrix sizeToCells];
    [legendForm sizeToFit];
    [lineText display];
    [symbolText display];
    [lineMatrix display];	/* don't need to redraw the window here */
    [symbolMatrix display];
    [legendForm display];
  }
  if ( (oldn == 0) && (newn == 1) ) { /* only for legendForm update */
    pdh = [datahunkArray elementAt:0];
    sprintf(formtitle, "%s, Curve 1",
	    strrchr(pdh->filename,'/')==NULL ? pdh->filename
	                                     : strrchr(pdh->filename,'/') + 1);
    [legendForm setStringValue:formtitle at:0];
    [legendForm drawCellAt:0];
    [legendForm display];
  }
  if ( oldn != 0 ) {		/* must add columns */
    pdh = [datahunkArray elementAt:(nfilestotal-1)];
    for (j=oldn; j<oldn+newn; j++) {
      [lineText addCol];
      [symbolText addCol];
      [lineMatrix addCol];
      [ [lineMatrix cellAt:0 :j] setState:1];
      /* highlighting of 1st row new column occurs automatically */
      [symbolMatrix addCol];
      [ [symbolMatrix cellAt:0 :j] setState:1];
      /* highlighting of 1st row new column occurs automatically */
      sprintf(formtitle, "Curve %d:", j+1);
      [legendForm addEntry:formtitle];
      sprintf(formtitle, "%s, Curve %d",
	      strrchr(pdh->filename,'/')==NULL ? pdh->filename
	                                       : strrchr(pdh->filename,'/') + 1,
	      j+1-oldn);
      [legendForm setStringValue:formtitle at:j];
      [legendForm drawCellAt:j];
      sprintf(formtitle, "%d", j+1);
      [[lineText cellAt:0 :j] setStringValue:formtitle];
      [[symbolText cellAt:0 :j] setStringValue:formtitle];
    }
    [lineText sizeToCells];
    [symbolText sizeToCells];
    [lineMatrix sizeToCells];
    [symbolMatrix sizeToCells];
    [legendForm sizeToFit];
    [lineText display];
    [symbolText display];
    [lineMatrix display];	/* don't need to redraw the window here */
    [symbolMatrix display];
    [legendForm display];
  }

  [self adjustScrollWindows];
  return self;
}

- adjustScrollWindows
{
// code from pdhowell for ScrollWindowing
  NXRect legendFormFrame, lineMatrixFrame, symbolMatrixFrame;

  [legendForm getFrame:&legendFormFrame];
  [lineMatrix getFrame:&lineMatrixFrame];
  [symbolMatrix getFrame:&symbolMatrixFrame];
// Put MAX in the the following lines because we don't want to allow the windows
// to get so small that the sliders aren't drawn; the 282 and 288 are the original
// sizes of the lineMatrixWindow and symbolMatrixWindow -- dcj
  [[[lineMatrixWindow contentView] docView]
                                   sizeTo:MAX(128.0+lineMatrixFrame.size.width,282.0)
                                         :92+lineMatrixFrame.size.height];
  [[[symbolMatrixWindow contentView] docView]
                                  sizeTo:MAX(128.0+symbolMatrixFrame.size.width,288.0)
                                        :92+symbolMatrixFrame.size.height];
  [[[legendFormWindow contentView] docView]
                                   sizeTo:32+legendFormFrame.size.width
                                         :192+legendFormFrame.size.height];

// Try sending windowDidResize (the "self" is bogus but does no harm) -- dcj
  [legendFormWindow windowDidResize:self];
  [lineMatrixWindow windowDidResize:self];
  [symbolMatrixWindow windowDidResize:self];

// Try to get the windows to display properly ... dcj
  [ [lineMatrix window] display];
  [ [symbolMatrix window] display];
  [ [legendForm window] display];

  return self;
}

// Go through a particular datahunk and find values for datamin.x,
// datamax.x, datamin.y, datamax.y
// We ignore any curves that are "turned off" (linestyle=NONE & symbolstyle=NONE);
// if all curves in the datahunk are turned off we ignore the x-data, too.
- findMinMax:(datahunk *)pdh
{
  int i, j;
  datahunk *qdh;
  int n, ncurve;
  BOOL ignore;

  pdh->datamin.x = MAXFLOAT;
  pdh->datamax.x = -MAXFLOAT;
  pdh->datamin.y = MAXFLOAT;
  pdh->datamax.y = -MAXFLOAT;

  // Find out which curves belong to datahunk pdh
  ncurve = 0;
  for (n=0; n<nfilestotal; n++) {
    qdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
    if (qdh == pdh)
      break;
    else
      ncurve += qdh->ncurves;
  }
  // Now we know to look at curves ncurve, ncurve+1,...,ncurve+pdh->ncurves-1
  ignore = YES;
  for (n = ncurve; n < ncurve + pdh->ncurves; n++) {
    if ([self providelinestyle:n] != NOLINE
	|| [self providesymbolstyle:n] != NOSYMBOL)
      ignore = NO;
  }
  // Now it's possible we want to ignore all the curves.
  if (ignore) return self;

  // If we don't ignore all the curves then we must look at the x data.
  for (i = 0; i < pdh->npoints; i++)  {
    pdh->datamin.x = MIN(pdh->datamin.x, pdh->x[i]);
    pdh->datamax.x = MAX(pdh->datamax.x, pdh->x[i]);
  }
  for (j = 0; j < pdh->ncurves; j++) {
    if ([self providelinestyle:(ncurve+j)] != NOLINE
	|| [self providesymbolstyle:(ncurve+j)] != NOSYMBOL) {
      for (i = 0; i < pdh->npoints; i++) {
	pdh->datamin.y = MIN(pdh->datamin.y, *(*(pdh->y+j)+i));
	pdh->datamax.y = MAX(pdh->datamax.y, *(*(pdh->y+j)+i));
      }
    }
  }
  return self;
}

- findGlobalMinMax
{
  int n;
  datahunk *pdh;

  globaldatamin.x = MAXFLOAT;
  globaldatamin.y = MAXFLOAT;
  globaldatamax.x = -MAXFLOAT;
  globaldatamax.y = -MAXFLOAT;
  for (n=0; n<nfilestotal; n++) {
    pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
    globaldatamin.x = MIN(globaldatamin.x, pdh->datamin.x);
    globaldatamax.x = MAX(globaldatamax.x, pdh->datamax.x);
    globaldatamin.y = MIN(globaldatamin.y, pdh->datamin.y);
    globaldatamax.y = MAX(globaldatamax.y, pdh->datamax.y);
  }
  return self;
}

// Get pleasing values for the min, max, and increments
- niceMinMaxInc
{
  float fmin, fmax, finc;

  fmin = globaldatamin.x;
  fmax = globaldatamax.x;
  if ([self xaxisLog] ) {
    computeNiceLogInc(&fmin, &fmax, &finc);
  }
  else {
    computeNiceLinInc(&fmin, &fmax, &finc);
  }
  [self resetXmin:fmin];
  [self resetXmax:fmax];
  [self resetXinc:finc];

  fmin = globaldatamin.y;
  fmax = globaldatamax.y;
  if ([self yaxisLog] ) {
    computeNiceLogInc(&fmin, &fmax, &finc);
  }
  else {
    computeNiceLinInc(&fmin, &fmax, &finc);
  }
  [self resetYmin:fmin];
  [self resetYmax:fmax];
  [self resetYinc:finc];
  return self;
}

// Use the OpenPanel object to get a filename
- open:sender
{
  static const char *const fileTypes[2] = {NULL, NULL};
				/* this is supposedly all ASCII files */
  char  fname[256];
  char  tempfname[256], command[512];
  id openPanel = [[OpenPanel new] allowMultipleFiles:NO];

  [openPanel setAccessoryView:nil]; /* may have to clean out an accessory view */
  if (nfilestotal == 0) {
    if (strncmp([errorBars title], "No error bars", 13) != 0) {
      [openPanel setTitle:"Open (error bars)"];
    }
    else {
      [openPanel setTitle:"Open"];	    /* make sure title is OK (cf. binary open) */
    }
  }
  else {
    if (strncmp([errorBars title], "No error bars", 13) != 0) {
      [openPanel setTitle:"Another (error bars)"]; /* 21 character limit here? */
    }
    else {
      [openPanel setTitle:"Open Additional File"];
    }
  }

  if ([openPanel runModalForTypes:fileTypes])  {
    strncpy(fname, (char *)[openPanel filename], 256);
    // Check to see if we are trying to open a compressed file:
    if (fname[strlen(fname)-1]=='Z' && fname[strlen(fname)-2]=='.') {
      // set plot button title:
      [plotButton setAltTitle:"Uncompressing"];
      [plotButton highlight:YES];
      NXPing();			/* force plotButton redraw */
      // Uncompress the file into a temporary file:
      strcpy(tempfname, "/tmp/file000000.xyp");
      NXGetTempFilename(tempfname, 9);
      sprintf(command, "zcat %s > %s\n", fname, tempfname);
      system(command);		/* no error checking */
      // Now just go ahead and open the temporary file:
      [self openFile:tempfname :fname];
      // After returning from the openFile it is safe to unlink:
      unlink(tempfname);
    }
    else {
      [self openFile:fname :fname];
    }
  }
  return self;
}

- openFile:(char *)dataFile :(char *)realName
{
  NXStream *dataStream;

  if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL)  {
    NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
    return self;
  }

  if ([self readData:dataStream :realName] == 0)  {
    NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
		    NULL, NULL, dataFile);
    NXCloseMemory(dataStream, NX_FREEBUFFER);
    return self;
  }
  NXCloseMemory(dataStream, NX_FREEBUFFER);
  [self plotPrepAndDraw];
  return self;
}

- openBinary:sender
{
  static const char *const fileTypes[2] = {NULL, NULL};
  char  fname[256];
  id openPanel = [[OpenPanel new] allowMultipleFiles:NO];

  [openPanel setAccessoryView:binaryOpenAccessory];
  if (strncmp([errorBars title], "No error bars", 13) != 0) {
    [openPanel setTitle:"Open Binary (error bars)"];
  }
  else {
    [openPanel setTitle:"Open Binary File"];
  }

  if ([openPanel runModalForTypes:fileTypes])  {
    if ([binaryOpenForm intValueAt:0] < 1) {
      NXRunAlertPanel("Binary Read",
		      "Number of curves is less than 1\n"
		      "Be sure to set this correctly",
		      "OK", NULL, NULL);
      return self;
    }
    strncpy(fname, (char *)[openPanel filename], 256);
    [self openBinaryFile:fname];
  }
  return self;
}

#import <sys/stat.h>
- openBinaryFile:(char *)dataFile
{
  struct stat filestat;
  int    filesize, numpoints;
  int    numcurves = [binaryOpenForm intValueAt:0];
  BOOL   xdatathere = [binaryXdatathere state];
  NXStream *dataStream;
  int    numcols;		/* number of "columns" expected in the data file */

  if (strncmp([errorBars title], "No error bars", 13) == 0) {
    numcols = numcurves;	/* no error bars */
  }
  else if (strncmp([errorBars title], "y only", 6) == 0) {
    numcols = 2*numcurves;
  }
  else if (strncmp([errorBars title], "x only", 6) == 0) {
    numcols = numcurves + 1;
  }
  else {
    numcols = 2*numcurves + 1;
  }

  stat(dataFile, &filestat);
  filesize = filestat.st_size;

  if (xdatathere) {
    // This consistency check is not sufficient to guarantee numcurves is
    // correct, but it's better than nothing.
    if ( (filesize % (sizeof(NXCoord)*(numcols+1))) != 0 ) {
      NXRunAlertPanel("Binary Read",
		      "File size inconsistent with number\n"
		      "of curves specified",
		      "OK", NULL, NULL);
      return self;
    }
    numpoints = filesize/(sizeof(NXCoord)*(numcols+1));
  }
  else {
    if ( (filesize % (sizeof(NXCoord)*numcols)) != 0 ) {
      NXRunAlertPanel("Binary Read",
		      "File size inconsistent with number\n"
		      "of curves specified",
		      "OK", NULL, NULL);
      return self;
    }
    numpoints = filesize/(sizeof(NXCoord)*(numcols));
  }
  
  if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL)  {
    NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
    return self;
  }

  if ([self readBinaryData:dataStream :dataFile :numcurves 
                          :numpoints :xdatathere] == 0)  {
    NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
		    NULL, NULL, dataFile);
    NXCloseMemory(dataStream, NX_FREEBUFFER);
    return self;
  }
  NXCloseMemory(dataStream, NX_FREEBUFFER);
  [self plotPrepAndDraw];

  return self;
}

- (int)readBinaryData:(NXStream *)aDataStream
                     :(char *)fname
                     :(int)numcurves
                     :(int)numpoints
                     :(BOOL)xdatathere
{
  int      j, oldncurves = ncurvestotal;
  datahunk *pdh = (void *)NULL;
  
  [self preludeToReading:fname :&pdh];	/* take care of some housekeeping */
  pdh->ncurves = numcurves;

  /* Now read the data into memory */
  NXSeek(aDataStream, 0L, NX_FROMSTART);
    
  pdh->x = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  if (pdh->has_exbars) {
    pdh->ex = (NXCoord *)malloc( numpoints * sizeof(NXCoord *) );
  }
  pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
  for (j = 0; j < pdh->ncurves; j++) {
    *(pdh->y+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
  }
  if (pdh->has_eybars) {
    pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
    for (j = 0; j < pdh->ncurves; j++) {
      *(pdh->ey+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
    }
  }
    
  pdh->npoints = numpoints;

  if (xdatathere) {
    NXRead(aDataStream, pdh->x, numpoints*sizeof(NXCoord));
  }
  else {
    for (j=0; j < numpoints; j++) {
      pdh->x[j] = (float)j;
    }
  }
  if (pdh->has_exbars) {
    NXRead(aDataStream, pdh->ex, numpoints*sizeof(NXCoord));
  }
  for (j = 0; j < pdh->ncurves; j++) {
    NXRead(aDataStream, *(pdh->y+j), numpoints*sizeof(NXCoord));
    if (pdh->has_eybars) {
      NXRead(aDataStream, *(pdh->ey+j), numpoints*sizeof(NXCoord));
    }
  }

  [self postludeToReading:fname :oldncurves :pdh];

  ncurvestotal += pdh->ncurves;

  return pdh->npoints;
}


- postludeToReading:(char *)fname :(int)oldncurves :(datahunk *)pdh
{
  int   j;
  float hue = 0.0;
#define N_PREDEFINED_COLORS 9
  NXColor predefined_colors[] = {NX_COLORRED, NX_COLORGREEN, NX_COLORBLUE,
			         NX_COLORCYAN, NX_COLORYELLOW, NX_COLORMAGENTA,
			         NX_COLORORANGE, NX_COLORPURPLE, NX_COLORBROWN};
// Those colors come from /usr/include/appkit/color.h

  /* Adjust the lineMatrix, symbolMatrix, and legendForm */
  [self adjustPanels:oldncurves :pdh->ncurves];

  if ([columnPanel isVisible])
    [columnSelectionHandler fixPanel:self];

  /*
   * Error bars don't get drawn unless the error bar matrix is correct.
   * Therefore we don't test on visibility here, rather we test on pdh itself.
   */
  if (pdh->has_exbars || pdh->has_eybars)
    [errorBarHandler updatePanel:self];

  if ([fileRemovalPanel isVisible])
    [self fixFileRemovalPanel:self];

  /* reset plot button */
  [plotButton setAltTitle:"Plotting"];
  [plotButton highlight:NO];

  [self findMinMax:pdh];
  /*
   * Don't bother to check lin/log unless this is the first file or
   * we already have at least one logarithmic axis:
   */
  if (nfilestotal==1 || [self xaxisLog] || [self yaxisLog] ) {
    [self checkLinLog:pdh];
  }
  else {
    pdh->xaxislin = YES;
    pdh->yaxislin = YES;
  }

  curvecolors = (NXColor *)realloc((void *)curvecolors,
                              (ncurvestotal + pdh->ncurves) * sizeof(NXColor));

  [[canvas window] setTitleAsFilename:fname];

  if (!colorOption) {
    for (j=0; j<pdh->ncurves; j++) {
      curvecolors[j + ncurvestotal] = NX_COLORBLACK;
    }
  }
  else {			/* adjust all the curve colors */
//    for (j=0; j<pdh->ncurves + ncurvestotal; j++) {
//      hue = 0.6667 * (float)((j+1) % MIN(11, ncurvestotal + pdh->ncurves))
//                       / (float)MIN(10, ncurvestotal + pdh->ncurves);
//      curvecolors[j] = NXConvertHSBToColor(hue, 1.0, 1.0);
//      /* update curve no. on color panel */
//      [curveNumber setIntValue:j+1];
//      /* and update the color */
//      [curveColorWell setColor:curvecolors[j]];
//    }
// The preceding has the unfortunate effect of changing colors on an already-plotted
// curve if a new file is read in.  This is undesirable.  Here is a more
// primitive method.  Maybe a method based on (repeatable) random number
// generation would be appropriate.
//    for (j=0; j<pdh->ncurves; j++) {
//      hue = 0.6667 * (float)((j+ncurvestotal+1) % 6) / 5.0;
//      curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
//      /* update curve no. on color panel */
//      [curveNumber setIntValue:j+ncurvestotal+1];
//      /* and update the color */
//      [curveColorWell setColor:curvecolors[j+ncurvestotal]];
//    }
// All right, here is the method based on random numbers.  Maybe this will be OK.
// The trouble with it is that with srandom(10) [see above -- this was about
// the best] the first two curves are about the same shade of green.  Now the
// strategy is to preset the first few curve colors, after that they will be
// random.
    for (j=0; j<pdh->ncurves; j++) {
      if (j + ncurvestotal < N_PREDEFINED_COLORS) {
	curvecolors[j + ncurvestotal] = predefined_colors[j + ncurvestotal];
      }
      else {
	hue = 0.8 * ((float)random())/2147483647.0 ; /* 2^31 - 1 */
	curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
      }
      /* update curve no. on color panel */
      [curveNumber setIntValue:j+ncurvestotal+1];
      /* and update the color */
      [curveColorWell setColor:curvecolors[j+ncurvestotal]];
    }
  }

  return self;
}

- preludeToReading:(char *)fname :(datahunk **)pdh
{
  datahunk dh;

  /* set plot button title "Reading" */
  [plotButton setAltTitle:"Reading"];
  [plotButton highlight:YES];
  NXPing();			/* force plotButton redraw */
  
  if (nfilestotal == 0) {
    datahunkArray = [Storage newCount:1
	  elementSize:sizeof(datahunk)
	  description:"{*{float *}{float *}{float **}{float **}iiffff{BOOL}{BOOL}{BOOL}{BOOL}}"];
    *pdh = (datahunk *)[datahunkArray elementAt:0];
    if (*pdh == NULL) {
      NXRunAlertPanel("readData",
		      "Weird error 0: NULL pointer in readData\n"
		      "I can't continue",
		      "OK", NULL, NULL);
      exit(0);
    }
    nfilestotal = 1;
  }
  else {
    [datahunkArray addElement:(void *)&dh];
    *pdh = (datahunk *)[datahunkArray elementAt:(unsigned)nfilestotal];
    if (*pdh == NULL) {
      NXRunAlertPanel("readData",
		      "Weird error 1: NULL pointer in readData\n"
		      "I can't continue",
		      "OK", NULL, NULL);
      exit(0);
    }
    nfilestotal++;
  }
  (*pdh)->filename = (char *)malloc(strlen(fname) + 1);
  strncpy((*pdh)->filename, fname, strlen(fname) + 1);

  (*pdh)->has_exbars = NO;
  (*pdh)->has_eybars = NO;
  if (strncmp([errorBars title], "y only", 6) == 0) {
    (*pdh)->has_eybars = YES;
  }
  else if (strncmp([errorBars title], "x only", 6) == 0) {
    (*pdh)->has_exbars = YES;
  }
  else if (strncmp([errorBars title], "x and y", 7) == 0) {
    (*pdh)->has_exbars = YES;
    (*pdh)->has_eybars = YES;
  }

  return self;
}

- plotPrepAndDraw
{
  /* Check for linear or log on x and y axes */
  [self checkGlobalLinLog];

  [self findGlobalMinMax];

  /* Only check and reset min/max if this is the first file */
  if (nfilestotal == 1) {
    [self niceMinMaxInc];
  }

  [self drawPlot:self];
    
  return self;
}

- writeDataFiles:sender
{
  int i, j, n;
  datahunk *pdh;
  NXStream *outputStream;
  char filename[1024], paneltitle[256];
  id    savePanel = [[SavePanel new] setRequiredFileType:""];

  if (nfilestotal == 0) {
    NXRunAlertPanel("Write Data", "No data", "OK", NULL, NULL);
  }
  else {
    for (n=0; n<nfilestotal; n++) {
      pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
      sprintf(paneltitle, "Save file %d (%s)", n+1,
	      strrchr(pdh->filename,'/')==NULL ? pdh->filename
	                                       : strrchr(pdh->filename,'/')+1);
      [savePanel setTitle:paneltitle];
      [savePanel setAccessoryView:writeDataAccButton];
      if ([savePanel runModal]) {
	strncpy(filename, [savePanel filename], 1024);
	if ((outputStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL) {
	  NXRunAlertPanel("Write Data", "Cannot open memory for file %d",
			  "OK", NULL, NULL, n);
	  return self;
	}
	if ([writeDataAccButton state]) { /* ascii write */
	  for (i=0; i<pdh->npoints; i++) {
	    NXPrintf(outputStream, "%g", pdh->x[i]);
	    if (pdh->has_exbars) {
	      NXPrintf(outputStream, " %g", pdh->ex[i]);
	    }
	    for (j=0; j<pdh->ncurves; j++) {
	      NXPrintf(outputStream, " %g", *(*(pdh->y+j)+i));
	      if (pdh->has_eybars) {
		NXPrintf(outputStream, " %g", *(*(pdh->ey+j)+i));
	      }
	    }
	    NXPrintf(outputStream, "\n");
	  }
	}
	else {			/* binary write */
	  NXWrite(outputStream, pdh->x, (pdh->npoints)*sizeof(NXCoord));
	  if (pdh->has_exbars) {
	    NXWrite(outputStream, pdh->ex, (pdh->npoints)*sizeof(NXCoord));
	  }
	  for (j=0; j<pdh->ncurves; j++) {
	    NXWrite(outputStream, *(pdh->y+j), (pdh->npoints)*sizeof(NXCoord));
	    if (pdh->has_eybars) {
	      NXWrite(outputStream, *(pdh->ey+j), (pdh->npoints)*sizeof(NXCoord));
	    }
	  }
	}
	NXFlush(outputStream);
	NXSaveToFile(outputStream, filename);
	NXClose(outputStream);
      }
    }
  }
  return self;
}
    
- whyTheBeep:sender
{
  switch(beepError) {
  case 0:
    NXRunAlertPanel("The Beep Panel", "Press this button if the program beeps\n"
       "and you want to know why", "OK", NULL, NULL);
    break;
  case 1:
    NXRunAlertPanel("The Beep Happened Because:",
		    "x-axis changed from log to linear", "OK", NULL, NULL);
    break;
  case 2:
    NXRunAlertPanel("The Beep Happened Because:",
		    "y-axis changed from log to linear", "OK", NULL, NULL);
    break;
  case 3:
    NXRunAlertPanel("The Beep Happened Because:",
		    "x-increment was negative\n (I changed it)", "OK", NULL, NULL);
    break;
  case 4:
    NXRunAlertPanel("The Beep Happened Because:",
		    "xmax was less than xmin\n (I changed them)", "OK", NULL, NULL);
    break;
  case 5:
    NXRunAlertPanel("The Beep Happened Because:",
		    "y-increment was negative\n (I changed it)", "OK", NULL, NULL);
    break;
  case 6:
    NXRunAlertPanel("The Beep Happened Because:",
		    "ymax was less than ymin\n (I changed them)", "OK", NULL, NULL);
    break;
  case 7:
    NXRunAlertPanel("The Beep Happened Because:",
		    "Too many tic marks would have been on the x-axis\n"
		    " (I changed the min, max, and/or increment)",
		    "OK", NULL, NULL);
    break;
  case 8:
    NXRunAlertPanel("The Beep Happened Because:",
		    "Too many tic marks would have been on the y-axis\n"
		    " (I changed the min, max, and/or increment)",
		    "OK", NULL, NULL);
    break;
  case 9:
    NXRunAlertPanel("The Beep Happened Because:",
		    "x-axis was logarithmic but some datum was negative\n"
		    " (I changed x-axis to linear)", "OK", NULL, NULL);
    break;
  case 10:
    NXRunAlertPanel("The Beep Happened Because:",
		    "y-axis was logarithmic but some datum was negative\n"
		    " (I changed y-axis to linear)", "OK", NULL, NULL);
    break;
  case 11:
    NXRunAlertPanel("The Beep Happened Because:",
		    "tried to set color of a non-existent curve", "OK", NULL, NULL);
    break;
  case 12:
    NXRunAlertPanel("The Beep Happened Because:",
		    "an increment was too small for a log axis\n"
		    " (I reset it)", "OK", NULL, NULL);
    break;
  }
  return self;
}

- (NXColor) provideBackgroundColor
{
  if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
      && ([accPrintColorButton state] == 0) ) {
    return NX_COLORWHITE;
  }
  else {
    return backgroundcolor;
  }
}

- (NXColor) provideTextColor
{
  if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
      && ([accPrintColorButton state] == 0) ) {
    return NX_COLORBLACK;
  }
  else {
    return textcolor;
  }
}

- (NXColor) provideCurveColor:(int)aCurve
{
  if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
      && ([accPrintColorButton state] == 0) ) {
    return NX_COLORBLACK;
  }
  else {
    return curvecolors[aCurve];
  }
}

- setBackgroundColor:sender
{
  backgroundcolor = [sender color];
  return self;
}

- forceBackgroundColor:(NXColor) aColor
{
  backgroundcolor = aColor;
  return self;
}

- forceTextColor:(NXColor) aColor
{
  textcolor = aColor;
  return self;
}

- forceCurveColor:(int)curvenum :(NXColor)aColor
{
  if (curvenum >= ncurvestotal) {
    NXBeep();
    beepError = 11;
  }
  else {
    curvecolors[curvenum] = aColor;
  }
  return self;
}


- setTextColor:sender
{
  textcolor = [sender color];
  return self;
}

- setCurveColor:sender
{
  int curvenum = [curveNumber intValue];

  if (curvenum > ncurvestotal || curvenum < 1) {
    NXBeep();
    beepError = 11;
    return self;
  }
/* Change the color in the color well right away: */
  [sender setColor:[sender color]];
  NXPing();			/* make sure it gets displayed */
  curvecolors[curvenum-1] = [sender color];
/* Try to be helpful and increment the curvenumber */
  curvenum = (curvenum == ncurvestotal? 1 : curvenum + 1);
/* Slight delay (1/2 second) so the well doesn't change instantly.  */
  usleep((unsigned)500000);
  [curveNumber setIntValue:curvenum];
/* Tell the sender (a color well) to go on the next color: */
  [sender setColor:curvecolors[curvenum - 1]];
  return self;
}

/* Update the curveColorWell if the curve number is changed */
- textDidEnd:textObject endChar:(unsigned short)whyEnd
{
  int curvenum = [curveNumber intValue];
  if (curvenum > ncurvestotal || curvenum < 1) {
    NXBeep();
    beepError = 11;
    return self;
  }
  [curveColorWell setColor:curvecolors[curvenum-1]];
  NXPing();			/* make sure it gets displayed */
  return self;
}

/* Update the global colorOption variable. */
- colorOn:(BOOL)onOff
{
  if (onOff) {
    colorOption = YES;
    backgroundcolor = NX_COLORBLACK;
    textcolor = NX_COLORWHITE;
  }
  else {
    colorOption = NO;
    backgroundcolor = NX_COLORWHITE;
    textcolor = NX_COLORBLACK;
  }
  [textColorWell setColor:textcolor];
  [backgroundColorWell setColor:backgroundcolor];
  [curveColorWell setColor:textcolor];

  return self;
}

- fixMatrixColumn:sender	/* The sender is a matrix */
{
  int row, col, i;

  row = [sender selectedRow];
  col = [sender selectedCol];
  if (sender == lineMatrix) {
    [self adjustLineStyleMatrix:col :row];
    // Now, instead of redisplaying the whole matrix, just redraw the
    // cells in the affected column.  It's much faster this way.
    for (i=0; i<N_LINE_STYLES; i++)
      [lineMatrix drawCellAt:i :col];
  }
  else if (sender == symbolMatrix) {
    [self adjustSymbolTypeMatrix:col :row];
    // Comment above applies here, too.
    for (i=0; i<N_SYMBOL_STYLES; i++)
      [symbolMatrix drawCellAt:i :col];
  }
  return self;
}

/* change the column which is to be taken as the x data */
- swapColumns:(int)prev_col :(int)col forFileNumber:(int)i
{
  datahunk *pdh;
  float    *tmp;

  if (col < 0 || prev_col < 0)	/* be very cautious */
    return self;

  if (col==0 && prev_col==0)	/* must avoid this special case */
    return self;

  pdh    = (datahunk *)[datahunkArray elementAt:(unsigned)i];
  if (prev_col == 0) {		/* was first column, generic x-data */
//  tmp    = pdh->x;		/* why doesn't this order work? */
//  pdh->x = *(pdh->y + col-1);
//  *(pdh->y + col-1) = tmp;
    tmp    = *(pdh->y + col-1);
    *(pdh->y + col-1) = pdh->x;
    pdh->x = tmp;
  }
  else if (col == 0) {		/* revert back to generic x-data */
    tmp    = *(pdh->y + prev_col - 1);
    *(pdh->y + prev_col - 1) = pdh->x;
    pdh->x = tmp;
  }
  else {			/* swapping y's only */
//    tmp    = *(pdh->y + prev_col - 1);
//    *(pdh->y + prev_col - 1) = *(pdh->y + col - 1);
//    *(pdh->y + col - 1) = tmp;
    tmp    = *(pdh->y + col - 1);
    *(pdh->y + col - 1)      = *(pdh->y + prev_col - 1);
    *(pdh->y + prev_col - 1) = pdh->x;
    pdh->x = tmp;
  }

  [self findMinMax:pdh];	/* reset these values here */
  [self findGlobalMinMax];

  return self;
}


/*
 * This function is called by the PlotView object during zooming.
 */
- stackOldMinMax:(float)xmin :(float)xmax :(float)ymin :(float)ymax
{
  oldMin = currentMin;		/* structure assignment */
  oldMax = currentMax;
  oldInc = currentInc;		/* have to deal with the increments, too */
  currentMin.x = xmin;
  currentMax.x = xmax;
  currentMin.y = ymin;
  currentMax.y = ymax;
  currentInc.x = [xInc floatValueAt:0];
  currentInc.y = [yInc floatValueAt:0];

  return self;
}

/*
 * This method is invoked by the "Previous View" button on the
 * control panel.
 */
- previousView:sender
{
  NXPoint tmp;

  [self resetXmin:(double)oldMin.x];
  [self resetXmax:(double)oldMax.x];
  [self resetXinc:(double)oldInc.x];
  [self resetYmin:(double)oldMin.y];
  [self resetYmax:(double)oldMax.y];
  [self resetYinc:(double)oldInc.y];
  tmp        = oldMin;
  oldMin     = currentMin;
  currentMin = tmp;
  tmp        = oldMax;
  oldMax     = currentMax;
  currentMax = tmp;
  tmp        = oldInc;
  oldInc     = currentInc;
  currentInc = tmp;
  [self drawPlotButton:1];	/* display "Plotting" */
  [canvas display];
  [self drawPlotButton:0];	/* display "Plot" */

  return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.