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.