
/* Generated by Interface Builder */

#import "Plot.h"
#import "NXYView.h"		/*  only for initializeLegendBox */
#import <appkit/Matrix.h>
#import <appkit/Cell.h>
#import <math.h>
#import <appkit/OpenPanel.h>
#import <appkit/publicWraps.h>  //  For NXBeep (from 1.7)

#define ALLOCSIZE 2048		/* used in malloc in readData */

//Added for 1.7 functions
int beepError=0;

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


@implementation Plot

- (NXCoord *)xdata  { return x;}
- (NXCoord **)ydata { return y;}

- (int)nPoints    { return npoints;}
- (int)nCurves    { return ncurves;}

//- (const char *)provideXtitle 
//   {return [xTitle stringValueAt:0];}
//   {return [canvas xaxisLabel];}
//- (const char *)provideYtitle  
//  {return [yTitle stringValueAt:0];}
 // {return [canvas yaxisLabel];}
//- (const char *)provideMaintitle 
// {return [mainTitle stringValueAt:0];}
 //{return [canvas mainTitle];}

- (BOOL) shouldDrawGrid
{
//  if ( [gridOnOff state] ) return YES;
  if ( [canvas gridState] ) return YES;
  else return NO;
}

- (BOOL) shouldDrawBox
{
//  if ( [borderBoxOnOff state] ) return YES;
  if ( [canvas borderState] ) return YES;
  else return NO;
}


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

- forceXaxisLinear
{
//  [xLinLog setState:0];
//  [xLinLog display];
	[canvas setXLinLogState:LINEAR];
  return self;
}

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

- forceYaxisLinear
{
//  [yLinLog setState:0];
//  [yLinLog display];
	[canvas setYLinLogState:LINEAR];
  return self;
}

- (int)providelinestyle:(int)aCurve
{
      return 0;			/* for safety */
}

- (int)providesymbolstyle:(int)aCurve
{
      return 0;			/* for safety */
}

- (int)providesymbolsize{  return 0;}
- (int)providelinethickness{  return 0;}

//- (float)provideXmin  {return [xMin floatValueAt:0];}
- (float)provideXmin  {return [canvas xMinValue];}
//- (float)provideXmax  {return [xMax floatValueAt:0];}
- (float)provideXmax  {return [canvas xMaxValue];}
//- (float)provideXinc  {return [xInc floatValueAt:0];}
- (float)provideXinc  {return [canvas xIncValue];}
//- (float)provideYmin  {return [yMin floatValueAt:0];}
- (float)provideYmin  {return [canvas yMinValue];}
//- (float)provideYmax  {return [yMax floatValueAt:0];}
- (float)provideYmax  {return [canvas yMaxValue];}
//- (float)provideYinc  {return [yInc floatValueAt:0];}
- (float)provideYinc  {return [canvas yIncValue];}

//- resetXmin:(float)aNum { [xMin setFloatValue:aNum at:0]; return self; }
- resetXmin:(float)aNum { [canvas setXminValue:aNum]; return self; }
//- resetXmax:(float)aNum { [xMax setFloatValue:aNum at:0]; return self; }
- resetXmax:(float)aNum { [canvas setXmaxValue:aNum]; return self; }
//- resetXinc:(float)aNum { [xInc setFloatValue:aNum at:0]; return self; }
- resetXinc:(float)aNum { [canvas setXincValue:aNum]; return self; }
//- resetYmin:(float)aNum { [yMin setFloatValue:aNum at:0]; return self; }
- resetYmin:(float)aNum { [canvas setYminValue:aNum]; return self; }
//- resetYmax:(float)aNum { [yMax setFloatValue:aNum at:0]; return self; }
- resetYmax:(float)aNum { [canvas setYmaxValue:aNum]; return self; }
//- resetYinc:(float)aNum { [yInc setFloatValue:aNum at:0]; return self; }
- resetYinc:(float)aNum { [canvas setYincValue:aNum]; return self; }

- drawPlot:sender
{
//    if (!x) return self;

  [self sanityCheck];		/* disallow various bad parameters */
    [canvas display];
    return self;
}

// Allocate enough memory and read the data points
/*
 * WARNING: This code will go into an infinite loop if there is illegal
 * crud in the data file (such as a comma, for example).  The culprit is
 * the "fscanf" below; we will put up with this for now, but we should
 * make this more robust.
 */
