This is CurveView.m in view mode; [Download] [Up]
/* CurveView.m -- Storage Scope object which can display multiple traces in
* first-in-first-out fashion. This is sort of an digital version of a
* long-persistence oscilloscope where you can see the most recent N traces.
* Unlike analog scopes, the persistence is not defined by decay time, but
* CurveView will simply retain the last N waveforms. As a new trace is
* added, the oldest trace will drop out of the display. The number of
* retained traces N (persistence?) may be specified via method:
* - allocTraceCache:(int)ncrvs :(int)npts;. As with everything, drawing
* will slow down as more traces are retained.
* V0.90 92-1-21 Izumi Ohzawa -- The initial version with constant X spacing.
* V0.91 92-2-20 Izumi Ohzawa
* Added NXImage compositing to provide fixed background.
* Added ability to specify trace by (x[], y[]) array.
* V0.92 92-03-16 Izumi Ohzawa
* Added ability to handle Color for traces, data points, and background.
* V0.94 92-03-16 Izumi Ohzawa
* Selection of data points and delegate notification of selection rect.
* Comments insertion of data source info that survives Tailor.app edits.
Usage:
[curveView freeTraceCache];
[curveView allocTraceCache: 1 : Ndisp]; // show only Gabor during fitting
[curveView setScaleX: -(xgsize/2.0+0.2): xgsize/2.0+0.2]; // Xmin Xmax plotrange
[curveView setScaleY: 1.25*tminmin : 1.25*tmaxmax]; // Fixed Y scale
[curveView setXvalues: xdisp]; // load X values for continuous curve(s)
[curveView setDataPoints: xdata :ydata :Npt]; // set fixed data points in BG
[curveView cacheDataPointsAndBackGround]; // put this stuff in NXImage cache
int i;
for(i=0; i<Ndisp; i++)
ydisp[i] = Gabor(paraset, xdisp[i]);
[curveView setCurveColor: NX_COLORRED]; // red
[curveView setNewTrace: ydisp];
[curveView drawIt:self];
*/
#import "CurveView.h"
#import "matalloc.h"
#import <stdlib.h> /* malloc */
#import <time.h>
/* Version of ContourView object. This is entered into PS code generated after NeXT
print package prolog.
*/
#define CV_VERSION "% CurveView.m [V0.94 95-01-29] by Izumi Ohzawa, izumi@pinoko.berkeley.edu."
@implementation CurveView
- (BOOL)acceptsFirstMouse { return YES; }
- (BOOL)acceptsFirstResponder { return YES; }
// ==== MOUSE EVENT, CURSOR, MARKER HANDLING METHODS =========================================
/* This method handles a mouse down. */
- mouseDown:(NXEvent *)event
{
if(!CacheOK) return self;
[window makeFirstResponder:self]; /* give me the first responder status */
[self dragMarker:event]; /* modal drag loop for drag rectangle selection */
[self cacheDataPointsAndBackGround]; /* redraw data points with new selection */
[self display]; /* delete the last of instance drawing */
return self;
}
- (int)dragMarker:(NXEvent *)event
{
int old_mask, drag_happened=0;
int i, select_count=0;
float sx=0.0, sy, ex=0.0, ey, tx, ty;
pstart = event->location; // keep the starting point
[self convertPoint:&pstart fromView:nil];
old_mask = [window addToEventMask:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
if([self lockFocus]) {
PSsetinstance(YES); // Instance drawing ON
PSsetgray(0.333333);
PSsetlinewidth(0.0);
while (event->type != NX_MOUSEUP) {
if (event->type == NX_MOUSEDRAGGED) {
p = event->location;
[self convertPoint:&p fromView:nil];
PSnewinstance(); // erase last rect
if(!drag_happened)
drag_happened = 1; // indicate that at least 1 drag event came
PSrectstroke(pstart.x, pstart.y, p.x - pstart.x, p.y - pstart.y);
}
// Get more events
event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK
waitFor:1000 threshold:NX_BASETHRESHOLD];
} /* end of while( <not mouse-up>) loop */
PSsetinstance(NO); // Instance drawing OFF
[self unlockFocus];
} /* end if([self lockFocus]) .. */
// ### MouseUp detected, do necessary things and exit drag loop ###
[window setEventMask:old_mask]; // restore original event mask
sx = (pstart.x - bounds.origin.x)/RX2PX + Xmin;
sy = (pstart.y - bounds.origin.y)/RY2PY + Ymin;
ex = (p.x - bounds.origin.x)/RX2PX + Xmin;
ey = (p.y - bounds.origin.y)/RY2PY + Ymin;
if(sx > ex) { // swap start and end X
tx = ex;
ex = sx;
sx = tx;
}
if(sy > ey) { // swap start and end Y
ty = ey;
ey = sy;
sy = ty;
}
// indicate points inside rectangle as selected.
if(drag_happened) {
for(i=0; i<ndata; i++) {
if(xdata[i] > sx && xdata[i] < ex && ydata[i] > sy && ydata[i] < ey) {
selected[i] = YES;
select_count++;
}
else
selected[i] = NO;
}
}
// If no point is selected, select all
if(!drag_happened || select_count == 0)
for(i=0; i<ndata; i++) selected[i] = YES;
// Report selection rectangle to delegate in real X, Y unit.
// Do it on mouse up event.
if(delegate && [delegate respondsTo:@selector(selectionRectDidMove::::::)]) {
// tempx, tempy should already be correct and converted
[delegate selectionRectDidMove:tag :sx :sy :ex :ey :drag_happened]; // mouse up
}
return drag_happened;
}
// 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;
}
// This generates comments block that survives editing by Tailor.app
// for keeping track of the data source and parameters for the plot.
//
- beginTailorGroupWithComments
{
DPSContext curContext = DPSGetCurrentContext();
long timenow;
char timestr[32];
// 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",
Xmin, (pXmax - pXmin)/RX2PX + Xmin);
DPSPrintf(curContext, "%% Y (V) range: %g - %g\\012",
Ymin, (pYmax - pYmin)/RY2PY + Ymin);
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 (CurveView 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
{
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 (H) range: %g - %g\n",
Xmin, (pXmax - pXmin)/RX2PX + Xmin);
DPSPrintf(curContext, "%% Y (V) range: %g - %g\n",
Ymin, (pYmax - pYmin)/RY2PY + Ymin);
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];
}
- initFrame:(NXRect *)nf
{
self = [super initFrame:nf];
Xscaled = 0;
ClearFlag =1;
CacheOK = 0;
ncin = 0;
icurve =0;
bbox[0] = pXmin = bounds.origin.x;
bbox[1] = pYmin = bounds.origin.y;
bbox[2] = bounds.size.width;
bbox[3] = bounds.size.height;
pXmax = bounds.size.width + bounds.origin.x;
pYmax = bounds.size.height + bounds.origin.y;
ops = NULL;
coords = NULL;
bgImage = NULL;
xdata = NULL;
ydata = NULL;
selected = NULL;
ndata = 0;
frameON = NO;
curveColor = NULL;
comment = NULL;
comment012 = NULL;
// color default settings
tempColor = NX_COLORBLACK;
markerColor= NXConvertRGBToColor(1.0, 0.4, 0.4);
bgColor= NX_COLORWHITE;
markerSize = 3.0;
return self;
}
- freeTraceCache
{
if(ops) free(ops);
if(coords) free_fmatrix(coords, 0, ncurves-1, 0, 2*npoints-1);
if(bgImage) [bgImage free];
if(curveColor) free(curveColor);
ops = NULL;
coords = NULL;
bgImage = NULL;
curveColor = NULL;
CacheOK = 0;
return self;
}
- free
{
[self freeTraceCache];
if(xdata) free(xdata);
if(ydata) free(ydata);
if(selected) free(selected);
if(comment) free(comment);
if(comment012) free(comment012);
[super free];
return self;
}
// Allocate stuff for DPSDoUserPath() and NXImage for background.
// ncrvs = # of traces in storage memory (first-in first-out)
// npts = # of points to represent each curve.
- allocTraceCache:(int)ncrvs :(int)npts
{
int ic, i;
float xi;
if(CacheOK)
[self freeTraceCache];
npoints = npts;
ncurves = ncrvs;
// color arrays for traces
curveColor = (NXColor *)malloc(ncurves * sizeof(NXColor));
// Allocate arrays for DPSDoUserPath()
ops = (char *)malloc((size_t)npoints*sizeof(char)); /* operator array */
coords = fmatrix(0, ncurves-1, 0, 2*npoints-1); /* 2-d arracy for UserPath */
// Allocate NXImage bitmap for storing background stuff, e.g. data points
bgImage = [[NXImage alloc] initSize:&bounds.size];
[bgImage useCacheWithDepth:NX_DefaultDepth];
// Initialize x-coords for simple traces with no X values information.
xi = pXmax/(npoints-1);
for(ic=0; ic<ncurves; ic++)
{
for(i=0; i<npoints; i++)
{
coords[ic][2*i] = xi*i;
if(i)
ops[i] = dps_lineto;
else
ops[i] = dps_moveto;
}
}
Xscaled = 0; // set it to use X values as specified above
return self;
}
// Coordinate conversion methods from units of real data to point unit
// used in this view. Separate functions for X and Y.
- (float)RXtoPX:(float)rx
{
return(RX2PX*(rx-Xmin) + pXmin);
}
- (float)RYtoPY:(float)ry
{
return(RY2PY*(ry-Ymin) + pYmin);
}
// Set X values for all traces. All traces use same X values.
// Use setNewTraceXY:: method if X values change from one trace to another.
- setXvalues: (float *)xa
{
int i,ic;
float xtmp;
for(i=0; i<npoints; i++)
{
xtmp = [self RXtoPX:xa[i]]; // do scaling
if(xtmp > pXmax) xtmp = pXmax;
if(xtmp < pXmin) xtmp = pXmin;
for(ic=0; ic<ncurves; ic++)
coords[ic][2*i] = xtmp;
}
return self;
}
// Use this method when X values need not be changed AND all traces
// share the same X values.
- setNewTrace: (float *)ya
{
int i;
float ytmp;
for(i=0; i<npoints; i++)
{
ytmp = [self RYtoPY:ya[i]]; // convert from Real Y to points
if(ytmp > pYmax) ytmp = pYmax; // enforce bounds
if(ytmp < pYmin) ytmp = pYmin;
coords[icurve][2*i+1] = ytmp; // DPSDoUserPath can't take out-of-range values!
}
curveColor[icurve] = tempColor;
icurve++;
icurve %= ncurves;
ncin++;
if(ncin > ncurves) ncin = ncurves;
return self;
}
// Use this method when both X and Y values are given for each trace.
// xa[] -- X coordinate array of npoints.
// ya[] -- Y coordinate array of npoints.
// Currently all traces must have the same number of points.
// Ideally, this should be specifiable for each trace provided that the number of
// points for a trace does not exceed allocated array size.
- setNewTraceXY:(float *)xa :(float *)ya
{
int i;
float ytmp=0.0,xtmp=0.0;
for(i=0; i<npoints; i++)
{
ytmp = [self RYtoPY:ya[i]]; // convert from Real Y to points
if(ytmp > pYmax) ytmp = pYmax; // enforce bounds
if(ytmp < pYmin) ytmp = pYmin;
xtmp = [self RXtoPX:xa[i]]; // do scaling for X
if(xtmp > pXmax) xtmp = pXmax;
if(xtmp < pXmin) xtmp = pXmin;
coords[icurve][2*i] = xtmp;
coords[icurve][2*i+1] = ytmp; // DPSDoUserPath can't take out-of-range values!
}
curveColor[icurve] = tempColor;
icurve++;
icurve %= ncurves;
ncin++;
if(ncin > ncurves) ncin = ncurves;
return self;
}
- setDataPoints:(float *)xd :(float *)yd :(int)nnd
{
int i;
if(nnd == 0) {
ndata = 0;
return self;
}
// do reallocation only if # of data points has changed.
if(ndata != nnd) {
if(xdata) free(xdata);
if(ydata) free(ydata);
if(selected) free(selected);
xdata = (float *)malloc((size_t)nnd*sizeof(float));
ydata = (float *)malloc((size_t)nnd*sizeof(float));
selected = (BOOL *)malloc((size_t)nnd*sizeof(BOOL));
}
ndata = nnd;
for(i=0; i<ndata; i++) {
xdata[i] = xd[i];
ydata[i] = yd[i];
selected[i] = YES; // indicate all points selected
}
return self;
}
// This method will plot 'nnd' data points (xd[i], yd[i]) into the NXImage cache,
// and XY axes as background. This NXImage bitmap cache will be composited to the
// view before traces are drawn. Assumption here is that data points do not change
// a lot, but traces are drawn many more times. This assumption may not be correct,
// in which case other optimizations must be made.
- cacheDataPointsAndBackGround
{
// Draw fixed image into NXImage cache..
if([bgImage lockFocus])
{
[self drawDataPointsAndBackGround];
[bgImage unlockFocus];
} /* end of if([bgImage lockFocus])... */
CacheOK = 1;
return self;
}
// This method will plot 'ndata' data points (xdata[i], ydata[i]) into a cache or a view.
- drawDataPointsAndBackGround
{
int i;
NXSetColor(bgColor);
NXRectFill(&bounds); /* clear */
// X-Y axes
PSsetgray(0.666667); /* light gray */
PSmoveto(pXmin, [self RYtoPY:0.0]);
PSlineto(pXmax, [self RYtoPY:0.0]); // horizontal line at Y=0
PSmoveto([self RXtoPX:0.0], pYmin);
PSlineto([self RXtoPX:0.0], pYmax); // vertical line at X=0
PSstroke();
// Frame rectangle
if(NXDrawingStatus == NX_DRAWING || frameON) {
PSsetgray(0.0); /* black */
PSrectstroke(pXmin,pYmin+0.1,pXmax-0.25,pYmax-0.25); /* frame rectangle */
}
if(ndata == 0) return self; // don't go further if no data points
// Data points as colored circles
NXSetColor(markerColor);
// Plot data points from storage (instance vars).
for(i=0; i<ndata; i++) {
PSarc([self RXtoPX:xdata[i]], [self RYtoPY:ydata[i]], markerSize, 0.0, 360.0);
PSclosepath();
if(selected[i])
PSfill(); // filled circle for selected points
else
PSstroke(); // open circle for unselected points
}
return self;
}
- autoScale
{
return self;
}
- setScaleY:(float)ymin : (float)ymax
{
Ymin = ymin;
RY2PY = (pYmax - pYmin)/(ymax - ymin); // scaling factor for RealY -> PointY
return self;
}
- setScaleX:(float)xmin :(float)xmax
{
[self setXscalingEnable: 1]; // Enable X scaling
Xmin = xmin;
RX2PX = (pXmax - pXmin)/(xmax - xmin); // scaling factor for RealX -> PointX
return self;
}
// Set X scaling flag -- Y scaling is always enabled.
- setXscalingEnable:(int)xse
{
int ic, i;
float xi;
Xscaled = xse;
if(!Xscaled)
{
xi = pXmax/(npoints-1);
for(ic=0; ic<ncurves; ic++)
for(i=0; i<npoints; i++)
coords[ic][2*i] = xi*i;
}
return self;
}
- setFrameEnabled:(BOOL)yn
{
frameON = yn;
return self;
}
- setCurveColor:(NXColor)color
{
tempColor = color;
return self;
}
- setMarkerColor:(NXColor)color
{
markerColor = color;
return self;
}
- setMarkerSize: (float)size
{
markerSize = size;
return self;
}
- setBgColor:(NXColor)color
{
bgColor = color;
return self;
}
- drawIt:sender
{
ClearFlag =0;
[self display];
return self;
}
- clear:sender
{
ClearFlag =1;
[self display];
return self;
}
- copy:sender
{
[self copyPScode:sender];
return self;
}
// Copies the current view into the Pasteboard as PostScript.
// Following code taken from the Graph example in NextDeveloper/Examples.
//
- 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;
}
// 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
{
int ic;
if(ClearFlag || !CacheOK) {
PSsetgray(1.0);
NXRectFill(&bounds); /* clear */
}
else {
if(NXDrawingStatus == NX_DRAWING) {
[bgImage composite: NX_SOVER toPoint: &bounds.origin ];
PSsetlinewidth(0.0);
}
else {
[self beginTailorGroupWithComments];
[self drawDataPointsAndBackGround]; // for printing/copying
PSsetlinewidth(1.0);
}
// Now draw dynamic traces..
for(ic=0; ic<ncin; ic++) {
NXSetColor(curveColor[ic]);
DPSDoUserPath(coords[ic], npoints*2, dps_float, ops, npoints, bbox, dps_ustroke);
}
if(NXDrawingStatus != NX_DRAWING)
[self endTailorGroup];
}
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.