This is HistogramView.m in view mode; [Download] [Up]
// By Judy D. Halchin, Educational Computing Services, Allegheny College. // You may freely copy, distribute and reuse this code. // Allegheny College and the author disclaim any warranty of any kind, // expressed or implied, as to its fitness for any particular use. // This work was partially supported by a Teacher Preparation grant from the // National Science Foundation. #import "HistogramView.h" #import "drawHistogram.h" #import <dpsclient/wraps.h> #import <appkit/graphics.h> #import <stdlib.h> #import <string.h> #import <math.h> #define LEFTMARGIN 60 #define RIGHTMARGIN 15 #define TOPMARGIN 35 #define BOTTOMMARGIN 60 #define SPACER 15 @implementation HistogramView float _mantissaOf(float floatValue); int _orderMagnitude(float floatValue); // global variables not open to the public, but used to pass info from the draw // methods to drawSelf::. We want all drawing to be done from drawSelf::, so // that we can print easily int graphType, numberOfBars, numTicks, labelType, numberDecPlaces; float *frequencyHeights, *midpoints, defaultBarWidth, extraSpace, ticks[5]; float actualSpacer=0, *boundaryPoints, *frequencyValues; char **labels, tickStrings[5][30]; - initFrame:(const NXRect *)frameRect { [super initFrame:frameRect]; graphType = NONE; midpoints = (float *)malloc(sizeof(float)); boundaryPoints = (float *)malloc(sizeof(float)); frequencyValues = (float *)malloc(sizeof(float)); frequencyHeights = (float *)malloc(sizeof(float)); labels = (char **)malloc(sizeof(char*)); numberOfBars = 0; barWidth = -1; barStyle = FILLED; numberDecPlaces = 0; graphTitle = (char *)malloc(1); horizAxisTitle = (char *)malloc(1); vertAxisTitle = (char *)malloc(1); strcpy(graphTitle, ""); strcpy(horizAxisTitle, ""); strcpy(vertAxisTitle, ""); return self; } - drawHistogramNumBars:(int)numBars boundaries:(float *)boundaries accuracy:(int)numDecPlaces frequencies:(float *)frequencies { int bar; //reallocate arrays and copy parameters to global variables graphType = HISTOGRAM; numberDecPlaces = numDecPlaces; for (bar = 0; bar < numberOfBars; bar++) free(labels[bar]); numberOfBars = numBars; frequencyHeights = (float *)realloc(frequencyHeights, numberOfBars * sizeof(float)); midpoints = (float *)realloc(midpoints, numberOfBars * sizeof(float)); boundaryPoints = (float *)realloc(boundaryPoints, (numberOfBars+1) * sizeof(float)); for (bar = 0; bar < numberOfBars+1; bar++) boundaryPoints[bar] = boundaries[bar]; frequencyValues = (float *)realloc(frequencyValues, numberOfBars * sizeof(float)); for (bar = 0; bar < numberOfBars+1; bar++) frequencyValues[bar] = frequencies[bar]; labels = (char **)realloc(labels, numberOfBars*sizeof(char *)); // do calculations and draw [self calculateAndDrawHistogram]; return self; } - drawHistogramNumBars:(int)numBars frequencies:(float *)frequencies labels:(char **)barLabels { int bar; //reallocate arrays and copy parameters to global variables graphType = SIMPLEHISTOGRAM; for (bar = 0; bar < numberOfBars; bar++) free(labels[bar]); numberOfBars = numBars; labels = (char **)realloc(labels, numberOfBars * sizeof(char *)); frequencyValues = (float *)realloc(frequencyValues, numberOfBars * sizeof(float)); for (bar = 0; bar < numberOfBars+1; bar++) frequencyValues[bar] = frequencies[bar]; frequencyHeights = (float *)realloc(frequencyHeights, numberOfBars * sizeof(float)); midpoints = (float *)realloc(midpoints, numberOfBars * sizeof(float)); for (bar = 0; bar < numberOfBars; bar++) { labels[bar] = malloc(strlen(barLabels[bar]) + 1); strcpy(labels[bar], barLabels[bar]); } // do calculations and draw [self calculateAndDrawSimpleHistogram]; return self; } - drawBarChartNumBars:(int)numBars frequencies:(float *)frequencies labels:(char **)barLabels { int bar; //reallocate arrays and copy parameters to global variables graphType = BARCHART; for (bar = 0; bar < numberOfBars; bar++) free(labels[bar]); numberOfBars = numBars; frequencyValues = (float *)realloc(frequencyValues, numberOfBars * sizeof(float)); for (bar = 0; bar < numberOfBars+1; bar++) frequencyValues[bar] = frequencies[bar]; labels = (char **)realloc(labels, numberOfBars * sizeof(char *)); for (bar = 0; bar < numberOfBars; bar++) { labels[bar] = malloc(strlen(barLabels[bar]) + 1); strcpy(labels[bar], barLabels[bar]); } frequencyHeights = (float *)realloc(frequencyHeights, numberOfBars * sizeof(float)); midpoints = (float *)realloc(midpoints, numberOfBars * sizeof(float)); // do calculations and draw [self calculateAndDrawBarChart]; return self; } - calculateAndDrawHistogram { float maxFreq, vertUnit, horizUnit, axisWidth, axisHeight, maxTick; char formatString[30]; int bar; //convert basic measurements to our coordinates axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN; defaultBarWidth = (axisWidth - 2 * SPACER) / numberOfBars; horizUnit = (axisWidth - 2 * SPACER) / (boundaryPoints[numberOfBars] - boundaryPoints[0]); maxFreq = 0; for (bar = 0; bar < numberOfBars; bar++) if (frequencyValues[bar] > maxFreq) maxFreq = frequencyValues[bar]; //calculate midpoints to use for bar labels sprintf(formatString, "%%.%df", numberDecPlaces + 1); for (bar = 0; bar < numberOfBars; bar++) { labels[bar] = malloc(30); midpoints[bar] = (boundaryPoints[bar] + boundaryPoints[bar+1])/2; sprintf(labels[bar], formatString, midpoints[bar]); midpoints[bar] = (midpoints[bar] - boundaryPoints[0]) * horizUnit + SPACER + LEFTMARGIN; } //format labels and decide how to display them [self chooseLabelFormat]; //calculate the vertical tick and labels axisHeight = bounds.size.height - BOTTOMMARGIN - TOPMARGIN - extraSpace; [self calculateTicksForMaxValue:maxFreq maxTick:&maxTick]; //calulate the drawing coordinates for bar heights and vert ticks vertUnit = (axisHeight - SPACER) / maxTick; for (bar = 0; bar < numberOfBars; bar++) frequencyHeights[bar] = vertUnit * frequencyValues[bar]; //frequencyHeights are measured from horizontal axis [self display]; return self; } - calculateAndDrawSimpleHistogram { float maxFreq, vertUnit, horizUnit, axisWidth, axisHeight, maxTick; int bar; //convert basic measurements to our coordinates axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN; defaultBarWidth = (axisWidth - 2 * SPACER) / numberOfBars; horizUnit = (axisWidth - 2 * SPACER) / numberOfBars; maxFreq = 0; for (bar = 0; bar < numberOfBars; bar++) if (frequencyValues[bar] > maxFreq) maxFreq = frequencyValues[bar]; //calculate positions for the bar labels for (bar = 0; bar < numberOfBars; bar++) midpoints[bar] = (bar + 0.5) * horizUnit + SPACER + LEFTMARGIN; //format labels and decide how to display them [self chooseLabelFormat]; //calculate the vertical ticks and their labels axisHeight = bounds.size.height - BOTTOMMARGIN - TOPMARGIN - extraSpace; [self calculateTicksForMaxValue:maxFreq maxTick:&maxTick]; //calulate the drawing coordinates for bar heights and vert ticks vertUnit = (axisHeight - SPACER) / maxTick; for (bar = 0; bar < numberOfBars; bar++) frequencyHeights[bar] = vertUnit * frequencyValues[bar]; //frequencyHeights are measured from horizontal axis [self display]; return self; } - calculateAndDrawBarChart { float maxFreq, vertUnit, axisWidth, axisHeight, maxTick; float actualBarWidth; int bar; //convert basic measurements to our coordinates axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN; defaultBarWidth = (axisWidth - SPACER) / numberOfBars - SPACER; maxFreq = 0; for (bar = 0; bar < numberOfBars; bar++) if (frequencyValues[bar] > maxFreq) maxFreq = frequencyValues[bar]; //calculate positions for the bar labels if (barWidth < -0.1) actualBarWidth = defaultBarWidth; else actualBarWidth = barWidth; actualSpacer = (axisWidth - actualBarWidth * numberOfBars) / (numberOfBars + 1); for (bar = 0; bar < numberOfBars; bar++) midpoints[bar] = actualSpacer + LEFTMARGIN + bar * (actualBarWidth+actualSpacer) + 0.5 * actualBarWidth; //format labels and decide how to display them [self chooseLabelFormat]; //calculate vertical ticks and labels [self calculateTicksForMaxValue:maxFreq maxTick:&maxTick]; //calulate the drawing coordinates for bar heights axisHeight = bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN; vertUnit = (axisHeight - SPACER) / maxTick; for (bar = 0; bar < numberOfBars; bar++) frequencyHeights[bar] = vertUnit * frequencyValues[bar]; [self display]; return self; } - setHorizAxisTitle:(char *)horizTitle { horizAxisTitle = realloc(horizAxisTitle, strlen(horizTitle) + 1); strcpy(horizAxisTitle, horizTitle); return self; } - setVertAxisTitle:(char *)vertTitle { vertAxisTitle = realloc(vertAxisTitle, strlen(vertTitle) + 1); strcpy(vertAxisTitle, vertTitle); return self; } - setGraphTitle:(char *)title { graphTitle = realloc(graphTitle, strlen(title) + 1); strcpy(graphTitle, title); return self; } - setBarWidth:(float)width { barWidth = width; return self; } - setBarStyle:(int)style { barStyle = style; return self; } - drawSelf:(const NXRect *)rects :(int)rectCount { float titlePosition=0, axisWidth, axisHeight, actualBarWidth; //draw the background and axes axisWidth = bounds.size.width - LEFTMARGIN - RIGHTMARGIN; axisHeight = bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN; drawAxes(bounds.size.width, bounds.size.height, LEFTMARGIN, BOTTOMMARGIN + extraSpace, axisWidth, axisHeight); //draw the graph switch (graphType) { case HISTOGRAM: case SIMPLEHISTOGRAM: drawHistogram(LEFTMARGIN, BOTTOMMARGIN + extraSpace, SPACER, axisWidth, numberOfBars, defaultBarWidth, frequencyHeights); break; case BARCHART: if (barWidth > -0.1) actualBarWidth = barWidth; else actualBarWidth = defaultBarWidth; if (barStyle == FILLED) drawFilledBarChart(LEFTMARGIN, BOTTOMMARGIN + extraSpace, actualSpacer, axisWidth, numberOfBars, actualBarWidth, frequencyHeights); else drawSolidBarChart(LEFTMARGIN, BOTTOMMARGIN + extraSpace, actualSpacer, axisWidth, numberOfBars, actualBarWidth, frequencyHeights); [self drawLabels]; break; case NONE: PSsetgray(1); NXRectFill(&bounds); } //draw bar labels, vertical ticks, titles if (graphType != NONE) { [self drawLabels]; drawVertTicks(numTicks, ticks, LEFTMARGIN-10, LEFTMARGIN - 2, tickStrings[0], tickStrings[1], tickStrings[2], tickStrings[3], tickStrings[4]); titlePosition = LEFTMARGIN + (bounds.size.width - RIGHTMARGIN - LEFTMARGIN) / 2; drawHorizTitle(15, titlePosition, 14, horizAxisTitle); titlePosition = BOTTOMMARGIN + extraSpace + (bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN) / 2; drawVertTitle(22, titlePosition, vertAxisTitle); drawHorizTitle(bounds.size.height - 25, bounds.size.width /2, 24, graphTitle); } return self; } - calculateTicksForMaxValue:(float)maximum maxTick:(float *)maxTick { float maxMantissa, axisHeight, vertUnit, tickSpacing=0, tick; int i, formatDigits=0; char formatString[20]; //calculate vertical tick spacing maxMantissa = _mantissaOf(maximum); if (maxMantissa == 1) { formatDigits = _orderMagnitude(maximum)-1; tickSpacing = 2 * pow(10, formatDigits); } else if ((1 < maxMantissa) && (maxMantissa <= 2.5)) { formatDigits = _orderMagnitude(maximum)-1; tickSpacing = 5 * pow(10, formatDigits); } else if ((2.5 < maxMantissa) && (maxMantissa <= 5)) { formatDigits = _orderMagnitude(maximum); tickSpacing = pow(10, formatDigits); } else if (5 < maxMantissa) { formatDigits = _orderMagnitude(maximum); tickSpacing = 2 * pow(10, formatDigits); } if (tickSpacing == 0) { formatDigits = 0; tickSpacing = 1; } //calculate the tick marks and format the labels numTicks = 0; if (formatDigits < 0) sprintf(formatString, "%c.%df", '%', -formatDigits); else strcpy(formatString, "%.0f"); for (tick = tickSpacing; tick < maximum + tickSpacing; tick = tick + tickSpacing) { sprintf(tickStrings[numTicks], formatString, tick); numTicks++; } *maxTick = tick - tickSpacing; for (i = numTicks; i < 5; i++) strcpy(tickStrings[i], ""); axisHeight = bounds.size.height - BOTTOMMARGIN - extraSpace - TOPMARGIN; vertUnit = (axisHeight - SPACER) / *maxTick; for (i = 0, tick = tickSpacing; i < numTicks; i++, tick = tick + tickSpacing) ticks[i] = tick * vertUnit + BOTTOMMARGIN + extraSpace; //tick heights are absolute--measured from bottom of view return self; } - chooseLabelFormat { //Draws the bar labels in one of three different ways, depending on how //much space there is. If possible, just puts the label for each bar //under the bar, horizontally. If there's not enough space for that, it //will try to stagger the labels--every other one is a little lower. If //there still isn't enought space, it will turn the labels sideways. //Assumes that the midpoints are evenly spaced. int bar; float maxWidth, width; //find the width of the widest label maxWidth = 0; for (bar = 0; bar < numberOfBars; bar++) { findLabelWidth(labels[bar], &width); if (width > maxWidth) maxWidth = width; } //decide which type of labels to use if (numberOfBars == 1) labelType = PLAIN; else if (maxWidth + 5 < midpoints[1] - midpoints[0]) labelType = PLAIN; else if (numberOfBars == 2) labelType = STAGGERED; else if (maxWidth + 5 < midpoints[2] - midpoints[0]) labelType = STAGGERED; else labelType = SIDEWAYS; //set amount of extra space needed at bottom to accomodate the labels switch (labelType) { case PLAIN: extraSpace = 0; break; case STAGGERED: extraSpace = 8; break; case SIDEWAYS: extraSpace = maxWidth; } return self; } - drawLabels { int bar; switch (labelType) { case PLAIN: for (bar = 0; bar < numberOfBars; bar++) drawHorizLabel(midpoints[bar], BOTTOMMARGIN + extraSpace - 12, labels[bar]); break; case STAGGERED: for (bar = 0; bar < numberOfBars; bar = bar + 2) { drawHorizLabel(midpoints[bar], BOTTOMMARGIN + extraSpace - 12, labels[bar]); if (bar+1 < numberOfBars) drawHorizLabel(midpoints[bar+1], BOTTOMMARGIN + extraSpace - 24, labels[bar+1]); } break; case SIDEWAYS:; for (bar = 0; bar < numberOfBars; bar++) drawVertLabel(midpoints[bar], BOTTOMMARGIN + extraSpace - 4, labels[bar]); } return self; } - sizeTo:(NXCoord)width :(NXCoord)height { [super sizeTo:width :height]; switch (graphType) { case HISTOGRAM:[self calculateAndDrawHistogram]; break; case SIMPLEHISTOGRAM:[self calculateAndDrawSimpleHistogram]; break; case BARCHART:[self calculateAndDrawBarChart]; break; } return self; } - setFrame:(const NXRect *)frameRect { [super setFrame:frameRect]; switch (graphType) { case HISTOGRAM:[self calculateAndDrawHistogram]; break; case SIMPLEHISTOGRAM:[self calculateAndDrawSimpleHistogram]; break; case BARCHART:[self calculateAndDrawBarChart]; break; } return self; } float _mantissaOf(float floatValue) { return floatValue / pow(10, _orderMagnitude(floatValue)); } int _orderMagnitude(float floatValue) { char stringValue[30], *ePointer; sprintf(stringValue, "%e", floatValue); ePointer = strchr(stringValue, 'e'); return atoi(ePointer + 1); } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.