|
|
1.1 ! root 1: ! 2: /* ! 3: LineGraph.m ! 4: ! 5: LineGraph is a simple view which plots a set of points as a connected line. ! 6: LineGraph draws the graph using a userpath. It retains the two arrays ! 7: that make up the userpath: the coordinates of the userpath and the ! 8: userpath operators (e.g., dps_lineto). ! 9: ! 10: You may freely copy, distribute, and reuse the code in this example. ! 11: NeXT disclaims any warranty of any kind, expressed or implied, as to its ! 12: fitness for any particular use. ! 13: */ ! 14: ! 15: #import "Graph.h" ! 16: ! 17: static void drawAxes(float x1, float y1, float x2, float y2); ! 18: static void testBounds(float *min, float *max, float *points, int num); ! 19: ! 20: #define POS_INFINITY (1.0/0.0) ! 21: #define NEG_INFINITY (-1.0/0.0) ! 22: #define IS_NAN(x) ((x)!=(x)) /* IEEE test for NAN (not a number) */ ! 23: #define IS_STRANGE(x) (IS_NAN(x) || x == POS_INFINITY || x == NEG_INFINITY) ! 24: ! 25: ! 26: @implementation LineGraph ! 27: ! 28: - initFrame:(NXRect *)aRect { ! 29: [super initFrame:aRect]; ! 30: lineGray = NX_WHITE; ! 31: backgroundGray = NX_BLACK; ! 32: return self; ! 33: } ! 34: ! 35: - free { ! 36: NXZoneFree([self zone], points); ! 37: NXZoneFree([self zone], ops); ! 38: return [super free]; ! 39: } ! 40: ! 41: - setPoints:(int)num x:(float *)x y:(float *)y ! 42: minX:(float)minX minY:(float)minY maxX:(float)maxX maxY:(float)maxY { ! 43: float *f; ! 44: int i; ! 45: char *op; ! 46: NXZone *zone; ! 47: int segPoints; ! 48: ! 49: /* ! 50: * We copy these points into our own an array that we later use when ! 51: * we draw them as a userpath. This array has the x and y values ! 52: * interleaved. We also generate an array of userpath operators that we ! 53: * will use to draw the userpath. These operators are just a moveto to ! 54: * get to the start of the line, and then lineto's connecting all the points ! 55: * from there on. ! 56: * ! 57: * We also filter out the troublesome floating point values of positive ! 58: * infinity, negative infinity and NAN ("not a number"). When we encounter ! 59: * these values, we skip that particular point and remember to start a new ! 60: * segment of the graph when we see the next well behaved value. ! 61: */ ! 62: zone = [self zone]; ! 63: NXZoneFree(zone, points); ! 64: NXZoneFree(zone, ops); ! 65: numPoints = num; ! 66: points = NXZoneMalloc(zone, 2 * num * sizeof(float)); ! 67: ops = NXZoneMalloc(zone, num * sizeof(char)); ! 68: numPoints = 0; ! 69: segPoints = 0; ! 70: for (f = points, op = ops, i = num; i--; ) { ! 71: if (IS_STRANGE(*x) || IS_STRANGE(*y)) { ! 72: if (segPoints == 1) { ! 73: /* ! 74: * This is a the case of a well behaved point with weird values ! 75: * on either side. In this case we just make a tiny line ! 76: * segment of this point to itself to. ! 77: */ ! 78: *f++ = f[-2]; ! 79: *f++ = f[-2]; ! 80: *op++ = dps_lineto; ! 81: numPoints++; ! 82: } ! 83: segPoints = 0; ! 84: x++; ! 85: y++; ! 86: } else { ! 87: *f++ = *x++; ! 88: *f++ = *y++; ! 89: if (segPoints == 0) ! 90: *op++ = dps_moveto; ! 91: else ! 92: *op++ = dps_lineto; ! 93: segPoints++; ! 94: numPoints++; ! 95: } ! 96: } ! 97: if (numPoints > 0) { ! 98: testBounds(&minX, &maxX, points, numPoints); ! 99: bbox[0] = minX; ! 100: bbox[2] = maxX; ! 101: testBounds(&minY, &maxY, points+1, numPoints); ! 102: bbox[1] = minY; ! 103: bbox[3] = maxY; ! 104: } else { ! 105: bbox[0] = bbox[1] = -1; ! 106: bbox[2] = bbox[3] = 1; ! 107: } ! 108: return self; ! 109: } ! 110: ! 111: - sizeTo:(NXCoord)width :(NXCoord)height { ! 112: NXPoint center; ! 113: ! 114: /* ! 115: * We override sizeto so that after we're resized the point at the center ! 116: * of the view remains the same. We just note the center, pass the message ! 117: * up to the super class, and then reset the center to what it was. ! 118: */ ! 119: center.x = NX_X(&bounds) + NX_WIDTH(&bounds) / 2; ! 120: center.y = NX_Y(&bounds) + NX_HEIGHT(&bounds) / 2; ! 121: [super sizeTo:width :height]; ! 122: [self setDrawOrigin:center.x - NX_WIDTH(&bounds) / 2 ! 123: :center.y - NX_HEIGHT(&bounds) / 2]; ! 124: return self; ! 125: } ! 126: ! 127: - scaleToFit { ! 128: float scale; ! 129: float bbWidth = bbox[2] - bbox[0]; ! 130: float bbHeight = bbox[3] - bbox[1]; ! 131: ! 132: /* ! 133: * First we figure out how much we need to scale so that our current graph ! 134: * will just fit within the view. We do this by taking the ratio of the ! 135: * the view's current size to the bounding box of the graph. We also add ! 136: * a little fudge factor of 0.95 so the graph will have a little border ! 137: * space around it. ! 138: */ ! 139: if (bbWidth == 0 && bbHeight == 0) ! 140: scale = 10; ! 141: else if (bbWidth == 0) ! 142: scale = NX_HEIGHT(&frame) / bbHeight * 0.95; ! 143: else if (bbHeight == 0) ! 144: scale = NX_WIDTH(&frame) / bbWidth * 0.95; ! 145: else ! 146: scale = MIN(NX_WIDTH(&frame) / bbWidth, ! 147: NX_HEIGHT(&frame) / bbHeight) * 0.95; ! 148: ! 149: /* scale the size of the view */ ! 150: [self setDrawSize:NX_WIDTH(&frame) / scale :NX_HEIGHT(&frame) / scale]; ! 151: ! 152: /* ! 153: * translate the view so the graph's bounding box is in the lower left corner ! 154: */ ! 155: [self setDrawOrigin:bbox[0] - (NX_WIDTH(&bounds) - bbWidth) / 2 ! 156: :bbox[1] - (NX_HEIGHT(&bounds) - bbHeight) / 2]; ! 157: return self; ! 158: } ! 159: ! 160: - zoom:(float)scale { ! 161: NXPoint center; ! 162: ! 163: /* remember the center to we can reset it after the scaling */ ! 164: center.x = NX_X(&bounds) + NX_WIDTH(&bounds) / 2; ! 165: center.y = NX_Y(&bounds) + NX_HEIGHT(&bounds) / 2; ! 166: ! 167: /* scale the view to its new size */ ! 168: [self setDrawSize:NX_WIDTH(&bounds) / scale :NX_HEIGHT(&bounds) / scale]; ! 169: ! 170: /* reset the center point */ ! 171: [self setDrawOrigin:center.x - NX_WIDTH(&bounds) / 2 ! 172: :center.y - NX_HEIGHT(&bounds) / 2]; ! 173: return self; ! 174: } ! 175: ! 176: /* ! 177: * In both the drawSelf method and the drawAxes function, we use a little ! 178: * trick to get the right line width regardless of how much we are zoomed ! 179: * in. If we are drawing on the screen, we just draw the lines with zero ! 180: * width. This the fastest way to draw lines, and ensures they will always ! 181: * one pixel wide. When drawing on the printer, we do the trick of ! 182: * setting the matrix to the default matrix after the path has been made ! 183: * but before it is stroked. Since the line width is actually used when ! 184: * the path is stroked, this makes the line width be the same regardless of ! 185: * what scale we were at when we stroked the path. The purpose of this is ! 186: * to make the line width independent of how far in or our we are zoomed. ! 187: */ ! 188: ! 189: - drawSelf:(const NXRect *)rects :(int)rectCount { ! 190: PSsetgray(backgroundGray); ! 191: NXRectFill(&bounds); ! 192: drawAxes(NX_X(&bounds), NX_Y(&bounds), NX_MAXX(&bounds), NX_MAXY(&bounds)); ! 193: if (points && numPoints > 0) { ! 194: PSnewpath(); ! 195: PSsetlinewidth(NXDrawingStatus == NX_DRAWING ? 0.0 : 1.0); ! 196: PSsetgray(lineGray); ! 197: if (NXDrawingStatus == NX_DRAWING) ! 198: /* stroke the userpath */ ! 199: DPSDoUserPath(points, numPoints * 2, dps_float, ops, numPoints, ! 200: bbox, dps_ustroke); ! 201: else { ! 202: /* append the userpath to the current path, but dont stroke yet */ ! 203: DPSDoUserPath(points, numPoints * 2, dps_float, ops, numPoints, ! 204: bbox, dps_uappend); ! 205: /* trick to ensure line width is independent of zoom */ ! 206: PSgsave(); ! 207: PSmatrix(); ! 208: PSdefaultmatrix(); ! 209: PSsetmatrix(); ! 210: PSstroke(); ! 211: PSgrestore(); ! 212: } ! 213: } ! 214: return self; ! 215: } ! 216: ! 217: ! 218: /* ! 219: * Simple line drawer, used to draw the axes of the graph in the current ! 220: * color and line width. ! 221: */ ! 222: static void drawAxes(float x1, float y1, float x2, float y2) { ! 223: PSsetlinewidth(NXDrawingStatus == NX_DRAWING ? 0.0 : 0.4); ! 224: PSsetgray(NX_DKGRAY); ! 225: PSnewpath(); ! 226: PSmoveto(0.0, y1); ! 227: PSlineto(0.0, y2); ! 228: PSmoveto(x1, 0.0); ! 229: PSlineto(x2, 0.0); ! 230: if (NXDrawingStatus != NX_DRAWING) { ! 231: /* trick to ensure line width is independent of zoom */ ! 232: PSgsave(); ! 233: PSmatrix(); ! 234: PSdefaultmatrix(); ! 235: PSsetmatrix(); ! 236: PSstroke(); ! 237: PSgrestore(); ! 238: } else ! 239: PSstroke(); ! 240: } ! 241: ! 242: - setLineGray:(float)gray { ! 243: lineGray = gray; ! 244: return self; ! 245: } ! 246: ! 247: - setBackgroundGray:(float)gray { ! 248: backgroundGray = gray; ! 249: return self; ! 250: } ! 251: ! 252: - (float)lineGray { ! 253: return lineGray; ! 254: } ! 255: ! 256: - (float)backgroundGray { ! 257: return backgroundGray; ! 258: } ! 259: ! 260: /* ! 261: * Tests the bounds for NAN or infinite values. If there are such values, ! 262: * we recalc the bounds with the given points. Since these points are ! 263: * the interleaved x-y pairs for the userpath, we skip every other value, ! 264: * since we want to only consider x's or y's. ! 265: */ ! 266: static void testBounds(float *min, float *max, float *points, int num) { ! 267: if (IS_STRANGE(*min) || IS_STRANGE(*max)) { ! 268: *min = *max = *points; ! 269: while (--num) { ! 270: points += 2; ! 271: if (*points > *max) ! 272: *max = *points; ! 273: else if (*points < *min) ! 274: *min = *points; ! 275: } ! 276: } ! 277: } ! 278: ! 279: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.