|
|
1.1 root 1: /*
2: * BarChart, A simple multi source loadable class.
3: *
4: * Written by: Joe Freeman 7/92
5: *
6: * HSB color sweep stolen from some of Randy Nelson's code
7: * should use NXEqualColor() for duplicates
8: *
9: */
10:
11:
12: #import "ChartOfMatrix.h"
13: #import <dpsclient/psops.h>
14: #import <c.h>
15: #import <stdio.h>
16:
17: #define NUM_BOGUS 8 /* number of bars to draw when no data */
18: #define COM_VERSION 3.3 /* version 3 ported to 3.0 */
19:
20: @implementation ChartOfMatrix
21:
22:
23: /*============================================================
24: *factory
25: *============================================================*/
26:
27:
28: + initialize
29: {
30: [super initialize];
31: [ChartOfMatrix setVersion:2];
32: return self;
33: }
34:
35: - (const char *)getInspectorClassName
36: {
37: return "ChartOfMatrixInspector"; }
38:
39: - initFrame:(NXRect *)r
40: {
41: self = [super initFrame:r];
42: minSheetSet = 0.0;
43: maxSheetSet = 1.0;
44: COM_Flags.autoScale = YES;
45: COM_Flags.drawType = DRAW_V_BAR;
46: backgroundColor = NX_COLORWHITE;
47: highlightColor = NX_COLORBLACK;
48: highlightIndex = MAXINT; /* don't show on default */
49: COM_Flags.drawFrame = YES;
50: hMargin = vMargin = 15.0;
51: hMargin = vMargin = 5.0;
52: numPrototypes = 5;
53: COM_Flags.randomBarColors = YES;
54: return self;
55: }
56:
57: - awake
58: {
59: [super awake];
60: highlightIndex = MAXINT;
61: [self registerForDraggedTypes:&NXColorPboardType count:1];
62: return self;
63: }
64:
65: /*============================================================
66: * color dragging support
67: *============================================================*/
68:
69: - (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
70: {
71: if ([sender draggingSourceOperationMask] & NX_DragOperationGeneric) {
72: return NX_DragOperationGeneric;
73: } else {
74: return NX_DragOperationNone;
75: }
76: }
77:
78: - (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
79: {
80: NXColor c = NXReadColorFromPasteboard([sender draggingPasteboard]);
81: [self setBackgroundColor: c];
82: return YES;
83: }
84: /*============================================================
85: * instance set / query methods
86: *============================================================*/
87:
88: - setDataSrc:anObject
89: {
90: dataSrc = anObject;
91: return self;
92: }
93:
94: - setGraphType:(int)drawCode
95: { COM_Flags.drawType = drawCode; [self update]; return self; }
96: - (int)graphType
97: { return COM_Flags.drawType; }
98:
99: - takeRandomColorStateFrom:sender
100: { [self setRandomBarColorEnabled:[sender state]]; return self; }
101: - setRandomBarColorEnabled:(BOOL)flag
102: { COM_Flags.randomBarColors = flag; [self update]; return self; }
103: - (BOOL)isRandomBarColorEnabled
104: { return COM_Flags.randomBarColors; }
105:
106: - takeBackgroundColorFrom:sender
107: { [self setBackgroundColor: [sender color]]; return self; }
108: - setBackgroundColor:(NXColor)aColor
109: { backgroundColor = aColor; [self update]; return self; }
110: - (NXColor)backgroundColor
111: { return backgroundColor; }
112:
113: - takeHighlightColorFrom:sender
114: { [self setHighlightColor: [sender color]]; return self; }
115: - setHighlightColor:(NXColor)aColor
116: { highlightColor = aColor; [self update]; return self; }
117: - (NXColor)highlightColor
118: { return highlightColor; }
119:
120:
121: /* set and query the size for the margins (in points) */
122: - takeHMarginFrom:sender
123: { hMargin = [sender floatValue]; [self update]; return self; }
124: - takeVMarginFrom:sender
125: { vMargin = [sender floatValue]; [self update]; return self; }
126: - (float)hMargin
127: { return hMargin; }
128: - (float)vMargin
129: { return vMargin; }
130:
131: - takeAutoScaleStateFrom:sender
132: { COM_Flags.autoScale = [sender floatValue]; return self; }
133: - setAutoScale:(BOOL)flag
134: { COM_Flags.autoScale = flag; [self update]; return self; }
135: - (BOOL)autoScale
136: { return COM_Flags.autoScale; }
137:
138: /* fancy controls */
139: - takeFrameStateFrom:sender
140: { COM_Flags.drawFrame = [sender state]; [self update]; return self; }
141: - (BOOL)frameState
142: { return COM_Flags.drawFrame; }
143:
144: - takeNumProtosFrom:sender
145: { numPrototypes= [sender intValue]; [self update]; return self; }
146: - (int)numProtos
147: { return numPrototypes; }
148:
149: - takeBorderTypeFrom:sender
150: { borderType = [sender tag]; [self update]; return self; }
151: - (int)borderType
152: { return borderType; }
153:
154: /* when autoscale is off, set the min and max for the sheet */
155: - takeMinValueFrom:sender
156: { minSheetSet = [sender floatValue]; return self; }
157: - takeMaxValueFrom:sender
158: { maxSheetSet = [sender floatValue]; return self; }
159: - (double)minValue
160: { return minSheetSet; }
161: - (double)maxValue
162: { return maxSheetSet; }
163:
164:
165: /*============================================================
166: * target/action
167: *============================================================*/
168:
169: - (BOOL)acceptsFirstResponder { return YES; }
170:
171: - copy:sender
172: {
173: id pb = [Pasteboard new]; /* global pasteboard object */
174: NXStream *st; /* stream to collect data in */
175: char *data; /* actual data buffer */
176: int length; /* length of data */
177: int maxLength; /* (not used here) */
178:
179: /* declare that we will supply a single type of data, eps */
180: [pb declareTypes:&NXPostScriptPboard num:1 owner:self];
181:
182: /* get a stream which writes to memory */
183: st = NXOpenMemory (NULL, 0, NX_WRITEONLY);
184:
185: /* find bounding box and then write it to the stream */
186: [self copyPSCodeInside:&bounds to:st];
187:
188: /* get actuall data buffer form stream */
189: NXGetMemoryBuffer (st, &data, &length, &maxLength);
190:
191: /* write data to pasteboard */
192: [pb writeType:NXPostScriptPboard data:data length:length ];
193:
194: /* dealocate stream including it's buffer */
195: NXCloseMemory (st, NX_FREEBUFFER );
196:
197:
198: return self;
199: }
200:
201: /*============================================================
202: * do real work
203: *============================================================*/
204:
205: - (int)numLocations
206: {
207: int numRows,numCols;
208:
209: if ([dataSrc respondsTo:@selector(getValue:forProperty:at:)]){
210: return [dataSrc count];
211: } else if ([dataSrc respondsTo:@selector(selectedCell)]){
212: [dataSrc getNumRows:&numRows numCols:&numCols];
213: return MAX(numRows,numCols);
214: } else if (!dataSrc) {
215: /* put up some dummy graph on palette */
216: return NUM_BOGUS;
217: }
218: return 0;
219: }
220:
221: - (float)valueOfLocation:(int)n
222: {
223: static float data[NUM_BOGUS] = {.1, .3, .2, .7, .4, .8, .5, .98};
224: int numRows,numCols;
225: float theValue;
226: id mrValue = [[DBValue alloc] init];
227:
228: if ([dataSrc respondsTo:@selector(getValue:forProperty:at:)]){
229: [dataSrc getValue:mrValue forProperty:mrExpression at:n];
230: theValue = [mrValue floatValue];
231: [mrValue free];
232: return theValue;
233: } else if ([dataSrc respondsTo:@selector(selectedCell)]){
234: [dataSrc getNumRows:&numRows numCols:&numCols];
235: if (numCols == 1) {
236: return [[dataSrc cellAt:n :0] floatValue];
237: } else { /* numRows == 1 */
238: return [[dataSrc cellAt:0 :n] floatValue];
239: }
240: } else if (!dataSrc) {
241: return data[n];
242: }
243:
244: return 0.0;
245: }
246:
247: /*============================================================
248: * target action loading
249: *============================================================*/
250:
251: - plotFromMatrix:sender
252: {
253: if (!dataSrc && [sender respondsTo:@selector(selectedCell)]){
254: [self setDataSrc:sender];
255: }
256: [self update];
257: return self;
258: }
259:
260: /*============================================================
261: * dbKit support
262: *============================================================*/
263:
264: - associationContentsDidChange:association
265: {
266: mrFetchGroup = [association fetchGroup];
267: dataSrc = [mrFetchGroup recordList];
268: highlightIndex = [mrFetchGroup currentRecord];
269: mrExpression = [association expression];
270: [self update];
271: return self;
272: }
273:
274: /*
275: ** adh 7/27/92
276: ** Do this so we redraw when values are updated in the UI
277: */
278: - association:association setValue:(DBValue *)value
279: {
280: return [self update];
281: }
282:
283: - associationSelectionDidChange:association
284: {
285: /* assume fetchgroup doesn't change so don't update mrFetchGroup */
286: highlightIndex = [[association fetchGroup] currentRecord];
287: return [self update];
288: }
289:
290: - associationCurrentRecordDidDelete:association
291: {
292: /* assumes record list is the same */
293: return [self update];
294: return self;
295: }
296:
297: /*============================================================
298: * do a selection with the mouse
299: *============================================================*/
300:
301: - mouseDown:(NXEvent *)theEvent
302: {
303: NXEvent lastEvent;
304: NXRect rectOfBar;
305: NXRect drawRect;
306: int longSize = [self numLocations];
307: int index;
308:
309:
310: if ((COM_Flags.drawType != DRAW_H_BAR &&
311: COM_Flags.drawType != DRAW_V_BAR ) ||
312: ![dataSrc respondsTo:@selector(getValue:forProperty:at:)]) {
313: NXBeep();
314: return nil;
315: }
316:
317: lastEvent = *theEvent;
318: [self convertPoint: &lastEvent.location fromView:nil];
319: /* modify the mouse position to be in psuedo drawing area coords */
320: lastEvent.location.x -= hMargin;
321: lastEvent.location.y -= vMargin;
322:
323: /* allow for the margins when calc'ing the bar moused down on */
324: drawRect = bounds;
325: drawRect.origin.x += hMargin;
326: drawRect.origin.y += vMargin;
327: drawRect.size.width -= (2*hMargin);
328: drawRect.size.height -= (2*vMargin);
329:
330: for ( index = 0 ; index < longSize; index++){
331: if (COM_Flags.drawType == DRAW_V_BAR) {
332: [self calcRect:&rectOfBar ofBar:index
333: insideRect:&drawRect
334: usingMin:NX_Y(&bounds) ];
335: rectOfBar.origin.y = bounds.origin.y;
336: rectOfBar.size.height = bounds.size.height;
337: } else if (COM_Flags.drawType == DRAW_H_BAR) {
338: [self calcRect:&rectOfBar ofBar:index
339: insideRect:&drawRect
340: usingMin:NX_X(&bounds) ];
341: rectOfBar.origin.x = bounds.origin.x;
342: rectOfBar.size.width = bounds.size.width;
343: } else {
344: return nil;
345: }
346: if ([self mouse:&lastEvent.location inRect:&rectOfBar]){
347: [mrFetchGroup setCurrentRecord:index];
348: break;
349: }
350: }
351: return self;
352: }
353:
354: /*============================================================
355: * target/action
356: *============================================================*/
357:
358: - read:(NXTypedStream *)stream
359: {
360: int tmpScale, tmpDrawFrame, tmpDrawType;
361: [super read:stream];
362: dataSrc = NXReadObject(stream);
363: minField = NXReadObject(stream);
364: maxField = NXReadObject(stream);
365: meanField = NXReadObject(stream);
366: /* the first demo palette went out as version 1 */
367: if (NXTypedStreamClassVersion(stream, "ChartOfMatrix")<2) {
368: NXReadTypes(stream,"ffffiii",
369: &minSheetSet,&maxSheetSet,
370: &vMargin,&hMargin,
371: &tmpScale,
372: &tmpDrawType,
373: &tmpDrawFrame);
374: COM_Flags.autoScale = tmpScale;
375: COM_Flags.drawType = tmpDrawType;
376: COM_Flags.drawFrame = tmpDrawFrame;
377: COM_Flags.randomBarColors = YES;
378: } else {
379: NXReadTypes(stream,"ffffi",
380: &minSheetSet,&maxSheetSet,
381: &vMargin,&hMargin,
382: &COM_Flags);
383: }
384:
385: backgroundColor = NXReadColor(stream);
386: highlightColor = NXReadColor(stream);
387: NXReadTypes(stream,"ii",
388: &numPrototypes,
389: &borderType);
390: return self;
391: }
392:
393: - write:(NXTypedStream *)stream
394: {
395: [super write:stream];
396: NXWriteObjectReference(stream, dataSrc);
397: NXWriteObjectReference(stream, minField);
398: NXWriteObjectReference(stream, maxField);
399: NXWriteObjectReference(stream, meanField);
400: NXWriteTypes(stream,"ffffi",
401: &minSheetSet,&maxSheetSet,
402: &vMargin,&hMargin,
403: &COM_Flags);
404: NXWriteColor(stream, backgroundColor);
405: NXWriteColor(stream, highlightColor);
406: NXWriteTypes(stream,"ii",
407: &numPrototypes,
408: &borderType);
409: return self;
410: }
411:
412: /*============================================================
413: *display
414: *============================================================*/
415:
416: - calcMin:(float *)rMin andMax:(float *)rMax andMean:(float *)rMean;
417: {
418: int index;
419: int longSize;
420: float thisVal;
421: float sumAll = 0;
422:
423: *rMin = *rMax = [self valueOfLocation:0];
424: longSize = [self numLocations];
425:
426: /* first figure out what the maxs and mins are */
427: for ( index = 0 ; index < longSize; index++){
428: thisVal = [self valueOfLocation:index];
429: if (*rMin > thisVal) *rMin = thisVal;
430: if (*rMax < thisVal) *rMax = thisVal;
431: sumAll += thisVal;
432: }
433: *rMean = sumAll / longSize;
434:
435: [minField setFloatValue:*rMin];
436: [maxField setFloatValue:*rMax];
437: [meanField setFloatValue:*rMean];
438: return self;
439: }
440:
441: - renderVLines:(NXRect *)r min:(float )minSheetVal max:(float )maxSheetVal
442: {
443: int longSize; /* numRows or numCols, whichever is the long side */
444: int index;
445: float thisVal;
446: float cellWidth; /* the width of a unit (N) to plot */
447:
448: /* scale the plot */
449: PSscale(1.0, r->size.height / ( maxSheetVal - minSheetVal) );
450: PStranslate(0.0, -minSheetVal);
451:
452: longSize = [self numLocations];
453: cellWidth = r->size.width / (longSize);
454:
455: /* now plot each square */
456: PSsetgray(NX_BLACK);
457: thisVal = [self valueOfLocation:0];
458: PSmoveto(cellWidth/2.0, thisVal);
459: for ( index = 0 ; index < longSize; index++){
460: thisVal = [self valueOfLocation:index];
461: PSlineto(cellWidth*index+ cellWidth/2.0, thisVal);
462: }
463: PSstroke();
464: return self;
465: }
466:
467: /* we can position the bars anywhere inside the view by changing the
468: * insideRect parameter
469: */
470: - calcRect:(NXRect *)r
471: ofBar:(int)n
472: insideRect:(NXRect *)boundingRect
473: usingMin:(float)minSheetVal
474: {
475: float thisVal;
476: float cellWidth; /* width of a unit (N) to plot */
477:
478: thisVal = [self valueOfLocation:n];
479: if (COM_Flags.drawType == DRAW_V_BAR){
480: cellWidth = boundingRect->size.width / (3 * [self numLocations] + 1);
481: r->origin.x = n * 3 * cellWidth + cellWidth;
482: if (thisVal < 0.0){
483: r->origin.y = thisVal;
484: r->size.height = -thisVal;
485: }else{
486: r->origin.y = MAX(minSheetVal, 0.0);
487: r->size.height= thisVal-r->origin.y;
488: }
489: r->size.width = 2 * cellWidth;
490: } else /* assume h bar */ {
491: cellWidth = boundingRect->size.height / (3 * [self numLocations] + 1);
492: r->origin.y = n * 3 * cellWidth + cellWidth;
493: if (thisVal < 0.0){
494: r->origin.x = thisVal;
495: r->size.width = -thisVal;
496: }else{
497: r->origin.x = MAX(minSheetVal, 0.0);
498: r->size.width= thisVal - r->origin.x;
499: }
500: r->size.height = 2 * cellWidth;
501: }
502:
503: return self;
504: }
505:
506: /* this should probably be empty but because Kris asked for a bar/lines switch
507: * in the inspectorwe have the abillity to draw bars (vertical). Of course
508: * this makes the vertical bar drawing object almost codeless
509: */
510: - renderBars:(NXRect *)r min:(float )minSheetVal max:(float )maxSheetVal
511: {
512: int longSize; /* numRows/numCols, whichever is long side */
513: int index;
514: NXRect barRect; /* bounding rectangle for bar in the chart */
515: NXColor HSBColor; /* in case randomBarColors */
516:
517: longSize = [self numLocations];
518: if (COM_Flags.drawType == DRAW_V_BAR){
519: /* scale the plot */
520: PSscale(1.0, r->size.height / ( maxSheetVal - minSheetVal) );
521: PStranslate(0.0, -minSheetVal);
522: } else /* assume h bar */ {
523: /* scale the plot */
524: PSscale(r->size.width / ( maxSheetVal - minSheetVal), 1.0 );
525: PStranslate( -minSheetVal, 0.0);
526: }
527:
528: /* now plot each square */
529: for ( index = 0 ; index < longSize; index++){
530: if (index == highlightIndex)
531: NXSetColor(highlightColor);
532: else if (!COM_Flags.randomBarColors) {
533: PSsetgray ((1.0 / (longSize+2.0)) * (index +1));
534: } else {
535: HSBColor = NXConvertHSBToColor(
536: ((float)index / (float)longSize),
537: 1.0, 1.0);
538: NXSetColor(HSBColor);
539: }
540: [self calcRect:&barRect ofBar:index
541: insideRect:(NXRect *)r
542: usingMin:minSheetVal];
543: NXRectFill(&barRect);
544: }
545: return self;
546: }
547:
548:
549: /* spacing: Each plot is a width of 2N and each gap is a width of 1N
550: * total width is m*(2N+N) + N = 3mN+N = where m = number of bars
551: * Thus N = width / (3m+1)
552: *
553: * scaling: The scaling can make some stuff look pretty funny.
554: */
555: - drawSelf:(NXRect *)r :(int)c
556: {
557: float minCellVal, maxCellVal; /* min & max of values to be plotted */
558: float meanCellVal; /* mean of the plotted values */
559: float minSheetVal,maxSheetVal;/* RangeOfNumbers will plot in graph */
560: NXRect rectOfPlot; /* bounds of rect that will hold plot */
561: NXSetColor(backgroundColor);
562: NXRectFill(&bounds);
563: PSsetgray(NX_BLACK);
564: switch (borderType) {
565: case NX_LINE:
566: NXFrameRect(&bounds);
567: break;
568: case NX_BEZEL:
569: NXDrawWhiteBezel(&bounds,&bounds);
570: break;
571: case NX_GROOVE:
572: NXDrawGroove(&bounds, &bounds);
573: break;
574: default: break;
575: }
576: rectOfPlot= bounds;
577: rectOfPlot.origin.x += hMargin;
578: rectOfPlot.origin.y += vMargin;
579: rectOfPlot.size.width -=2*hMargin;
580: rectOfPlot.size.height -=2*vMargin;
581: if ( NX_WIDTH(&bounds) < (2*hMargin) ||
582: NX_HEIGHT(&bounds) < (2*vMargin) )
583: return self;
584:
585:
586: [window disableFlushWindow];
587: PSgsave();
588: PSsetlinewidth(0.0);
589: PStranslate (hMargin, vMargin);
590:
591: if (YES){
592: /* great we only have a single axis to work on */
593: [self calcMin:&minCellVal andMax:&maxCellVal andMean:&meanCellVal];
594:
595: if (COM_Flags.autoScale){
596: /* give us a plot if they are all the same (but not 0.0) */
597: if (minCellVal == maxCellVal){
598: if (minCellVal > 0.0) minCellVal = 0.0;
599: else if (maxCellVal < 0.0) maxCellVal = 0.0;
600: }
601:
602: /* figure out what min and the max should be on the sheet */
603: if (minCellVal == 0.0) minSheetVal= 0.0;
604: else minSheetVal= minCellVal - ((maxCellVal - minCellVal)*0.2);
605: /* we shouldn't have pushed this across the origin */
606: if (minSheetVal < 0.0 && minCellVal > 0.0) minSheetVal = 0.0;
607:
608: if (maxCellVal == 0.0) maxSheetVal = 0.0;
609: else maxSheetVal= maxCellVal +((maxCellVal - minCellVal)* 0.2);
610: /* make sure we didn't go across the origin */
611: if (maxSheetVal > 0.0 && maxCellVal < 0.0) maxSheetVal = 0.0;
612: } else {
613: minSheetVal = minSheetSet;
614: maxSheetVal = maxSheetSet;
615: }
616:
617: switch(COM_Flags.drawType){
618: case DRAW_H_BAR:
619: case DRAW_V_BAR:
620: [self renderBars:&rectOfPlot min:minSheetVal max:maxSheetVal];
621: break;
622: case DRAW_H_LINE:
623: break;
624: case DRAW_V_LINE:
625: [self renderVLines:&rectOfPlot min:minSheetVal max:maxSheetVal ];
626: break;
627: default:
628: break;
629: }
630:
631: PSgrestore();
632: if (COM_Flags.drawFrame){
633: PSsetgray(NX_BLACK);
634: NXFrameRect(&rectOfPlot);
635: }
636: }
637: [window reenableFlushWindow];
638: return self;
639: }
640:
641: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.