This is ContourView.m in view mode; [Download] [Up]
/* ContourView.m
Contour Plot object with optional color fills.
It is pretty easy to use. If instantiated within IB, all you need to do
to get a plot is to call the following two method to get a default
plot. If you understand the following method, you can use the
ContourView object easily.
- setCartesianGridData:(float *)f :(float)xmin :(float)xmax
:(float)ymin :(float)ymax
ofSize:(int)nx :(int)ny
withInterpolationTo:(int)n1x :(int)n1y;
f[nx*ny] is a 1-d array containing 2-d grid data such that f[iy*nx+ix]
contains the value at (ix, iy).
Typically, just a few messages below will produce a contour plot with color
fills.
[contourView setBipolar:YES];
[contourView setCartesianGridData: fdata :1.0 :5.0 :1.0 :5.0
ofSize: 20 :20
withInterpolationTo: 50 :50];
[contourView setFillEnable:YES];
[contourView setEnabled:YES];
[contourView updateImageCache];
[contourView display];
* ---------------------------------------------------------------------------
* TO DO:
* Optimize drawing. Especially, do not recompute everything in drawSelf::.
*
* ---------------------------------------------------------------------------
* Version History:
* V0.99 95-01-27 Izumi Ohzawa
* Implemented cross-hair to provide cross-section 1-D profile.
* Now starts out disabled so method call [contourView setEnabled:YES];
* must be done before it does anything useful (see above).
* Also, it now caches the plot for cross-hair, so -updateImageCache
* must be called when the plot changes.
* V0.98 93-10-03 Izumi Ohzawa
* Tailor-resistant comment insertion.
* Also, comments moved to top of EPS file.
* V0.97 93-07-10 Izumi Ohzawa
* Odd number of contours allowed.
* Bug with coloring tick, axis, frame colors fixed.
* V0.96 93-02-19 Izumi Ohzawa
* Dash control for negative contour and grid mesh.
* Control for doing or not doing contour path stroke
* V0.95 93-01-21 Izumi Ohzawa
* Added tick marks.
* V0.94 93-01-20 Izumi Ohzawa
* More info such as grid size, etc have been added as comments.
* Finally, a bug with splin2.c from Numerical Recipes has been found and
* fixed. Axis and grid methods added.
* V0.93 92-12-04 Izumi
* Small fixes.
* V0.92 92-12-03 Izumi
* It now correctly does color fills via sorting of contour drawing order.
* V0.91 92-05-24 Izumi
* Does OK color fills, printing, copying PS to paste board.
* V0.90 92-05-18 Izumi Ohzawa, izumi@pinoko.berkeley.edu
* The initial version.
*/
/* Version of ContourView object. This is entered into PS code generated after NeXT
print package prolog.
*/
#define CV_VERSION "% ContourView.m [V0.99 95-01-24] by Izumi Ohzawa, izumi@pinoko.berkeley.edu."
#import <appkit/appkit.h>
#import "ContourView.h"
#import "contour.h"
#import "matalloc.h"
#import "splin2.h"
#import <strings.h>
#import <stdlib.h> /* malloc */
#import <math.h>
#import <time.h>
#define N6 6 /* expand 3 points on 4 sides to close all contours */
#define N3 3
#define N2 2
#define DEFAULTNCLEVELS 16 /* Default number of contour levels */
// static float ncontdash[] = {5.0, 5.0 }; /* dash for negative contours */
static float psolid[] = {0.0 };
// static float griddash[] = {1.0, 2.0 }; /* dash for grid */
@implementation ContourView
- initFrame:(NXRect *)nf
{
self = [super initFrame:nf];
// ClearFlag = 0;
enabled = NO;
invertY = NO;
doFill = NO;
doContourStroke = YES; // stroke contour path
debug = NO;
frameON = YES;
axisON = NO;
gridON = NO;
ticksON = NO;
frameLineWidth = 1.0;
axisLineWidth = 0.5;
gridLineWidth = 0.2;
tickLineWidth = 0.8;
tickLength = 6.0;
axisX = axisY = 0.0; // where to draw axis
gridXstep = gridYstep = 1.0;
minNumPoints = 3;
ndata = 0;
xd = NULL;
yd = NULL;
fd = NULL;
f2 = NULL;
fd2a = NULL;
xt = NULL;
yt = NULL;
SortedCntrPtr = NULL;
comment = NULL;
comment012 = NULL;
bipolar = 0;
basevalue = 0.0;
smallx = smally = 0.0001;
pXmin = bounds.origin.x;
pYmin = bounds.origin.y+0.25;
pXmax = bounds.size.width + bounds.origin.x -0.25;
pYmax = bounds.size.height + bounds.origin.y -0.25;
pX_Vline = pXmin - 10.0;
pY_Hline = pYmin - 10.0;
lastx = lasty = -10.0;
// Allocate cache
imageCache = [[NXImage alloc] initSize:&bounds.size];
[imageCache useCacheWithDepth:NX_DefaultDepth];
cacheOK = 0;
printCrossHair = NO;
continuousNotify = NO;
nclevels = DEFAULTNCLEVELS;
cA = (CntrAttribute *)malloc((size_t)nclevels*sizeof(CntrAttribute));
contourList = NULL;
numContours = 0;
// default attributes of contours
positiveColor = NX_COLORRED;
negativeColor = NX_COLORBLUE;
backgroundColor = NX_COLORWHITE;
contourLineColor = NX_COLORBLACK;
frameColor = NX_COLORBLACK;
axisColor = NX_COLORBLACK;
gridColor = NX_COLORBLACK;
tickColor = NX_COLORBLACK;
minorContourLineWidth = 1.0;
majorContourLineWidth = 2.0;
griddash[0] = 1.0; // initial grid dash (mark)
griddash[1] = 2.0; // (space)
Nncdash = 2; // Negative contour dash array size
ncontdash = (float *)malloc((Nncdash+1)*sizeof(float));
ncontdash[0] = 6.0; // (mark)
ncontdash[1] = 6.0; // (space)
[self setDefaultContourAttributes];
return self;
}
- free
{
if(imageCache) [imageCache free];
if(f2) free_fmatrix(f2, 1, nxraw, 1, nyraw);
if(fd2a) free_fmatrix(fd2a, 1, nxraw, 1, nyraw);
if(xt) free((void *)xt);
if(yt) free((void *)yt);
if(cA) free(cA);
if(xd) free(xd);
if(yd) free(yd);
if(fd) free(fd);
[super free];
return self;
}
- (BOOL)acceptsFirstMouse { return YES; }
- (BOOL)acceptsFirstResponder { return YES; }
// ========= DRAWing code now draws into cache
- drawAll
{
int j, OK2stroke = 0;
float xxa, yya;
float xg, yg, xxg, yyg;
ContourPath *cntr;
DPSContext curContext = DPSGetCurrentContext();
NXSetColor(backgroundColor);
NXRectFill(&bounds);
if(!fd) return self; // if no data set, do nothing.
if(contourList) [self freeContourList]; /* to start out */
numContours = 0;
computeContour(self, nx, ny, xd, yd, fd, nclevels, cA);
if(debug)
fprintf(stderr, "ContourView: # levels=%d, # contours=%d\n", nclevels, numContours);
if(doFill) {
/* Sort the order of contour drawing from outermost to innermost for filling */
SortedCntrPtr = (ContourPath **)(malloc((size_t)(sizeof(ContourPath *)*numContours)));
sort_contourList(contourList, xdmin, xdmax, ydmin, ydmax, numContours, SortedCntrPtr);
for(j=0; j<numContours; j++) {
cntr = SortedCntrPtr[j];
[self findInsideHighLow:cntr]; /* inside contour goin up or down */
[self plotContour:cntr :curContext]; /* This plots one contour curve */
}
}
else {
/* Do NOT do filling -- just go through the list */
cntr = contourList;
while(cntr) {
[self plotContour:cntr :curContext]; /* This plots one contour curve */
cntr = cntr->next; /* point to the next one */
};
}
PSsetdash(psolid, 0, 0.0);
/* ==== Do GRID MESH ======================================== */
if(gridON) {
OK2stroke = 0;
if(NXDrawingStatus != NX_DRAWING)
DPSPrintf(curContext, "\n%% Grid Mesh\n");
PSsetdash(griddash, 2, 0.0);
NXSetColor(gridColor);
PSsetlinewidth(gridLineWidth);
for(xg=axisX; xg < xdmax; xg += gridXstep) {
xxg = (xg - xdmin)*ppxu;
PSmoveto(xxg, pYmin);
PSlineto(xxg, pYmax);
OK2stroke++;
}
for(xg=axisX; xg > xdmin; xg -= gridXstep) {
xxg = (xg - xdmin)*ppxu;
PSmoveto(xxg, pYmin);
PSlineto(xxg, pYmax);
OK2stroke++;
}
for(yg=axisY; yg < ydmax; yg += gridYstep) {
if(invertY)
yyg = (ydmax - yg)*ppyu;
else
yyg = (yg - ydmin)*ppyu;
PSmoveto(pXmin, yyg);
PSlineto(pXmax, yyg);
OK2stroke++;
}
for(yg=axisY; yg > ydmin; yg -= gridYstep) {
if(invertY)
yyg = (ydmax - yg)*ppyu;
else
yyg = (yg - ydmin)*ppyu;
PSmoveto(pXmin, yyg);
PSlineto(pXmax, yyg);
OK2stroke++;
}
if(OK2stroke) PSstroke();
PSsetdash(psolid, 0, 0.0);
} /* end of if(gridON) {} */
/* ==== Do TICK marks ======================================== */
if(ticksON) {
OK2stroke = 0;
if(NXDrawingStatus != NX_DRAWING)
DPSPrintf(curContext, "\n%% Tick Marks\n");
NXSetColor(tickColor);
PSsetlinewidth(tickLineWidth);
for(xg=axisX; xg < xdmax; xg += gridXstep) {
xxg = (xg - xdmin)*ppxu;
PSmoveto(xxg, pYmin); PSlineto(xxg, pYmin+tickLength);
PSmoveto(xxg, pYmax); PSlineto(xxg, pYmax-tickLength);
OK2stroke++;
}
for(xg=axisX; xg > xdmin; xg -= gridXstep) {
xxg = (xg - xdmin)*ppxu;
PSmoveto(xxg, pYmin); PSlineto(xxg, pYmin+tickLength);
PSmoveto(xxg, pYmax); PSlineto(xxg, pYmax-tickLength);
OK2stroke++;
}
for(yg=axisY; yg < ydmax; yg += gridYstep) {
if(invertY)
yyg = (ydmax - yg)*ppyu;
else
yyg = (yg - ydmin)*ppyu;
PSmoveto(pXmin, yyg); PSlineto(pXmin+tickLength, yyg);
PSmoveto(pXmax, yyg); PSlineto(pXmax-tickLength, yyg);
OK2stroke++;
}
for(yg=axisY; yg > ydmin; yg -= gridYstep) {
if(invertY)
yyg = (ydmax - yg)*ppyu;
else
yyg = (yg - ydmin)*ppyu;
PSmoveto(pXmin, yyg); PSlineto(pXmin+tickLength, yyg);
PSmoveto(pXmax, yyg); PSlineto(pXmax-tickLength, yyg);
OK2stroke++;
}
if(OK2stroke) PSstroke();
} /* end of if(ticksON) {} */
/* ==== Do AXIS lines ======================================== */
if(axisON) {
if(NXDrawingStatus != NX_DRAWING)
DPSPrintf(curContext, "\n%% Axis\n");
NXSetColor(axisColor);
PSsetlinewidth(axisLineWidth);
xxa = (axisX - xdmin)*ppxu;
if(invertY)
yya = (ydmax - axisY)*ppyu;
else
yya = (axisY - ydmin)*ppyu;
if(xxa > pXmin && xxa < pXmax) {
PSmoveto(xxa, pYmin);
PSlineto(xxa, pYmax);
PSstroke();
}
if(yya > pYmin && yya < pYmax) {
PSmoveto(pXmin, yya);
PSlineto(pXmax, yya);
PSstroke();
}
} /* end of if(axisON) {} */
/* ==== Do FRAME RECTANGLE ======================================== */
if(frameON) {
if(NXDrawingStatus != NX_DRAWING)
DPSPrintf(curContext, "\n%% Frame Rectangle\n");
// Frame rectangle
NXSetColor(frameColor);
PSsetlinewidth(frameLineWidth);
PSrectstroke(pXmin,pYmin,pXmax,pYmax); /* frame rectangle */
}
if(doFill && SortedCntrPtr) {
free(SortedCntrPtr);
SortedCntrPtr = NULL;
}
[self freeContourList];
return self;
}
// This draws the plot into NXImage cache
//
- updateImageCache
{
if([imageCache lockFocus]) {
[self drawAll];
[imageCache unlockFocus];
} /* end of if([imageCache lockFocus])... */
cacheOK = 1;
return self;
}
// ==== MOUSE EVENT, CURSOR, MARKER HANDLING METHODS =========================================
- enablePrintCrossHair:(BOOL)yn
{
printCrossHair = yn;
return self;
}
- enableContinuousNotify:(BOOL)yn
{
continuousNotify = yn;
return self;
}
/* This method handles a mouse down. */
- mouseDown:(NXEvent *)event
{
NXPoint p;
int mknum;
if(!enabled || !cacheOK) return self;
[window makeFirstResponder:self]; /* give me the first responder status */
p = event->location;
[self convertPoint:&p fromView:nil]; /* to this view's coordinate from window's */
if ((mknum = [self isHit:&p]) >= 0) /* hit ! */
[self dragMarker: mknum :event]; /* modal drag loop only if there's a hit */
[self display];
return self;
}
/*
* Hit Test -- Hit test is entirely done in the client side and this is faster than
* relying on the server for hit tests. This is possible because all test regions
* are rectangles and not made up of complicated paths.
* Returns maker number (0..N) of movable object.
* Returns a negative value if NO hit.
*/
- (int) isHit:(const NXPoint *) p
{
float tempx, tempy;
NXRect hitRect; // for hit testing
// On first ever mouseDown, initialize pX_Vline and pY_Hline to location
// of mouse cursor.
if(pY_Hline < pYmin || pX_Vline < pXmin ) {
pX_Vline = p->x;
pY_Hline = p->y;
if (delegate && [delegate respondsTo:@selector(markerDidMove:::::)]) {
// tempx, tempy are in View's coordinate in points, convert to real units.
tempx = (pX_Vline - bounds.origin.x)/ppxu + xdmin;
tempy = (pY_Hline - bounds.origin.y)/ppyu + ydmin;
[delegate markerDidMove:tag :2 :tempx :tempy :1]; // for cross pt
}
return(-1); // indicate no hit for this
}
// void NXSetRect(NXRect *aRect, NXCoord x, NXCoord y, NXCoord width, NXCoord height)
// BOOL NXMouseInRect(const NXPoint *aPoint, const NXRect *aRect, BOOL flipped)
// Setup hit test rectangle for trigger thresholds, pre-trigger length lines.
// These are done later because long lines can be easily grabbed.
NXSetRect(&hitRect, pX_Vline-4.0, pY_Hline-4.0, 8.0, 8.0); // tiny rect at cross pt
if(NXMouseInRect(p, &hitRect, NO))
return(2);
NXSetRect(&hitRect, pXmin, pY_Hline-4.0, (pXmax-pXmin), 8.0); // horiz line
if(NXMouseInRect(p, &hitRect, NO))
return(0);
NXSetRect(&hitRect, pX_Vline-4.0, pYmin, 8.0, (pYmax-pYmin)); // vert line
if(NXMouseInRect(p, &hitRect, NO))
return(1);
// if no hit and current line positions near the edge, set lines to clicked location.
if(pY_Hline <= pYmin || pY_Hline >= pYmax || pX_Vline <= pXmin || pX_Vline >= pXmax) {
pX_Vline = p->x;
pY_Hline = p->y;
if (delegate && [delegate respondsTo:@selector(markerDidMove:::::)]) {
// tempx, tempy are in View's coordinate in points, convert to real units.
tempx = (pX_Vline - bounds.origin.x)/ppxu + xdmin;
tempy = (pY_Hline - bounds.origin.y)/ppyu + ydmin;
[delegate markerDidMove:tag :2 :tempx :tempy :1]; // for cross pt
}
}
return(-1); // no hit
}
// Draging of Lines -- called only when there's a MouseDown hit on something
// mk = marker or line index that got MouseDown.
- dragMarker:(int)mk :(NXEvent *)event
{
int old_mask, drag_happened=0;
NXPoint p;
float tempx=0.0, tempy=0.0;
lastx = lasty = 0.0;
old_mask = [window addToEventMask:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
while (event->type != NX_MOUSEUP) {
if (event->type == NX_MOUSEDRAGGED) {
p = event->location;
[self convertPoint:&p fromView:nil];
if(!drag_happened) {
drag_happened = 1; // indicate that at least 1 drag event came
}
}
switch(mk) {
case 0: /* horiz line */
tempy = p.y;
if(tempy > pYmax) tempy = bounds.origin.y+bounds.size.height;
if(tempy < pYmin) tempy = bounds.origin.y;
pY_Hline = tempy;
tempx = 0.0;
break;
case 1: /* vert line */
tempx = p.x;
if(tempx > pXmax) tempx = bounds.origin.x+bounds.size.width;
if(tempx < pXmin) tempx = bounds.origin.x;
pX_Vline = tempx;
tempy = 0.0;
break;
case 2:
tempx = p.x;
if(tempx > pXmax) tempx = bounds.origin.x+bounds.size.width;
if(tempx < pXmin) tempx = bounds.origin.x;
pX_Vline = tempx;
tempy = p.y;
if(tempy > pYmax) tempy = bounds.origin.y+bounds.size.height;
if(tempy < pYmin) tempy = bounds.origin.y;
pY_Hline = tempy;
break;
default: // shouldn't happen
break;
} /* end of switch(mk) */
// Update display only if values have changed since last time
if(tempx != lastx || tempy != lasty) {
lastx = tempx; lasty = tempy;
[self display];
// Report change in the marker position to delegate.
// Do it for drag.
if (drag_happened && delegate && continuousNotify
&& [delegate respondsTo:@selector(markerDidMove:::::)]) {
// tempx, tempy are in View's coordinate in points, convert to real units.
tempx = (tempx - bounds.origin.x)/ppxu + xdmin;
tempy = (tempy - bounds.origin.y)/ppyu + ydmin;
[delegate markerDidMove:tag :mk :tempx :tempy :0]; // for drag
}
// NXPing();
}
event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
waitFor:1000 threshold:NX_BASETHRESHOLD];
} /* end of while( <not mouse-up>) loop */
// ### MouseUp detected, do necessary things and exit drag loop ###
[window setEventMask:old_mask]; // restore original event mask
// Report change in the marker position to delegate.
// Do it for mouse up event ater drag.
if (drag_happened && delegate && [delegate respondsTo:@selector(markerDidMove:::::)]) {
// tempx, tempy should already be correct and converted
tempx = (pX_Vline - bounds.origin.x)/ppxu + xdmin;
tempy = (pY_Hline - bounds.origin.y)/ppyu + ydmin;
[delegate markerDidMove:tag :mk :tempx :tempy :1]; // mouse up
}
return self;
}
// fakes a delegage message without mouse action using current marker positions.
- fakeMakerMovedFor:(int)mk
{
float tempx, tempy;
if(!enabled || !cacheOK) return self;
if (delegate && [delegate respondsTo:@selector(markerDidMove:::::)]) {
tempx = (pX_Vline - bounds.origin.x)/ppxu + xdmin;
tempy = (pY_Hline - bounds.origin.y)/ppyu + ydmin;
[delegate markerDidMove:tag :mk :tempx :tempy :1]; // mouse up
}
return self;
}
// Programmatically sets markers to specified position
// (float)pos in real XY unit, not in points.
- setMarkerPositionFor:(int)mk to:(float)pos
{
if(mk)
pX_Vline = (pos - xdmin)*ppxu; // actually shoud add bounds.origin.x
else
pY_Hline = (pos - ydmin)*ppyu;
[self fakeMakerMovedFor:mk];
[self display];
return self;
}
// This will change scale of plotting, but will not touch internally
// stored grid data: xd[], yd[] arrays. May be used for zooming?
//
- setScaleXY
{
ppxu = bounds.size.width/(xdmax - xdmin); // scaling factor points per unit of X
ppyu = bounds.size.height/(ydmax - ydmin); // scaling factor points per unit of Y
smallx = fabs(xdmax - xdmin) * 0.0001;
smally = fabs(ydmax - ydmin) * 0.0001;
return self;
}
- setEnabled:(BOOL)yn
{
enabled = yn;
return self;
}
// Set a comment string to be inserted into PostScript stream when printed or
// copied onto pasteboard as EPS. The comment string must already be formatted
// as valid PostScript comments: Each new line must start with '%'.
//
// The comment does not appear at the top of EPS file. It will be inserted
// immmediately AFTER the NeXT printPackage.ps, but before all the drawing
// commands for the contour appear.
// This is used to identify in detail the data used to create the contour
// plot, so we can look inside the EPS file later and relate to the raw data.
//
- setComment:(char *)cstr :(int)len
{
char *ptr, *ptr2, ch;
int lfcount;
if(comment) {
free((void *)comment);
comment = NULL;
}
comment = (char *)malloc((size_t)(len*sizeof(char)+2));
strncpy(comment, cstr, len);
comment[len] = '\0'; /* string terminator */
// We need a duplicate comment with '\n' translated to '\012' char sequence
if(comment012) {
free((void *)comment012);
comment012 = NULL;
}
lfcount = 0;
ptr = comment;
while( (ch = *ptr++) != '\0')
if(ch == '\n') lfcount++;
comment012 = (char *)malloc((size_t)(len + lfcount*4 + 2));
ptr = comment;
ptr2 = comment012;
while( (ch = *ptr++) != '\0') {
if(ch == '\n') {
*ptr2++ = '\\';
*ptr2++ = '0'; *ptr2++ = '1'; *ptr2++ = '2';
}
else
*ptr2++ = ch;
}
*ptr2 = '\0';
return self;
}
// Given array x[] with npts elements, returns min and max values in the array.
//
- findMinMax:(float *)x :(int)npts :(float *)vmin :(float *)vmax
{
int i;
float fmin= MAXFLOAT;
float fmax= MINFLOAT;
for(i=0; i<npts; i++)
{
if(x[i] < fmin) fmin = x[i];
if(x[i] > fmax) fmax = x[i];
}
*vmin = fmin;
*vmax = fmax;
return self;
}
- setInvertY:(BOOL)invy
{
invertY = invy;
return self;
}
- setDebugEnable:(BOOL)de
{
debug = de;
return self;
}
- setBipolar:(BOOL)yn
{
if(yn) bipolar = 1;
else bipolar = 0;
[self setDefaultContourAttributes];
return self;
}
- setFillEnable:(BOOL)fe
{
doFill = fe;
return self;
}
- setContourStrokeEnable:(BOOL)fe
{
doContourStroke = fe;
return self;
}
// Set the number of contour levels.
// It will be forced to an EVEN number if not already.
// It will automatically set default contour level values.
// If customized contour levels are needed,
// Call - setContourLevelArray:(float *)caa :(int)nl instead.
//
- setNumberOfContourLevels:(int)nl
{
if(nl == nclevels) return self; /* no change */
if(cA) free((void *)cA);
// nclevels = nl/2*2; // force it to be even
nclevels = nl; // force it to be even
cA = (CntrAttribute *)malloc((size_t)nclevels*sizeof(CntrAttribute));
[self setDefaultContourAttributes];
return self;
}
// Set the range of contour level values used.
// This can be used to override default autoscaling.
// For real flexibility use -setContourLevelArray:: method.
//
- setMinMaxOfContourLevels:(float)min :(float)max
{
fdmin = min;
fdmax = max;
[self setDefaultContourAttributes];
return self;
}
- setContourLineColor:(NXColor)clc
{
contourLineColor = clc;
[self setDefaultContourAttributes];
return self;
}
- setFrameColor:(NXColor)fc
{
frameColor = fc;
return self;
}
- setTickColor:(NXColor)fc
{
tickColor = fc;
return self;
}
- setGridColor:(NXColor)fc
{
gridColor = fc;
return self;
}
- setAxisColor:(NXColor)fc
{
axisColor = fc;
return self;
}
- setBackgroundColor:(NXColor)bc
{
backgroundColor = bc;
[self setDefaultContourAttributes];
return self;
}
- setFillColors:(NXColor)pe :(NXColor)ne
{
positiveColor = pe;
negativeColor = ne;
[self setDefaultContourAttributes];
return self;
}
// This method allows customization of contour attributes to use
// overriding the default ones.
//
- setContourAttributeArray:(CntrAttribute *)caa :(int)nl;
{
// FIXME #### copy caa to cA.
return self;
}
// Setup contour levels and other attributes to use based on the max and min of
// values in the data. This will be called automatically when grid
// data are set.
//
- setDefaultContourAttributes
{
int i, ncl2;
float absfmax, step;
float rbase, gbase, bbase; // RGB of background
float rp, gp, bp; // RGB of positive extreme
float rn, gn, bn; // RGB of negative extreme
float rpstep, gpstep, bpstep;
float rnstep, gnstep, bnstep;
float r, g, b;
NXConvertColorToRGB(positiveColor, &rp, &gp, &bp);
NXConvertColorToRGB(negativeColor, &rn, &gn, &bn);
NXConvertColorToRGB(backgroundColor, &rbase, &gbase, &bbase);
if(fabs(fdmax) > fabs(fdmin))
absfmax = fabs(fdmax);
else
absfmax = fabs(fdmin);
// if(fdmin<0.0 && fdmax>0.0)
if(bipolar)
{
/* bipolar data */
ncl2 = nclevels/2;
nclevels = ncl2 * 2; // Quietly change nclevels -- this may not be great.
// bipolar = 1; /* instance var flag */
step = absfmax /(float)ncl2;
rpstep = (rp - rbase)/(float)ncl2;
gpstep = (gp - gbase)/(float)ncl2;
bpstep = (bp - bbase)/(float)ncl2;
rnstep = (rn - rbase)/(float)ncl2;
gnstep = (gn - gbase)/(float)ncl2;
bnstep = (bn - bbase)/(float)ncl2;
for(i=0; i<ncl2; i++)
{
/* positive */
cA[ncl2+i].dash = 0;
cA[ncl2+i].level = step*i + step/2.0;
r = rpstep*(i+1) + rbase;
g = gpstep*(i+1) + gbase;
b = bpstep*(i+1) + bbase;
cA[ncl2+i].fillcolor_hi = NXConvertRGBToColor(r, g, b);
if(i) cA[ncl2+i].fillcolor_lo = cA[ncl2+i-1].fillcolor_hi;
else cA[ncl2+i].fillcolor_lo = backgroundColor;
cA[ncl2+i].linecolor = contourLineColor;
cA[ncl2+i].linewidth = minorContourLineWidth;
/* negative */
cA[ncl2-1-i].dash = 1;
cA[ncl2-1-i].level = -(step*i + step/2.0);
r = rnstep*(i+1) + rbase;
g = gnstep*(i+1) + gbase;
b = bnstep*(i+1) + bbase;
cA[ncl2-1-i].fillcolor_lo = NXConvertRGBToColor(r, g, b);
if(i) cA[ncl2-1-i].fillcolor_hi = cA[ncl2-i].fillcolor_lo;
else cA[ncl2-1-i].fillcolor_hi = backgroundColor;
cA[ncl2-1-i].linecolor = contourLineColor;
cA[ncl2-1-i].linewidth = minorContourLineWidth;
}
}
else
{
/* monopolar data */
// bipolar = 0; /* store flag in instance variable */
if( fdmin < 0.0 )
fdmin = 0.0; // This is a bug. for all negative data.
step = (fdmax - fdmin)/(float)(nclevels+1);
rpstep = (rp - rbase)/(float)nclevels;
gpstep = (gp - gbase)/(float)nclevels;
bpstep = (bp - bbase)/(float)nclevels;
for(i=0; i<nclevels; i++) {
cA[i].dash = 0;
cA[i].level = fdmin + step*(i+1);
r = rpstep*(i+1) + rbase;
g = gpstep*(i+1) + gbase;
b = bpstep*(i+1) + bbase;
cA[i].fillcolor_hi = NXConvertRGBToColor(r, g, b);
if(i) cA[i].fillcolor_lo = cA[i-1].fillcolor_hi;
else cA[i].fillcolor_lo = backgroundColor;
cA[i].linecolor = contourLineColor;
cA[i].linewidth = minorContourLineWidth;
}
}
return self;
}
/* The following two methods must be used AFTER the method
* - setCartesianGridData:(float *)f :(float)xmin :(float)xmax
* :(float)ymin :(float)ymax
* ofSize:(int)nnx :(int)nny
* withInterpolationTo:(int)n1x :(int)n1y
* has been called with
* active interpolation.
*/
- horizCrossSectionAtY:(float)fy :(int)np forXs:(float *)fx :(float *)fcurve
{
int i;
for(i=0; i<np; i++)
splin2(xt-1, yt-1, f2, fd2a, nxraw, nyraw, fx[i], fy, &fcurve[i]);
return self;
}
- vertCrossSectionAtX:(float)fx :(int)np forYs:(float *)fy :(float *)fcurve
{
int i;
for(i=0; i<np; i++)
splin2(xt-1, yt-1, f2, fd2a, nxraw, nyraw, fx, fy[i], &fcurve[i]);
return self;
}
- xMinMax:(float *)xmin :(float *)xmax;
{
*xmin = xdmin;
*xmax = xdmax;
return self;
}
- yMinMax:(float *)ymin :(float *)ymax
{
*ymin = ydmin;
*ymax = ydmax;
return self;
}
- zMinMax:(float *)zmin :(float *)zmax
{
*zmin = fdmin;
*zmax = fdmax;
return self;
}
// ==========================================================================
// Set surface data for regular Cartesian grid with optional interpolation.
// This is probably the most important method of this object.
// Use this if you want to plot data on uniform XY grid.
// Input data f[] of size (nx,ny) will be interpolated into (n1x, n1y) for
// smoother contour plot if desired. If no interpolation is needed,
// make (n1x, n1y) same as (nx, ny). This will turn off interpolation.
//
// (int) nx, ny : index sizes of input data
// (int) n1x, n1y : size of interpolated grid
// for (i = 0; i < nx*ny; i++) (float)f[i] : f value at (x,y)
// f[i] is a 1-D array. f(x,y) is in f[x + nx*y]
//
// (xmin,ymin) and (xmax,ymax) define the (X,Y) domain of the plot.
//
// We extend the final (interpolated) data 3 points on each border to
// let the contour algorithm close all contours. 1-point wide region copy
// the data of the original border, and 2 points margins outside that are
// set to the base value. This way, open contour lines that would have
// terminated at a border will get closed outside the original domain.
// This extra region for contour closure outside the original domain is
// outside the clip path, thus will not show up in the final plot.
// ---------------------------------------------------------------------------
//
- setCartesianGridData:(float *)f :(float)xmin :(float)xmax
:(float)ymin :(float)ymax
ofSize:(int)nnx :(int)nny
withInterpolationTo:(int)n1x :(int)n1y
{
int i, ix, iy;
int oldnx, oldny;
int redoAlloc = 0;
float xstep, ystep, fxtemp, fytemp;
float *fd1;
xdmin = xmin;
xdmax = xmax;
ydmin = ymin;
ydmax = ymax;
if(nnx != nxraw || nny != nyraw)
redoAlloc = 1; // redo allocation of arrays
oldnx = nxraw; // save for use in free_fmatrix()
oldny = nyraw;
nxraw = nnx;
nyraw = nny;
// do reallocation only if # of data points has changed.
nx = n1x+N6;
ny = n1y+N6;
nx1 = n1x;
ny1 = n1y;
if(ndata != ((n1x+N6)*(n1y+N6))) {
ndata = (n1x+N6)*(n1y+N6); /* new grid size */
if(xd) free(xd);
if(yd) free(yd);
if(fd) free(fd);
xd = (float *)malloc((size_t)ndata*sizeof(float));
yd = (float *)malloc((size_t)ndata*sizeof(float));
fd = (float *)malloc((size_t)ndata*sizeof(float));
}
// ===== Interpolation Code =============================
/* ######## Interpolate (nx, ny) to new size grid (n1x, n1y) ######## */
// Allocate temporary input arrays for interpolation
if(redoAlloc) {
if(f2) free_fmatrix(f2, 1, oldnx, 1, oldny);
if(fd2a) free_fmatrix(fd2a, 1, oldnx, 1, oldny);
if(xt) free((void *)xt);
if(yt) free((void *)yt);
f2 = NULL;
fd2a = NULL;
xt = NULL;
yt = NULL;
}
if(!xt) xt = (float *)malloc((size_t)nxraw*sizeof(float));
if(!yt) yt = (float *)malloc((size_t)nyraw*sizeof(float));
if(!f2) f2 = fmatrix(1, nxraw, 1, nyraw);
if(!fd2a) fd2a = fmatrix(1, nxraw, 1, nyraw);
// This will be freed within this method.
fd1 = (float *)malloc((size_t)nx1*ny1*sizeof(float)); /* for interpolation */
xstep = (xdmax-xdmin)/(float)(nxraw-1);
ystep = (ydmax-ydmin)/(float)(nyraw-1);
for(ix=0; ix<nxraw; ix++)
xt[ix] = xstep*ix + xdmin;
for(iy=0; iy<nyraw; iy++)
yt[iy] = ystep*iy + ydmin;
/* copy original data to 2-D array for interpolation */
for(ix=0; ix <nxraw; ix++)
for(iy=0; iy <nyraw; iy++)
f2[ix+1][iy+1] = f[iy*nxraw+ix];
/* precompute second-derivative arrays for splin2()
* 2-nd derivative is returned in fd2a[][].
* (xt-1) and (yt-1) are for passing C's zero-offset array to
* splie2() which expects 1-offset arrays. */
splie2(xt-1, yt-1, f2, nxraw, nyraw, fd2a);
// Interpolate f(nnx,nny) to finer array fd1(n1x,n1y)
xstep = (xdmax-xdmin)/(float)(nx1-1);
ystep = (ydmax-ydmin)/(float)(ny1-1);
for(ix=0; ix <nx1; ix++) {
for(iy=0; iy <ny1; iy++) {
fxtemp = xstep*ix+xdmin;
fytemp = ystep*iy+ydmin;
splin2(xt-1, yt-1, f2, fd2a, nxraw, nyraw, fxtemp, fytemp, &fd1[iy*nx1+ix]);
}
}
/* Copy interpolated array to expanded array while setting border values. */
for(i=0; i<ndata; i++) {
ix = i % nx;
iy = i / nx;
if(ix >= N3 && ix < (nx-N3) && iy >= N3 && iy < (ny-N3))
fd[i] = fd1[(iy-N3)*n1x+(ix-N3)]; /* copy interpolated data */
else if(iy < N3 && ix >= N3 && ix <(nx-N3)) /* bottom */
fd[i] = fd1[ix-N3];
else if(iy >= (ny-N3) && ix >= N3 && ix <(nx-N3)) /* top */
fd[i] = fd1[(n1y-1)*n1x+(ix-N3)];
else if(ix < N3 && iy >= N3 && iy <(ny-N3)) /* left */
fd[i] = fd1[(iy-N3)*n1x];
else if(ix >=(nx-N3) && iy >= N3 && iy <(ny-N3)) /* right */
fd[i] = fd1[(iy-N3)*n1x + n1x-1];
else if(ix < N3 && iy <N3) /* lower left */
fd[i] = fd1[0];
else if(ix < N3 && iy >= (ny-N3)) /* uppper left */
fd[i] = fd1[(n1y-1)*n1x];
else if(ix >= (nx-N3) && iy < N3) /* lower right */
fd[i] = fd1[n1x-1];
else if(ix >= (nx-N3) && iy >= (ny-N3))
fd[i] = fd1[(n1y-1)*n1x + n1x-1];
/* wipe 2 pixel border to base level */
if(ix < N2 || ix >= (nx-N2) || iy <N2 || iy >= (ny-N2))
fd[i] = basevalue; /* totally out */
} /* end for(i=0; i< ndata...) */
// Free temporary input array.
// if(f2) free_fmatrix(f2, 1, nxraw, 1, nyraw);
// if(fd2a) free_fmatrix(fd2a, 1, nxraw, 1, nyraw);
// if(xt) free((void *)xt);
// if(yt) free((void *)yt);
if(fd1) free((void *)fd1);
// ===== Interpolation Code =============================
// Generate proper xd[], yd[] array for countour_().
xstep = (xdmax-xdmin)/(float)(nx1-1);
ystep = (ydmax-ydmin)/(float)(ny1-1);
for(iy=0; iy<ny; iy++) {
for(ix=0; ix<nx; ix++) {
xd[ix+iy*nx] = xstep*(ix-N3) + xdmin;
yd[ix+iy*nx] = ystep*(iy-N3) + ydmin;
}
}
// Do appropriate scaling
[self setScaleXY];
// Get min and max fd[]
// [self findMinMax:f :nxraw*nyraw :&fdmin :&fdmax]; // get it from original
[self findMinMax:fd :ndata :&fdmin :&fdmax]; // get it from interpolated
[self setDefaultContourAttributes];
return self;
}
// Set XY grid points and values at each grid points.
// Grid does not have to be Cartesian or regular.
// Use this routine if you need flexibility. No interpolation of
// grid is provided. If you want interpolation,
// you have to do that yourself external to this object.
// (int) nj, nk : index sizes of data
// for (i = 0; i < nj*nk; i++) (float)x[i] : x coordinate
// for (i = 0; i < nj*nk; i++) (float)y[i] : y coordinate
// for (i = 0; i < nj*nk; i++) (float)f[i] : f value at (x[i], y[i])}
// f[i] is a 1-D array. f(j,k) is in f[j + nj*k]. Same for x[], y[].
//
// *** FIXME ***
// NOTE: This methond does not support contour closure by expanding the domain.
// THIS METHOD PROBABLY DOES NOT WORK ANYMORE.
//
- setGridAndValueData:(float *)x :(float *)y :(float *)f ofSize:(int)nj :(int)nk
{
int i;
// do reallocation only if # of data points has changed.
nx = nj;
ny = nk;
if(ndata != (nj*nk))
{
ndata = nj*nk;
if(xd) free(xd);
if(yd) free(yd);
if(fd) free(fd);
xd = (float *)malloc((size_t)ndata*sizeof(float));
yd = (float *)malloc((size_t)ndata*sizeof(float));
fd = (float *)malloc((size_t)ndata*sizeof(float));
}
for(i=0; i<ndata; i++) // copy data into instance variables
{
xd[i] = x[i];
yd[i] = y[i];
fd[i] = f[i];
}
// Do appropriate scaling
// Get min and max of xd[], yd[], and fd[]
[self findMinMax:xd :ndata :&xdmin :&xdmax];
[self findMinMax:yd :ndata :&ydmin :&ydmax];
[self findMinMax:fd :ndata :&fdmin :&fdmax];
[self setScaleXY];
[self setDefaultContourAttributes];
return self;
}
// Controls width and whether a rectangular frame is drawn.
//
- setFrameEnable:(BOOL)fflag lineWidth:(float)fw
{
frameON= fflag; // Draw bounds frame
frameLineWidth=fw;
return self;
}
- setAxisEnable:(BOOL)fflag
{
axisON = fflag;
return self;
}
- setGridEnable:(BOOL)fflag
{
gridON = fflag;
return self;
}
- setTicksEnable:(BOOL)fflag
{
ticksON = fflag;
return self;
}
- setAxisLineWidth:(float)alw position:(float)x :(float)y
{
axisLineWidth = alw;
axisX = x;
axisY = y;
return self;
}
- setGridLineWidth:(float)glw spacing:(float)xs :(float)ys
{
gridLineWidth = glw;
gridXstep = xs;
gridYstep = ys;
return self;
}
- setNumXgrid:(int)numXgrid
{
gridXstep = (xdmax - xdmin)/(float)(numXgrid-1);
return self;
}
- setTickLineWidth:(float)tlw length:(float)tll
{
tickLineWidth = tlw;
tickLength = tll;
return self;
}
- setContourLineWidthMinor:(float)clw andMajor:(float)clwm
{
minorContourLineWidth = clw;
majorContourLineWidth = clwm;
[self setDefaultContourAttributes];
return self;
}
- setGridDash:(float)lmark: (float)lspace
{
griddash[0] = lmark;
griddash[1] = lspace;
return self;
}
- setContourDash:(float *)darray :(int)nd
{
int i;
if(nd != Nncdash) {
if(ncontdash) free(ncontdash);
Nncdash = nd;
ncontdash = (float *)malloc((Nncdash+1)*sizeof(float));
}
for(i=0; i<Nncdash; i++)
ncontdash[i] = darray[i];
return self;
}
// Contours with number of points less than nmp are not plotted.
// This is used to eliminate tiny "speckles" in contour plot.
//
- setMinNumberOfPointsPerContour:(int)mnp
{
minNumPoints = mnp;
return self;
}
- clear:sender
{
/* ClearFlag = 1;
[self display];
ClearFlag = 0;
*/
return self;
}
- copy:sender
{
[self copyPScode:sender];
return self;
}
// Copies the current view into the Pasteboard as PostScript.
//
- copyPScode:sender
{
NXStream *psStream;
id pb;
char *data;
int dataLen, maxDataLen;
/* Open a stream on memory where we will collect the PostScript */
psStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
if (!psStream)
return self;
/* Tell the Pasteboard we're going to copy PostScript */
pb = [Pasteboard new];
[pb declareTypes:&NXPostScriptPboardType num:1 owner:self];
/* writes the PostScript for the whole plot as EPS into the stream */
[self copyPSCodeInside:&bounds to:psStream];
/* get the buffered up PostScript out of the stream */
NXGetMemoryBuffer(psStream, &data, &dataLen, &maxDataLen);
/* put the buffer in the Pasteboard, free the stream (and the buffer) */
[pb writeType:NXPostScriptPboardType data:data length:dataLen];
NXCloseMemory(psStream, NX_FREEBUFFER);
return self;
}
// Save EPS for the view to a fileName.
//
- (int)saveEPStoFile:(const char *)fileName
{
BOOL saveOK;
int fdes; // File descriptor
int retval = 0;
NXStream *stream = NULL;
if(saveOK =
(((fdes = open(fileName, O_WRONLY|O_CREAT|O_TRUNC, 0644)) != -1) &&
(stream = NXOpenFile(fdes, NX_WRITEONLY)))) {
[self copyPSCodeInside:&bounds to:stream];
}
if(stream) NXClose(stream);
if(fdes != -1) close(fdes);
if(!saveOK) retval = -1;
return retval;
}
// Call back method messaged from computeContour() function.
// The method accumulates contour path information into a linked list
// each time it is called from the function.
// This method is called back from conputeContour() function for
// each contour path it finds. ContourView object passes the id of
// itself as an argument to computeContour() so it can call back.
// icont = level index for contour.
// np = number of points in contour path
// x, y = arrays containing x and y values of each point on contour path.
//
- accumContour:(int)icont :(int)np :(float *)x :(float *)y
{
ContourPath *newcntr; /* scratch pad pointer to new contourList items */
int c_closed = 1;
if(np < minNumPoints) return self; /* too few points for contour */
if (fabs(x[0] - x[np-1]) < smallx && fabs(y[0] - y[np-1]) < smally) {
c_closed = 1;
x[np-1] = x[0]; y[np-1] = y[0]; /* guarantee closure */
}
else
c_closed = 0; /* contour not closed */
/* ---- Save new contour in a linked list ----------------------------------- */
/* Allocate new item for contourList */
newcntr = (ContourPath *)malloc((size_t)sizeof(ContourPath));
newcntr->num_pts = np; /* # of (x,y) points in contour */
newcntr->closed = c_closed; /* contour is closed flag */
newcntr->levelindex = icont; /* levelindex-th contour (indx to contour level) */
newcntr->level = cA[icont].level; /* value of contour */
/* Allocate arrays for X and Y coordinates. */
newcntr->x = (float *)malloc((size_t)(np*sizeof(float)));
newcntr->y = (float *)malloc((size_t)(np*sizeof(float)));
/* copy (x,y) coordinates of contour path */
memcpy((void *)newcntr->x, x, np * sizeof(float));
memcpy((void *)newcntr->y, y, np * sizeof(float));
/* insert new item into the list */
newcntr->next = contourList; /* point to previous one as next */
contourList = newcntr; /* for next time */
numContours++; /* increment number of contours counter */
/* ---- New contour saved in a linked list ----------------------------------- */
return self;
}
// This generates comments block that survives editing by Tailor.app
// for keeping track of the data source and parameters for the plot.
//
- beginTailorGroupWithComments
{
int j;
float rr, gg, bb;
long timenow;
char timestr[32];
DPSContext curContext = DPSGetCurrentContext();
// Do it only if printing or copying to pasteboard.
if(NXDrawingStatus != NX_DRAWING) {
DPSPrintf(curContext, "\n%% Comments below should survive Tailor editing.\n\n");
DPSPrintf(curContext, "(%s\\012", CV_VERSION);
if(comment012)
DPSPrintf(curContext, "%s\\012", comment012);
DPSPrintf(curContext, "%% X (H) range: %g - %g\\012", xdmin, xdmax);
DPSPrintf(curContext, "%% Y (V) range: %g - %g\\012", ydmin, ydmax);
if(gridON)
DPSPrintf(curContext, "%% Grid interval (xintvl, yintvl) = (%g, %g)\\012",
gridXstep, gridYstep);
if(axisON)
DPSPrintf(curContext, "%% Axes at (x, y) = (%g, %g)\\012", axisX, axisY);
DPSPrintf(curContext, "%% Raw grid (%d, %d) interpolated to (%d, %d)\\012",
nxraw, nyraw, nx1, ny1);
DPSPrintf(curContext, "%% Number of contour levels: %d\\012", nclevels);
DPSPrintf(curContext, "%% Number of contour paths: %d\\012", numContours);
DPSPrintf(curContext, "%% Z (value) range: %g -- %g\\012", fdmin, fdmax);
DPSPrintf(curContext, "%% Removed paths with fewer than %d points.\\012",
minNumPoints);
DPSPrintf(curContext, "%% Cross-hair at: (%.3g, %.3g)\\012",
(pX_Vline - bounds.origin.x)/ppxu + xdmin,
(pY_Hline - bounds.origin.y)/ppyu + ydmin);
DPSPrintf(curContext, "%% ---- Graphic Attributes ----\\012");
DPSPrintf(curContext, "%% Contour LineWidth: %g (minor), %g (major)\\012",
minorContourLineWidth, majorContourLineWidth);
DPSPrintf(curContext, "%% Dash (Neg Contour [mark space ...]): [");
for(j=0; j<Nncdash; j++)
DPSPrintf(curContext, "%g ", ncontdash[j]);
DPSPrintf(curContext, "]\\012");
if(gridON) {
DPSPrintf(curContext, "%% Grid Dash [mark space] = [%g %g]\\012",
griddash[0], griddash[1]);
}
NXConvertColorToRGB(positiveColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% Positive Color: %g, %g, %g\\012", rr, gg, bb);
NXConvertColorToRGB(negativeColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% Negative Color: %g, %g, %g\\012", rr, gg, bb);
NXConvertColorToRGB(backgroundColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% BackGND Color: %g, %g, %g\\012", rr, gg, bb);
NXConvertColorToRGB(contourLineColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% ContourLine Color: %g, %g, %g\\012", rr, gg, bb);
DPSPrintf(curContext, ") TailorGroupBegin\n\n");
if(comment) {
DPSPrintf(curContext, "%% For Acrobat Distiller to generate a Note with comments.\n\n");
DPSPrintf(curContext, "[ /Rect [0 0 300 150]\n");
DPSPrintf(curContext, "/Contents (");
DPSPrintf(curContext, "%s)\n", comment);
time(&timenow); strcpy(timestr, ctime(&timenow)); timestr[24] = '\0';
DPSPrintf(curContext, "/Title (ContourView plot: %s)\n", timestr);
DPSPrintf(curContext, "/Open false\n"); /* initialize for iconized appearance */
DPSPrintf(curContext, "/ANN pdfmark\n\n");
}
}
return self;
}
- endTailorGroup
{
DPSContext curContext = DPSGetCurrentContext();
if(NXDrawingStatus != NX_DRAWING) {
DPSPrintf(curContext, "\n%% End of ContourView drawing.\n");
DPSPrintf(curContext, "TailorGroupEnd\n\n");
}
return self;
}
// This puts the data trace info right after the %% comments.
// %%EndComments etc moved to after this, which is not good, but OK.
//
- endHeaderComments
{
int j;
float rr, gg, bb;
DPSContext curContext = DPSGetCurrentContext();
if(NXDrawingStatus != NX_DRAWING) {
DPSPrintf(curContext, "\n\n%s\n", CV_VERSION);
if(comment)
DPSPrintf(curContext, "%s\n", comment);
DPSPrintf(curContext, "%% X (horiz) range: %g - %g\n", xdmin, xdmax);
DPSPrintf(curContext, "%% Y (vert) range: %g - %g\n", ydmin, ydmax);
if(gridON)
DPSPrintf(curContext, "%% Grid interval (xintvl, yintvl) = (%g, %g)\n",
gridXstep, gridYstep);
if(axisON)
DPSPrintf(curContext, "%% Axes at (x, y) = (%g, %g)\n", axisX, axisY);
DPSPrintf(curContext, "%% Raw grid (%d, %d) interpolated to (%d, %d)\n",
nxraw, nyraw, nx1, ny1);
DPSPrintf(curContext, "%% Number of contour levels: %d\n", nclevels);
DPSPrintf(curContext, "%% Number of contour paths: %d\n", numContours);
DPSPrintf(curContext, "%% Z (value) range: %g -- %g\n", fdmin, fdmax);
DPSPrintf(curContext, "%% Removed paths with fewer than %d points.\n",
minNumPoints);
DPSPrintf(curContext, "%% Cross-hair at: (%.3g, %.3g)\n",
(pX_Vline - bounds.origin.x)/ppxu + xdmin,
(pY_Hline - bounds.origin.y)/ppyu + ydmin);
DPSPrintf(curContext, "%% ---- Graphic Attributes ----\n");
DPSPrintf(curContext, "%% Contour LineWidth: %g (minor), %g (major)\n",
minorContourLineWidth, majorContourLineWidth);
DPSPrintf(curContext, "%% Dash (Neg Contour [mark space ...]): [");
for(j=0; j<Nncdash; j++)
DPSPrintf(curContext, "%g ", ncontdash[j]);
DPSPrintf(curContext, "]\n");
if(gridON) {
DPSPrintf(curContext, "%% Grid Dash [mark space] = [%g %g]\n",
griddash[0], griddash[1]);
}
NXConvertColorToRGB(positiveColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% Positive Color (RGB): %g, %g, %g\n", rr, gg, bb);
NXConvertColorToRGB(negativeColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% Negative Color (RGB): %g, %g, %g\n", rr, gg, bb);
NXConvertColorToRGB(backgroundColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% BackGND Color (RGB): %g, %g, %g\n", rr, gg, bb);
NXConvertColorToRGB(contourLineColor, &rr, &gg, &bb);
DPSPrintf(curContext, "%% ContourLine Color (RGB): %g, %g, %g\n", rr, gg, bb);
DPSPrintf(curContext, "%%\n\n");
}
return [super endHeaderComments];
}
// This is inserted for embedding comments that can persist across
// editing by Tailor.app (FirstClass). Tailor strips regular comments
// from its output.
//
- endPrologue
{
DPSContext curContext = DPSGetCurrentContext();
if(NXDrawingStatus != NX_DRAWING) {
DPSPrintf(curContext, "\n\n%%%%BeginResource: procset (Tailor group constructs)\n");
DPSPrintf(curContext, "/TailorGroupBegin {pop} def /TailorGroupEnd {} def\n");
DPSPrintf(curContext, "/pdfmark where\n");
DPSPrintf(curContext, "{pop} {userdict /pdfmark /cleartomark load put} ifelse\n");
DPSPrintf(curContext, "%%%%EndResource\n\n");
}
return [super endPrologue];
}
// Draws objects (lines/markers) that are manipulated by the user.
- drawMarkers
{
DPSContext curContext = DPSGetCurrentContext();
if(pY_Hline > pYmin && pY_Hline < pYmax && pX_Vline > pXmin && pX_Vline < pXmax) {
if(NXDrawingStatus != NX_DRAWING) {
DPSPrintf(curContext, "\n%% Cross-hair\n");
}
PSmoveto(pXmin, pY_Hline);
PSrlineto(bounds.size.width, 0.0);
PSmoveto(pX_Vline, pYmin);
PSrlineto(0.0, bounds.size.height);
PSsetgray(0.66667); // stroke with lightgray, 0 linewidth
PSsetlinewidth(0.0);
PSstroke();
}
return self;
}
// Mandatory method for any View that does something useful.
// This will override the default -drawSelf::, which does no real drawing.
// Should never be called directly. It will be called by [self display];
- drawSelf:(const NXRect *)r:(int)count
{
if(!enabled || !cacheOK) return self;
if(NXDrawingStatus == NX_DRAWING) {
[imageCache composite: NX_SOVER toPoint: &bounds.origin];
[self drawMarkers]; // vert/horiz lines
}
else {
[self beginTailorGroupWithComments];
[self drawAll]; // this does actual PS generation
if(printCrossHair)
[self drawMarkers]; // vert/horiz lines
[self endTailorGroup];
}
return self;
}
// This plots a single contour path with optional color fill.
//
- plotContour:(ContourPath *)cntr :(DPSContext)cContext
{
int i, np;
float xp, yp;
static char *ocmsg[] = {"open", "closed" };
static char *hlmsg[] = {"low", "high" };
np = cntr->num_pts; /* # points in this contour */
if(NXDrawingStatus != NX_DRAWING)
DPSPrintf(cContext,
"\n%% ### Contour: level index=%d, value=%g, #pts=%d, [%s], inside=%s\n",
cntr->levelindex, cntr->level, np,
ocmsg[(cntr->closed)?1:0], hlmsg[(cntr->hi_inside)?1:0]);
for(i=0; i<np; i++) {
xp = (cntr->x[i] - xdmin)*ppxu;
if(invertY)
yp = (ydmax - cntr->y[i])*ppyu;
else
yp = (cntr->y[i] - ydmin)*ppyu;
if(i==0)
PSmoveto(xp, yp);
else
PSlineto(xp, yp);
}
if(cntr->closed)
PSclosepath();
if(doFill) {
if(cntr->hi_inside)
NXSetColor(cA[cntr->levelindex].fillcolor_hi);
else
NXSetColor(cA[cntr->levelindex].fillcolor_lo);
if(doContourStroke) {
PSgsave();
PSfill();
PSgrestore(); // save-restore contour path for later stroke
}
else
PSfill(); // Fill only, no stroke.
}
if(doContourStroke || !doFill) {
if(cA[cntr->levelindex].dash)
PSsetdash(ncontdash, 2, 0.0);
/* fprintf(fpp, "[4 4] 0 setdash\n"); */ /* dashed curve */
else
PSsetdash(psolid, 0, 0.0);
/* fprintf(fpp, "[] 0 setdash\n"); */ /* solid curve */
NXSetColor(cA[cntr->levelindex].linecolor);
PSsetlinewidth(cA[cntr->levelindex].linewidth);
PSstroke();
}
return self;
}
// Finds if inside of contour path is going up or going down
// (higher or lower than the contour level).
// This is critical for filling contours with correct color.
// Contour lines are drawn at a certain height. Colors that should
// fill the inside depends on whether the inside is higher than the level
// or lower. This method tries to figure this out for each contour.
//
- findInsideHighLow:(ContourPath *)cntr
{
int i, i3, j, ii=0, ix=0, iy=0, npp, inside=0, gpfound=0, jx=0, jy=0;
float xstep, ystep, xg=0.0, yg=0.0, fg= 0.0;
static int ipx[] = { 0, -1, 1, 0, -1, 1, 0, -1, 1 };
static int ipy[] = { 0, 0, 0, -1, -1, -1, 1, 1, 1 };
xstep = (xdmax-xdmin)/(float)(nx1-1);
ystep = (ydmax-ydmin)/(float)(ny1-1);
npp = cntr->num_pts;
// Typical setting for hi_inside flag
if(cntr->level > 0.0) cntr->hi_inside = 1;
else cntr->hi_inside = 0;
// Now try to determine it exactly. Start with i=3 in an attempt to get
// a good point that is not on the edge.
for(i3=3; i3<(npp+3); i3++) {
i = i3 % npp; /* i = [3, .. (npp-1), 0, 1, 2] */
if( ![self pointInDomain:cntr->x[i] :cntr->y[i]] )
continue; // this contour point is outside domain
ix = (cntr->x[i] - xdmin)/xstep + 0.5; // indices of closest grid point
iy = (cntr->y[i] - ydmin)/ystep + 0.5;
// find indices (jx, jy) which is inside contour and inside domain
// by checking 3x3 points centered on (ix, iy)
for(j=0; j<9; j++) {
jx = ix + ipx[j];
jy = iy + ipy[j];
xg = xstep*(float)jx + xdmin;
yg = ystep*(float)jy + ydmin;
if([self pointInDomain:xg :yg] && non_zero_winding(xg, yg, cntr->x, cntr->y, npp)==2)
{
inside = 1;
break; // out of for(j..)
}
}
if(!inside) continue; // try another point on contour
ii = i;
gpfound = 1;
break; // good point found.
} /* end of for(i=0...) */
// If inside point not found, use closed point that is outside
if(!inside) {
for(i3=3; i3<(npp+3); i3++) {
i = i3 % npp; /* i = [3, .. (npp-1), 0, 1, 2] */
if( ![self pointInDomain:cntr->x[i] :cntr->y[i]] )
continue; // this contour point is outside domain
jx = (cntr->x[i] - xdmin)/xstep + 0.5; // indices of closest grid point
jy = (cntr->y[i] - ydmin)/ystep + 0.5;
xg = xstep*(float)jx + xdmin;
yg = ystep*(float)jy + ydmin;
if( ![self pointInDomain:xg :yg]) // at least it must be inside domain
continue;
ii = i;
gpfound = 1;
if(non_zero_winding(xg, yg, cntr->x, cntr->y, npp) == 2) inside = 1;
break;
}
}
fg = fd[(jy+N3)*nx+jx+N3]; // grid value
if(inside) {
if(fg > cntr->level) cntr->hi_inside = 1; // inside is high
else cntr->hi_inside = 0;
} else {
if(fg > cntr->level) cntr->hi_inside = 0; // outside is high
else cntr->hi_inside = 1;
}
if(debug && !gpfound)
fprintf(stderr,
"ContourView: Can't determine hi_inside: idx=%d, val=%g, #pt=%d, hi_ins=%d\n",
cntr->levelindex, cntr->level, npp, cntr->hi_inside);
return self;
}
- (BOOL)pointInDomain:(float)xx :(float)yy
{
if(xx >= xdmin && xx <= xdmax && yy >= ydmin && yy <= ydmax)
return YES;
else
return NO;
}
- freeContourList;
{
ContourPath *cntr;
while((cntr = contourList))
{
if(cntr->x) free(cntr->x);
if(cntr->y) free(cntr->y);
contourList = cntr->next;
free(cntr);
};
contourList = NULL;
return self;
}
- setDelegate:anObject
{
delegate = anObject;
return self;
}
- delegate
{
return delegate;
}
- setTag:(int)t
{
tag = t;
return self;
}
- (int)tag
{
return tag;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.