Annotation of Examples/AppKit/Graph/LineGraph.m, revision 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.