- (int) readDataFromFile:(FILE *)aDataStream
{
    BOOL    inword = NO;
    char    c;
    int     j, size = ALLOCSIZE;
//    int     oldncurves = ncurves; /* for linestyle matrix column deleting */

    NXPing();			/* Probably not needed now--was for Plot Button */

    /* First, free x and y here if there's already data in them */
    if (x) {
      free( (void *)x );
      for (j = 0; j < ncurves; j++) {
	free( (void *)(*(y+j)) );
      }
      free( (void *)y );
    }

    /* 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.                                    */
    /* For now we assume the input file is an ascii file.                 */
    ncurves = -1;
    while (1) {
      c = getc(aDataStream);
      if (c == '\n') {
	break;			/* breaks out of while loop */
      }
      if ((inword==NO) && !(c==' ' || c=='\t')) {
	ncurves++;
	inword = YES;
      }
      if ((inword==YES) && (c==' ' || c=='\t')) {
	inword = NO;
      }
    }
    if (ncurves == -1) {	/* couldn't find "\n", give up */
      return 0;
    }

    /* Now read the data into memory */
    rewind(aDataStream);
    
    y = (NXCoord **)malloc( ncurves * sizeof(NXCoord *) );
    x = (NXCoord *)malloc( size * sizeof(NXCoord) );
    for (j = 0; j < ncurves; j++) {
      *(y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
    }
    npoints = 0;
    while(1) {
      if( (fscanf(aDataStream, "%f", x+npoints)) == EOF ) {
	break;			/* breaks out of the while loop */
      }
      for (j = 0; j < ncurves; j++) {
	fscanf(aDataStream, "%f", *(y+j)+npoints);
      }
      npoints++;
      if (npoints == size) {		/* get more memory */
	size += ALLOCSIZE;
	x = (NXCoord *)realloc(x, size * sizeof(NXCoord));
	for (j = 0; j < ncurves; j++) {
	  *(y+j) = (NXCoord *)realloc( *(y+j), size * sizeof(NXCoord) );
	}
      }      
    }

     return npoints;
}

// Following was added by cwf. It is just the above method modified for a NXStream
// This should be replaced by the equivalent code from nxyplot1.7
/*
 * WARNING: This code will go into an infinite loop if there is illegal
 * crud in the data file (such as a comma, for example).  The culprit is
 * the "fscanf" below; we will put up with this for now, but we should
 * make this more robust.
 */
- (int) readDataFromStream:(NXStream *)aDataStream;
{
    BOOL    inword = NO;
    char    c;
    int     j, size = ALLOCSIZE;
//    int     oldncurves = ncurves; /* for linestyle matrix column deleting */

    NXPing();			/* Probably not needed now--was for Plot Button */

    /* First, free x and y here if there's already data in them */
    if (x) {
      free( (void *)x );
      for (j = 0; j < ncurves; j++) {
	free( (void *)(*(y+j)) );
      }
      free( (void *)y );
    }

    /* 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.                                    */
    /* For now we assume the input file is an ascii file.                 */
    ncurves = -1;
    while (1) {
      c = NXGetc(aDataStream);
      if (c == '\n') {
	break;			/* breaks out of while loop */
      }
      if ((inword==NO) && !(c==' ' || c=='\t')) {
	ncurves++;
	inword = YES;
      }
      if ((inword==YES) && (c==' ' || c=='\t')) {
	inword = NO;
      }
    }
    if (ncurves == -1) {	/* couldn't find "\n", give up */
      return 0;
    }

    /* Now read the data into memory */
    NXSeek(aDataStream,0L,NX_FROMSTART);
    
    y = (NXCoord **)malloc( ncurves * sizeof(NXCoord *) );
    x = (NXCoord *)malloc( size * sizeof(NXCoord) );
    for (j = 0; j < ncurves; j++) {
      *(y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
    }
    npoints = 0;
    while(1) {
      if( (NXScanf(aDataStream, "%f", x+npoints)) == EOF ) {
	break;			/* breaks out of the while loop */
      }
      for (j = 0; j < ncurves; j++) {
	NXScanf(aDataStream, "%f", *(y+j)+npoints);
      }
      npoints++;
      if (npoints == size) {		/* get more memory */
	size += ALLOCSIZE;
	x = (NXCoord *)realloc(x, size * sizeof(NXCoord));
	for (j = 0; j < ncurves; j++) {
	  *(y+j) = (NXCoord *)realloc( *(y+j), size * sizeof(NXCoord) );
	}
      }      
    }

     return npoints;
}


/* Might want to make sure INLINE_MATH is defined when math.h is included */

- checkLinLog
{
  /* 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 (datamax.x > 0.0  &&  datamin.x > 0.0  && datamax.x != datamin.x) {
    scale = fabs( (double)datamax.x - (double)datamin.x );
    linsum = 0.0;
    for (j=1; j<npoints; j++) {
      tmp = ( (double)x[j]-(double)x[j-1] )/(double)scale; /* avoid overflow */
      linsum += tmp*tmp;
    }
    scale = log10( (double)datamax.x/(double)datamin.x );
				/* what if datamax.x<datamin.x? */
    logsum = 0.0;
    for (i=1; i<npoints; i++) {
      tmp = log10( (double)x[i]/(double)x[i-1] ) / scale;
      logsum += tmp*tmp;
    }
    if (linsum < logsum) {
//      [xLinLog setState:0];	/* linear axis */
	[canvas setXLinLogState:LINEAR];
    }
    else {
//      [xLinLog setState:1];	/* logarithmic axis */
	[canvas setXLinLogState:LOG];      
    }
  }
  else {
//    [xLinLog setState:0];	/* linear */
	[canvas setXLinLogState:LINEAR];    
  }
  /* Now test y axis */
  if (datamax.y > 0.0  &&  datamin.y > 0.0  && datamax.y != datamin.y) {
    scale = fabs( (double)datamax.y - (double)datamin.y );
    linsum = 0.0;
    for (j=0; j<ncurves; j++) {
      for (i=1; i<npoints; i++) {
	tmp = ( (double)*(*(y+j)+i) - (double)*(*(y+j)+i-1) )
	                    / scale; /* avoid overflow */
	linsum += tmp*tmp;
      }
    }
    scale = log10((double)datamax.y/(double)datamin.y);
				/* what if datamax.y<datamin.y? */
    logsum = 0.0;
    for (j=0; j<ncurves; j++) {
      for (i=1; i<npoints;i++) {
	tmp = log10( (double)*(*(y+j)+i)/(double)*(*(y+j)+i-1) ) / scale;
	logsum += tmp*tmp;
      }
    }
    if (linsum < logsum) {
//      [yLinLog setState:0];	/* linear axis */
	[canvas setYLinLogState:LINEAR];
    }
    else {
//      [yLinLog setState:1];	/* logarithmic axis */
	[canvas setYLinLogState:LOG];
    }
  }
  else {
//    [yLinLog setState:0];	/* linear */
	[canvas setYLinLogState:LINEAR];
  }
//  [xLinLog display];
//  [yLinLog display];
  return self;
}

