data Sample {
dec values[]
char category[]
}
//marker length in pixels (the "ticks" on the axes)
const int MARKER_LENGTH = 3
component provides Category:box requires ChartCore, stats.StatCore stcore, io.Output out, data.IntUtil iu, data.DecUtil du, ui.Font {
Sample samples[]
Color seriesColor = new Color(0, 0, 0, 255)
Color seriesFillColor = new Color(255, 255, 255, 255)
Color medianColor = new Color(200, 100, 100, 255)
Color axisColor = new Color(0, 0, 0, 255)
Color gridColor = new Color(220, 220, 220, 255)
Color bgColor = new Color(255, 255, 255, 255)
Font axisFont
Font labelFont
dec highestX
dec lowestX
dec highestY
dec lowestY
dec numberIntervalX = 1.0
dec markerIntervalX = 1.0
dec numberIntervalY
dec markerIntervalY
dec gridIntervalY
dec xAxMax
dec xAxMin
dec yAxMax
dec yAxMin
//bar width, in percentage of category area
dec barWidth = 50.0
int barPad = 0
//w/h in pixels
int width
int height
Category:Category()
{
super()
axisFont = new Font("SourceSansPro.ttf", 12)
setXMarkerInterval(1.0)
}
void Category:addSample(char cat[], dec yvalues[], opt bool redraw)
{
//update our highest and lowest X and Y points, to use in normalising coordinates
dec hy = stcore.max(yvalues)
dec ly = stcore.min(yvalues)
xAxMax += 1.0
if (samples == null)
{
highestY = hy
lowestY = ly
}
else
{
if (hy > highestY) highestY = hy
if (ly < lowestY) lowestY = ly
}
//add sample
samples = new Sample[](samples, new Sample(yvalues, cat))
// -- automated calculation of the highest and lowest points on the axis --
//update the axis endpoints, if the series values are now outside the bounds of whatever endpoints have been set
if (highestY > yAxMax) yAxMax = highestY
if (lowestY < yAxMin) yAxMin = lowestY
setXMinMax(xAxMin, xAxMax)
setYMinMax(yAxMin, yAxMax)
if (redraw) postRepaint()
}
void Category:setYMinMax(dec min, dec max)
{
//TODO: disallow values that can't contain all of the graph points...
yAxMin = min
yAxMax = max
super(min, max)
}
void Category:setSeriesColor(store Color c)
{
seriesColor = c
}
void Category:showErrorBars(bool on)
{
}
void Category:clampErrorBars(dec low, dec high)
{
}
void Category:setCatDisplayWidth(dec percent)
{
if (percent <= 0.0 || percent > 100.0)
throw new Exception("display width must be a percentage, between 1 and 100")
barWidth = percent
}
void Category:setCatDisplayPadding(int pixels)
{
barPad = pixels
}
void Category:paint(Canvas c)
{
Point pos = getPosition()
WH wh = getSize()
c.pushSurface(new Rect(pos.x, pos.y, wh.width, wh.height), 0, 0, 255)
//TODO: calculate the 5num of every sample, and check if our chart is large enough to show the outlier whiskers
// (by using setYMinMax())
preparePlotArea()
//background
c.rect(new Rect2D(0, 0, wh.width, wh.height, bgColor))
//grid lines, if any
drawGrid(c)
//data
int xSpacing = getPlotPoint(1.0, 0.0)[0] - getPlotPoint(0.0, 0.0)[0]
int barWidthPX = (barWidth / 100.0) * xSpacing
dec thisX = 0.0
for (int i = 0; i < samples.arrayLength; i++)
{
//get 5-number-summary, generate box and whiskers + median, and draw...
dec fn[] = stcore.fiveNum(samples[i].values)
//this kind of graph plots the mean of the set
dec thisY = stcore.mean(samples[i].values)
//top-left
int xy[] = getPlotPoint(thisX, fn[3])
//top-right
int xyTR[] = getPlotPoint(thisX+1.0, fn[3])
//bottom-left
int xyBL[] = getPlotPoint(thisX, fn[1])
//median
int xyMedian[] = getPlotPoint(thisX, fn[2])
//low whisker
int xyLowWhisker[] = getPlotPoint(thisX, fn[0])
//high whisker
int xyHighWhisker[] = getPlotPoint(thisX, fn[4])
//x-axis location
int xyXA[] = getPlotPoint(thisX, 0.0)
int centreX = xy[0] + ((xyTR[0] - xy[0]) / 2)
//box spanning 1st and 3rd quartiles (interquartile range)
c.rectOutline(new Rect2D(centreX - (barWidthPX / 2), xy[1], barWidthPX, xyBL[1] - xy[1], seriesColor))
if (xyBL[1] - xy[1] > 2) c.rect(new Rect2D(centreX - (barWidthPX / 2)+1, xy[1]+1, barWidthPX-2, (xyBL[1] - xy[1])-2, seriesFillColor))
//median line
c.line(new Line2D(centreX - (barWidthPX / 2), xyMedian[1], (centreX - (barWidthPX / 2) + barWidthPX)-1, xyMedian[1], medianColor))
//low whisker
c.line(new Line2D(centreX, xyBL[1], centreX, xyLowWhisker[1], seriesColor))
c.line(new Line2D(centreX - (barWidthPX / 2), xyLowWhisker[1], (centreX - (barWidthPX / 2) + barWidthPX)-1, xyLowWhisker[1], seriesColor))
//high whisker
c.line(new Line2D(centreX, xy[1], centreX, xyHighWhisker[1], seriesColor))
c.line(new Line2D(centreX - (barWidthPX / 2), xyHighWhisker[1], (centreX - (barWidthPX / 2) + barWidthPX)-1, xyHighWhisker[1], seriesColor))
//category label
int textWidth = axisFont.getTextWidth(samples[i].category)
c.text(new Point2D(centreX - (textWidth/2), xyXA[1] + MARKER_LENGTH, axisColor), axisFont, samples[i].category)
thisX += 1.0
}
//axes and labels
drawAxes(c)
c.popSurface()
}
}