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.