|
|
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.