This is ContourPlotApp.m in view mode; [Download] [Up]
/* Test application for ContourView.m class */
/* Izumi Ohzawa 92-5-22 */
#import <appkit/appkit.h>
#import "ContourPlotApp.h"
#import "ContourView.h"
#import "CurveView.h"
#import "version.h"
#import <stdio.h>
#import <stdlib.h>
#import <math.h>
#define PI 3.141592654
@implementation ContourPlotApp
-appDidInit:sender
{
static NXDefaultsVector CPDefaults = {
{"SaveDir", "~"},
{NULL}
};
NXRegisterDefaults(APPNAME, CPDefaults);
zdata = NULL;
// #### Some default settings which may be overridden later. ####
nxInterp = 60;
nyInterp = 60;
xgstep = ygstep = 1.0;
manual_scaling = 0; // auto
zrange_in_file = 0;
min_zval = 0.0;
max_zval = 1.0;
xt = yt = NULL;
xcurve = ycurve = NULL;
[NXApp setDelegate:self];
openPanel = [OpenPanel new]; // just caching
[brightCWell setColor:NX_COLORRED];
[darkCWell setColor:NX_COLORBLUE];
[bgCWell setColor:NX_COLORWHITE];
[lineCWell setColor:NX_COLORBLACK];
[self colorWellsAction:self]; // reflect color well settings
[versionField setStringValue: VERSION];
[xposTextField setFloatingPointFormat:YES left:3 right:2];
[yposTextField setFloatingPointFormat:YES left:3 right:2];
[maxZplotField setFloatingPointFormat:YES left:3 right:2];
[minZplotField setFloatingPointFormat:YES left:3 right:2];
[maxZdataField setFloatingPointFormat:YES left:3 right:2];
[minZdataField setFloatingPointFormat:YES left:3 right:2];
// [contourView setDebugEnable:YES]; // only for debugging
[contourView setDelegate:self]; // set ContourView's delegete to myself
[contourView enableContinuousNotify:YES];
[contourView setBipolar:YES];
[contourView setEnabled: NO]; // start up disabled
[contourView setFillEnable:YES]; // if color fill should be used
[contourView setMinNumberOfPointsPerContour: 4]; // (optional) eliminates tiny noise
[[contourView window] makeKeyAndOrderFront:self];
return self;
}
- actionPrintCrossHair:sender
{
if([sender state])
[contourView enablePrintCrossHair:YES];
else
[contourView enablePrintCrossHair:NO];
return self;
}
- markerDidMove:(int)viewtag :(int)marker :(float)x :(float)y :(int)mup
{
// Marker 2 is generated by drag of cross point.
if(marker == 0 || marker == 2) {
[yposTextField setFloatValue: y]; // Y coord of Horiz line
[contourView horizCrossSectionAtY:y :nxInterp forXs:xt :xcurve];
[horizCSView setNewTrace: xcurve];
[horizCSView drawIt:self];
}
if(marker == 1 || marker == 2) {
[xposTextField setFloatValue: x]; // X coord of Vertical line
[contourView vertCrossSectionAtX:x :nyInterp forYs:yt :ycurve];
[vertCSView setNewTrace: ycurve];
[vertCSView drawIt:self];
}
return self;
}
// Loads the sample data file from app wrapper
- openSample:sender
{
char buf[MAXPATHLEN + 1];
NXBundle *bundle;
bundle = [NXBundle mainBundle];
if( [bundle getPath:buf forResource:"SpaceTimeRF" ofType:"2d"] ) {
[self openFile: buf];
}
return self;
}
- viewSampleFile:sender
{
char buf[MAXPATHLEN + 1];
NXBundle *bundle;
NXStream *stream;
bundle = [NXBundle mainBundle];
if( [bundle getPath:buf forResource:"SpaceTimeRF" ofType:"2d"] ) {
if ((stream = NXMapFile(buf, NX_READONLY)) == NULL) {
NXRunAlertPanel("File Error",
"Cannot open file %s","OK",NULL,NULL, buf);
return nil;
}
[commentText readText: stream];
NXClose(stream);
[[[commentText window] setTitleAsFilename: buf] orderFront:self];
}
return self;
}
/*
* appOpenFile:type: is invoked by Workspace via command-dragging of file to app
* icon. ContourPlot does not have any special doc type, so double-clicking
* does not apply.
*/
- (int)appOpenFile:(const char *)path type:(const char *)type
{
if ([self openFile:path] == nil) return NO;
else return YES;
}
/*
* The following method indicates that Yap is ready to open multiple
* files at one time. Really NO, but without this, appicon does not
* accept command-dragged data file.
*/
- (BOOL)appAcceptsAnotherFile:sender
{
return YES;
}
// connect to Menu's File -> Open button.
//
- open:sender
{
static int FirstFile = 1;
const char *fileName;
const char *const types[2] = {NULL, NULL};
if(FirstFile) {
if ([openPanel runModalForDirectory: NXHomeDirectory() file: NULL types: types]
&& (fileName =[openPanel filename])) {
[self openFile:fileName];
FirstFile = 0;
}
}
else {
/* Use last directory again */
if ([openPanel runModalForTypes:types] && (fileName =[openPanel filename])) {
[self openFile:fileName];
}
}
return self;
}
static char *keyword[] =
{
"NUMPOINTS", "XRANGE", "YRANGE", "ZDATA", "GRIDSTEP",
"NINTERPOL", "ZRANGE",
""
};
#define NKEYWORD 7 /* # of names in *keyword[] */
- openFile:(const char *)fileName
{
FILE *fpin;
char linebuf[256], wdbuf[256];
int i, n2read;
int Done = 0; // raised when ZDATA keyword is detected
int BadData = 0; // raised if bad data detected
int prmnumber = 0;
int nzRead = 0; // # of Z values read from file
NXStream *scStream;
char *scBuffer, *mbuf, *fptr;
int scLen, dummy;
float zmaxtmp = -1.0e30, zmintmp = 1.0e30;
float xstep = 0.0, ystep = 0.0;
BOOL bipolar = YES;
if((fpin = fopen(fileName, "r")) == NULL) {
NXRunAlertPanel("File Error",
"Cannot open file %s","OK",NULL,NULL, fileName);
return nil;
}
fptr = rindex(fileName, '/');
fptr++;
if(fptr) strncpy(datafile, fptr, 254); // for later use in EPS save.
[[contourView window] setTitleAsFilename: fileName];
// Invalidate data until new file loaded successfully.
[contourView setEnabled:NO]; // disable plot until OK
[contourView setComment:"% None" :4];
nx = ny = -1;
zrange_in_file = 0;
scStream = NXOpenMemory(NULL, 0, NX_WRITEONLY); // create memory stream
NXPrintf(scStream, "%% Contour Plot by: %s\n", VERSION);
NXPrintf(scStream, "%% From Grid File: %s\n", fileName);
// Read line by line until we see ZDATA keyword, upon which
// we switch to reading nx*ny float numbers regardless of line breaks.
//
while(!Done && !BadData)
{
if(fgets(linebuf, 250, fpin) == NULL)
break;
linebuf[250] = '\0'; /* for safety */
wdbuf[0] = '\0';
sscanf(linebuf, "%s", wdbuf); /* first word */
if(wdbuf[0] == '%' || wdbuf[0] == '#' || wdbuf[0] == ';' || strlen(wdbuf) == 0) {
if(wdbuf[0] == '%')
NXPrintf(scStream, "%s", linebuf);
continue; /* skip comment or blank lines */
}
prmnumber = -1;
for(i=0; i<NKEYWORD; i++) {
if( strcasecmp(wdbuf, keyword[i]) == 0 ) {
prmnumber = i;
break;
}
}
/* --- lookup in *parname[] done ----- */
if( prmnumber == -1 ) {
fprintf(stderr, "...Unknown parameter: %s\n", wdbuf);
}
else { /* valid prmnumber .. param name found */
switch(prmnumber) {
case 0: /* NUMPOINTS */
sscanf(linebuf, "%*s %d %d", &nx, &ny);
if(nx >= 5 && ny >= 5) {
if(zdata) free(zdata);
zdata = (float *)malloc((nx*ny)*sizeof(float));
}
else {
Done = BadData = 1;
NXRunAlertPanel("File Error",
"Bad number of points: (%d %d)","OK",NULL,NULL, nx, ny);
}
break;
case 1: /* XRANGE */
sscanf(linebuf, "%*s %f %f", &xmin, &xmax);
break;
case 2: /* YRANGE */
sscanf(linebuf, "%*s %f %f", &ymin, &ymax);
break;
case 3: /* ZDATA */
Done = 1;
break;
case 4: /* GRIDSTEP -- Optional */
sscanf(linebuf, "%*s %f %f", &xgstep, &ygstep);
break;
case 5: /* NINTERPOL -- Optional */
sscanf(linebuf, "%*s %d %d", &nxInterp, &nyInterp);
break;
case 6: /* ZRANGE -- Optional */
sscanf(linebuf, "%*s %f %f", &min_zval, &max_zval);
zrange_in_file = 1;
if(min_zval < 0.0 && max_zval > 0.0) bipolar = YES;
else bipolar = NO;
break;
} /* end switch(prmnumber) */
} /* end if( prmnumber == ..){} else {} */
} /* end while( ... ) */
if(BadData) {
fclose(fpin);
return nil;
}
// Now, start reading zdata[] values one by one
nzRead = 0;
n2read = nx*ny;
for(i=0; i<n2read; i++) {
if(fscanf(fpin, "%f", &zdata[i]) == EOF) {
BadData = 1;
NXRunAlertPanel("File Error",
"Premature end of file (%d read, %d expected)",
"OK",NULL,NULL, nzRead, n2read);
break;
}
nzRead++;
if(zmaxtmp < zdata[i]) zmaxtmp = zdata[i];
if(zmintmp > zdata[i]) zmintmp = zdata[i];
} /* end of for(i=0...) */
fclose(fpin);
if(BadData) return nil;
// Data successfully read
// Copy comments in the data file to ContourView's comment to
// keep track of data source.
NXPrintf(scStream, "%% ---\n");
NXFlush(scStream);
NXGetMemoryBuffer(scStream, &scBuffer, &scLen, &dummy);
// This may not be needed, but I don't know if scBuffer above is null terminated.
// If it is, malloc() stuff is not needed.
mbuf = (char *)malloc(scLen+1);
strncpy(mbuf, scBuffer, scLen);
mbuf[scLen] = '\0';
[contourView setComment: mbuf : scLen];
[horizCSView setComment: mbuf : scLen];
[vertCSView setComment: mbuf : scLen];
[[commentText window] setTitle: "Comments from Data File"];
[commentText setText: mbuf];
free(mbuf);
NXCloseMemory(scStream, NX_FREEBUFFER);
// Set info fields from file
[nxField setIntValue: nx];
[nyField setIntValue: ny];
[xminField setFloatValue: xmin];
[xmaxField setFloatValue: xmax];
[yminField setFloatValue: ymin];
[ymaxField setFloatValue: ymax];
// These may be optionally specified in file.
[xgStepField setFloatValue: xgstep];
[ygStepField setFloatValue: ygstep];
[nxInterpField setIntValue: nxInterp];
[nyInterpField setIntValue: nyInterp];
// #### Check logic here ####
[self setMinMaxZdata:zmintmp :zmaxtmp];
if(!manual_scaling)
[self loadPlotRangeFromData:self];
if(zmintmp < 0.0 && zmaxtmp > 0.0) bipolar = YES;
else bipolar = NO;
if(zrange_in_file && !manual_scaling)
[self setMinMaxZplot:min_zval :max_zval];
else
[self getMinMaxZplot];
[contourView setBipolar: bipolar];
// Set real data portion here.
[contourView setCartesianGridData: zdata : xmin : xmax : ymin : ymax
ofSize: nx : ny
withInterpolationTo: nxInterp : nyInterp];
[contourView setGridLineWidth:0.2
spacing: xgstep : ygstep]; // if grid is used
// Horizontal Cross Section display initialization
[horizCSView freeTraceCache];
[horizCSView allocTraceCache: 1 : nxInterp];
[horizCSView setScaleX: xmin : xmax];
[horizCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
: max_zval + (max_zval - min_zval)*0.1];
xstep = (xmax - xmin)/(nxInterp-1);
if(xt) free(xt);
if(xcurve) free(xcurve);
xt = (float *) malloc(sizeof(float)*nxInterp);
xcurve = (float *) malloc(sizeof(float)*nxInterp);
for(i=0; i<nxInterp; i++) {
xt[i] = xmin + xstep * i;
xcurve[i] = 0.0;
}
[horizCSView setXvalues: xt];
[horizCSView setDataPoints: NULL : NULL : 0];
[horizCSView cacheDataPointsAndBackGround];
[horizCSView setNewTrace: xcurve];
[vertCSView freeTraceCache];
[vertCSView allocTraceCache: 1 : nyInterp];
[vertCSView setScaleX: ymin : ymax];
[vertCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
: max_zval + (max_zval - min_zval)*0.1];
ystep = (ymax - ymin)/(nyInterp-1);
if(yt) free(yt);
if(ycurve) free(ycurve);
yt = (float *) malloc(sizeof(float)*nyInterp);
ycurve = (float *) malloc(sizeof(float)*nyInterp);
for(i=0; i<nyInterp; i++) {
yt[i] = ymin + ystep * i;
ycurve[i] = 0.0;
}
[vertCSView setXvalues: yt];
[vertCSView setDataPoints: NULL : NULL : 0];
[vertCSView cacheDataPointsAndBackGround];
[vertCSView setNewTrace: ycurve];
[self redisplayView];
return self;
}
// For saving ContourView to an EPS file.
// This way, you don't have to go through a 2-step process of using BBFig or Yap
// to save as EPS.
//
- saveEPSRequest:sender
{
char pathbuf[1024];
char msgbuf[256];
char filename[256];
char *cptr, *sptr;
id savePanel;
sptr = (char *)NXGetDefaultValue(APPNAME, "SaveDir");
if(sptr[0] == '~')
strcpy(pathbuf, NXHomeDirectory());
else
strcpy(pathbuf, sptr);
strcpy(filename, datafile);
strcat(filename, ".eps"); /* make it an EPS file */
savePanel = [SavePanel new];
if ([savePanel runModalForDirectory:pathbuf file:filename]
&& (saveFile = [savePanel filename])) {
// Assume filename is OK.
if([contourView saveEPStoFile: saveFile] == -1) {
// Couldn't save to EPS file
sprintf(msgbuf, "Cannot open %s for saving EPS file.", saveFile);
NXRunAlertPanel("Save Error",msgbuf,"OK",NULL,NULL);
}
else {
// Save was successful.
strcpy(pathbuf, saveFile);
cptr = rindex(pathbuf, '/');
cptr = '\0';
NXWriteDefault(APPNAME, "SaveDir", pathbuf);
}
}
return self;
}
- redisplayView
{
if(manual_scaling || zrange_in_file) {
[self getMinMaxZplot];
[contourView setMinMaxOfContourLevels:min_zval :max_zval];
}
[contourView setEnabled: YES];
[contourView updateImageCache];
[contourView display];
[horizCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
: max_zval + (max_zval - min_zval)*0.1];
[vertCSView setScaleY: min_zval - (max_zval - min_zval)*0.1
: max_zval + (max_zval - min_zval)*0.1];
[horizCSView drawIt:self];
[vertCSView drawIt:self];
return self;
}
/* from button to set plot range */
- loadPlotRangeFromData:sender
{
[self getMinMaxZdata];
[self setMinMaxZplot:min_zval :max_zval];
[self redisplayView];
return self;
}
- getMinMaxZplot
{
max_zval = [maxZplotField floatValue];
min_zval = [minZplotField floatValue];
return self;
}
- getMinMaxZdata
{
max_zval = [maxZdataField floatValue];
min_zval = [minZdataField floatValue];
return self;
}
- setMinMaxZplot:(float)min :(float)max
{
[maxZplotField setFloatValue: max];
[minZplotField setFloatValue: min];
return self;
}
- setMinMaxZdata:(float)min :(float)max
{
[maxZdataField setFloatValue: max];
[minZdataField setFloatValue: min];
return self;
}
/* from Z-scaling Min Max fields */
- setZscaleMinMax:sender
{
zrange_in_file = 0; // once manually set, ignore file's
[self getMinMaxZplot];
[self redisplayView];
return self;
}
- setAutoManualScaling:sender
{
switch([scaleSelectRadio selectedRow])
{
case 0: /* Auto selected */
manual_scaling = 0;
[contourView setCartesianGridData: zdata : xmin : xmax : ymin : ymax
ofSize: nx : ny
withInterpolationTo: nxInterp : nyInterp];
break;
case 1: /* manual selected */
manual_scaling = 1;
[self getMinMaxZplot];
break;
default:
break;
}
[self redisplayView];
return self;
}
- setNcontourLevels:sender
{
static int new, old=16;
// new = [sender intValue]/2 *2; // force to even number
new = [sender intValue]; // force to even number
if(new == old) return self;
old = new;
[nlevelField setIntValue:new];
[contourView setNumberOfContourLevels:new];
[contourView updateImageCache];
[contourView display];
return self;
}
- setLineWidth:sender
{
float newwidth;
newwidth = [sender floatValue];
if(newwidth < 0.0) newwidth = 0.0;
if(newwidth > 20.0) newwidth = 20.0;
[contourView setContourLineWidthMinor:newwidth andMajor: newwidth];
[contourView updateImageCache];
[contourView display];
return self;
}
- setGridSpacing:sender
{
float gx, gy;
gx = [xgStepField floatValue];
xgstep = gx;
gy = [ygStepField floatValue];
ygstep = gy;
[contourView setGridLineWidth:0.2
spacing: xgstep : ygstep]; // if grid is used
if([tickSwitch state] || [gridSwitch state]) {
[contourView updateImageCache];
[contourView display];
}
return self;
}
- setNInterpolation:sender
{
int ix, iy;
int i;
float xstep, ystep;
ix = [nxInterpField intValue];
if(ix < 5) [nxInterpField setIntValue: nxInterp];
else nxInterp = ix;
iy = [nyInterpField intValue];
if(iy < 5) [nyInterpField setIntValue: nyInterp];
else nyInterp = iy;
[contourView setCartesianGridData: zdata : xmin : xmax : ymin : ymax
ofSize: nx : ny
withInterpolationTo: nxInterp : nyInterp];
[contourView updateImageCache];
[contourView display];
[horizCSView freeTraceCache];
[horizCSView allocTraceCache: 1 : nxInterp]; // realloc trace cache
xstep = (xmax - xmin)/(nxInterp-1);
if(xt) free(xt);
if(xcurve) free(xcurve);
xt = (float *) malloc(sizeof(float)*nxInterp);
xcurve = (float *) malloc(sizeof(float)*nxInterp);
for(i=0; i<nxInterp; i++) {
xt[i] = xmin + xstep * i;
}
[horizCSView setXvalues: xt];
[horizCSView cacheDataPointsAndBackGround];
[contourView fakeMakerMovedFor:0]; // have fake delegate message
[vertCSView freeTraceCache];
[vertCSView allocTraceCache: 1 : nyInterp]; // realloc trace cache
ystep = (ymax - ymin)/(nyInterp-1);
if(yt) free(yt);
if(ycurve) free(ycurve);
yt = (float *) malloc(sizeof(float)*nyInterp);
ycurve = (float *) malloc(sizeof(float)*nyInterp);
for(i=0; i<nyInterp; i++) {
yt[i] = ymin + ystep * i;
}
[vertCSView setXvalues: yt];
[vertCSView cacheDataPointsAndBackGround];
[contourView fakeMakerMovedFor:1];
return self;
}
- clearAction:sender
{
[contourView clear:self];
return self;
}
- drawAction:sender
{
[contourView updateImageCache];
[contourView display];
return self;
}
- colorWellsAction:sender
{
NXColor cB, cD, cBG, cLine;
cB =[brightCWell color];
cD =[darkCWell color];
cBG = [bgCWell color];
cLine = [lineCWell color];
[contourView setFillColors:cB :cD];
[contourView setBackgroundColor:cBG];
[contourView setContourLineColor:cLine];
[contourView updateImageCache];
[contourView display];
return self;
}
- useColorAction:sender
{
int state;
state = [sender state];
[contourView setFillEnable:(BOOL)state];
[contourView updateImageCache];
[contourView display];
return self;
}
- setGridMesh:sender
{
if([sender state])
[contourView setGridEnable:YES];
else
[contourView setGridEnable:NO];
[contourView updateImageCache];
[contourView display];
return self;
}
- setTickMark:sender
{
if([sender state])
[contourView setTicksEnable:YES];
else
[contourView setTicksEnable:NO];
[contourView updateImageCache];
[contourView display];
return self;
}
- setAxis:sender
{
if([sender state])
[contourView setAxisEnable:YES];
else
[contourView setAxisEnable:NO];
[contourView updateImageCache];
[contourView display];
return self;
}
- flipYAction:sender
{
int state;
state = [sender state];
[contourView setInvertY:(BOOL)state];
[contourView updateImageCache];
[contourView display];
return self;
}
- setMinNumberContour:sender
{
[contourView setMinNumberOfPointsPerContour:[sender intValue]];
[contourView updateImageCache];
[contourView display];
return self;
}
- setDash:sender /* set Dash pattern of contour */
{
float cdash[2];
cdash[0] = [dashMarkField floatValue];
cdash[1] = [dashSpaceField floatValue];
[contourView setContourDash: cdash : 2];
[contourView updateImageCache];
[contourView display];
return self;
}
- setStrokeContour:sender
{
int state;
state = [sender state];
[contourView setContourStrokeEnable:(BOOL)state];
[contourView updateImageCache];
[contourView display];
return self;
}
// This fixes the problem of messed up text when you resize the
// document window.
- windowDidResize:sender
{
[commentText calcLine];
[commentText sizeToFit];
return self;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.