ftp.nice.ch/pub/next/developer/objc/appkit/ContourPlot.1.4.NIHS.bs.tar.gz#/ContourPlot/ContourView.m

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.