// Go through the data set and find values for datamin.x,
// datamax.x, datamin.y, datamax.y
- findMinAndMax
{
    if (x)  {
      int	i, j;
      datamin.x = datamax.x = x[0];
      for (i = 1; i < npoints; i++)  {
	if (x[i] < datamin.x)
	  datamin.x = x[i];
	else if (x[i] > datamax.x)
	  datamax.x = x[i];
      }
      datamin.y = datamax.y = *(*(y+0)+0);
      for (j = 0; j < ncurves; j++) {
	for (i = 0; i < npoints; i++) {
	  if (*(*(y+j)+i) < datamin.y)
	    datamin.y = *(*(y+j)+i);
	  else if (*(*(y+j)+i) > datamax.y)
	    datamax.y = *(*(y+j)+i);
	}
      }
    }
// reset min and max here (may want to change the placement of this)
//    [self resetXmin:datamin.x];
 //   [self resetXmax:datamax.x];
//    [self resetXinc:(datamax.x - datamin.x)/5.0];
//    [self resetYmin:datamin.y];
//    [self resetYmax:datamax.y];
//    [self resetYinc:(datamax.y - datamin.y)/5.0];

    return self;
}

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

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

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

//Added from nxyplot 1.7 (Beep code "removed" for now)
// 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 */
  if ( [self xaxisLog] ) {
    if (datamin.x <= 0.0 || xmin <= 0.0 || xmax <= 0.0) {
 //     [xLinLog setState:0];	/* back to linear */
 	[canvas setXLinLogState:0];    //Changes above line since there is no control panel
      NXBeep();			/* audible alert */
      beepError = 1;
    }
  }
  if ( [self yaxisLog] ) {
    if (datamin.y <= 0.0 || ymin <= 0.0 || ymax <= 0.0) {
//      [yLinLog setState:0];	/* back to linear */
 	[canvas setYLinLogState:0];    //Changes above line since there is no control panel      
      NXBeep();			/* audible alert */
      beepError = 2;
    }
  }
  /* 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;
    }
  }
  // This check not implemented in palette
  /* Another check: the axes should be in the frame box. */
