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.