This is Plot.m in view mode; [Download] [Up]
/* 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.