//  if ([self shouldDrawAxes] && (ymin>0.0 || ymax<0.0 || xmin>0.0 || xmax<0.0)) {
//    NXBeep();
//    beepError = 14;		/* this is only a warning message */
//  }

  return self;
}

// The next method (whyTheBeep) was added from 1.7, but must be implemented by the user
// Look for the key words "NXBeep()" and "beepError", some have been commented out
- 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:",
                    "box thickness was negative\n"
                    " (I changed it to a positive number)", "OK", NULL, NULL);
    break;
  case 13:
    NXRunAlertPanel("The Beep Happened Because:",
                    "axis thickness was negative\n"
                    " (I changed it to a positive number)", "OK", NULL, NULL);
    break;
  case 14:
    NXRunAlertPanel("The Beep Happened Because:",
		    "axis drawing was requested and at least\n"
		    "one axis is outside the framing box", "OK", NULL, NULL);
    break;
  }
  return self;
}

// Use the OpenPanel object to get a filename
- open:sender
{
    char const	*fileTypes[2] = {0,0};	// this type is all ASCII files (?)
//    char const	*fileTypes[2] = {"xyp",0};
//          this would be only files with an xyp extension
    char	fname[1024];

    id openPanel = [OpenPanel new];
    if ([openPanel runModalForTypes:fileTypes])  {
      strncpy(fname, [openPanel filename], 1024);
      [self openFile:fname];
    }
    return self;
}

- openFile:(char *)dataFile
{
    FILE	*dataStream;

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

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

//    [canvas initializeLegendBox];

if([canvas autoMaxMinState] == YES)
{
// Get Max and Min from data
   [self findMinAndMax];
   [self niceMinMaxInc];
}    
//else
// Use values set in Inspector
//	[canvas setMaxMinValues];

    /* Check for linear or log on x and y axes */
//    [self checkLinLog];
	if([canvas autoPaperState])
{
		[self checkLinLog];
	        [self niceMinMaxInc];		
}	
    [self drawPlot:self];
    
    return self;
}


// The following was added to use a stream for data to plot which has been sent from
// some controling App--this will(should) be replaced by later nxyplot (1.7) code since
// it uses streams for all data plots.
-useDataStreamAndPlot:(NXStream *)dataStream
{
	[self  readDataFromStream:dataStream];

if([canvas autoMaxMinState] == YES)
{
    [self findMinAndMax];
    [self niceMinMaxInc];		
}
    /* Check for linear or log on x and y axes */
	if([canvas autoPaperState])
	{
		[self checkLinLog];
	        [self niceMinMaxInc];		
	}		
    [self drawPlot:self];

return self;
}


//This archiving is unnecessary for the most part (canvas and ?? need to be archived,
// the other instance variables could be removed.) Just here on the off chance I figure
// out how to add a control panel.
- read:(NXTypedStream*)stream
/*
 * Unarchives the PlotView.  Initializes four of the PlotView's instance variables
 * to the values stored in the stream. 
 */
{
	[super read:stream];
	NXReadTypes(stream, "@", &canvas);
		
	return self;
}

- write:(NXTypedStream*)stream
/*
 * Archives the PlotView by writing its important instance variables to the stream. 
 */
{
	[super write:stream];
	NXWriteTypes(stream, "@", &canvas);
	
	return self;
}




@end
