HomeForumSourceResearchGuide
Sign in to contribute to source. how it works
Component stats.chart.Category:barH by barry
expand copy to clipboardexpand
data Sample {
	dec values[]
	char category[]
	}

//marker length in pixels (the "ticks" on the axes)
const int MARKER_LENGTH = 3

const int MARKER_SIZE = 7

component provides Category:barH requires ChartCore, stats.StatCore stcore, io.Output out, data.IntUtil iu, data.DecUtil du, ui.Font {
	
	Sample samples[]
	
	Color seriesColor = new Color(100, 200, 200, 255)
	Color stdDevColor = new Color(0, 0, 0, 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
	
	bool showStdDev
	
	Category:Category()
		{
		super()
		
		axisFont = new Font("SourceSansPro.ttf", 12)
		
		setYMarkerInterval(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)
		
		yAxMax += 1.0
		
		if (samples == null)
			{
			highestX = hy
			lowestX = ly
			}
			else
			{
			if (hy > highestX) highestX = hy
			if (ly < lowestX) lowestX = 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 (highestX > xAxMax) xAxMax = highestX
		if (lowestX < xAxMin) xAxMin = lowestX
		
		setXMinMax(xAxMin, xAxMax)
		setYMinMax(yAxMin, yAxMax)
		
		if (redraw) postRepaint()
		}
	
	void Category:setYLabelInterval(dec ivr)
		{
		numberIntervalY = ivr
		super(ivr)
		}
	
	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)
		{
		showStdDev = on
		}
	
	void Category:setAxisFont(store Font f)
		{
		axisFont = f
		super(f)
		}
	
	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 prepAxisLimits()
		{
		dec highX
		dec lowX
		dec highY
		dec lowY
		
		//calculate what our exact min/max should be, and account for stdDev
		for (int i = 0; i < samples.arrayLength; i++)
			{
			dec mean = stcore.mean(samples[i].values)
			
			if (showStdDev)
				{
				dec stdDev = stcore.stdDev(samples[i].values)
				
				if (mean - stdDev < lowX) lowX = mean - stdDev
				if (mean + stdDev > highX) highX = mean + stdDev
				}
				else
				{
				if (mean < lowX) lowX = mean
				if (mean > highX) highX = mean
				}
			
			highY += 1.0
			}
		
		setXMinMax(lowX, highX)
		setYMinMax(lowY, highY)
		
		//out.println("x limits: $lowX : $highX")
		}
	
	int getMaxCatTextWidth()
		{
		int high = 0
		for (int i = 0; i < samples.arrayLength; i++)
			{
			int textWidth = axisFont.getTextWidth(samples[i].category)
			
			if (textWidth > high) high = textWidth
			}
		
		return high
		}
	
	void Category:paint(Canvas c)
		{
		prepAxisLimits()
		
		int maxCatWidth = getMaxCatTextWidth()
		setAxisLabelSpace(ChartCore.AXIS_Y, maxCatWidth)
		
		Point pos = getPosition()
		WH wh = getSize()
		
		c.pushSurface(new Rect(pos.x, pos.y, wh.width, wh.height), 0, 0, 255)
		
		preparePlotArea()
		
		//background
		c.rect(new Rect2D(0, 0, wh.width, wh.height, bgColor))
		
		//grid lines, if any
		drawGrid(c)
		
		//data
		
		int ySpacing = (getPlotPoint(0.0, 0.0)[1] - getPlotPoint(0.0, 1.0)[1]+1) - (barPad * 2)
		
		int barHeightPX = (barWidth / 100.0) * ySpacing
		
		dec thisY = 0.0
		for (int i = 0; i < samples.arrayLength; i++)
			{
			//this kind of graph plots the mean of the set
			dec thisX = stcore.mean(samples[i].values)
			
			//top-right (if thisX > x-axis intercept)
			int xy[] = getPlotPoint(thisX, thisY+1.0)
			
			//top-left (if thisX > x-axis intercept)
			int xyTR[] = getPlotPoint(0.0, thisY+1.0)
			
			//bottom-right (if thisX > x-axis intercept)
			int xyBL[] = getPlotPoint(thisX, thisY)
			
			int xHigh = xy[0]
			if (xyTR[0] > xy[0])
				xHigh = xyTR[0]
			int xLow = xyTR[0]
			if (xyTR[0] > xy[0])
				xLow = xy[0]
			
			int centreY = xy[1] + ((xyBL[1] - xy[1]) / 2)
			
			c.rect(new Rect2D(xLow, (centreY - (barHeightPX / 2)) - barPad, xHigh - xLow, barHeightPX, seriesColor))
			
			if (showStdDev)
				{
				dec stdDev = stcore.stdDev(samples[i].values)
				
				if (stdDev != 0.0)
					{
					int xyHigh[] = getPlotPoint(thisX+stdDev, thisY)
					int xyLow[] = getPlotPoint(thisX-stdDev, thisY)
					
					c.line(new Line2D(xyHigh[0], centreY, xyLow[0], centreY, stdDevColor))
					
					c.line(new Line2D(xyHigh[0], centreY-MARKER_SIZE/2, xyHigh[0], centreY+MARKER_SIZE/2, stdDevColor))
					c.line(new Line2D(xyLow[0], centreY-MARKER_SIZE/2, xyLow[0], centreY+MARKER_SIZE/2, stdDevColor))
					}
				}
			
			//category label
			int textWidth = axisFont.getTextWidth(samples[i].category)
			int textHeight = axisFont.getFontMetrics().height
			
			c.text(new Point2D(getPlotArea().x - MARKER_LENGTH - textWidth, centreY - (textHeight / 2), axisColor), axisFont, samples[i].category)
			
			thisY += 1.0
			}
		
		//axes and labels
		drawAxes(c)
		
		c.popSurface()
		}
	
	}
Revision history
To propose a new revision to this entity, use dana source put -uc your/new/version.dn -n stats.chart.Category:barH -m "reason for update" -u yourUsername
Version 2 (this version) by barry
Notes for this version: Updates to prepare for upcoming compiler strictness changes in function parameter qualifier equivalence
Version 1 by barry