Annotation of Examples/AppKit/Graph/LineGraph.m, revision 1.1.1.1

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.