How to draw something like a pie chart

BlitzMax Forums/BlitzMax Beginners Area/How to draw something like a pie chart

Here's a nice challenge!

In my game I have temporary goodies. A goodie is visualised as a circle with a growing segment (like a two segment pie chart) so you see how much time (approx.) you have to catch the goodie.

How do you draw such a thing where there is no such thing as:
DrawSegment(centerX, centerY, startAngle, endAngle)

I prefer nice anti-aliased graphics (so no pixel draw routines, please)

This is my attempt. Any other suggestions?

SuperStrict

Graphics 800, 600, 0

Local pie1:TPieClock = TPieClock.Create(300, 300, 24, 3, 20000)'	centerX, centerY, radius, margin, duration
pie1.start()
Local pie2:TPieClock = TPieClock.Create(500, 300, 124, 10, 10000)'	centerX, centerY, radius, margin, duration
pie2.start()

While Not KeyHit(KEY_ESCAPE)
	Cls
	pie1.update()
	pie2.update()

	pie1.draw()
	pie2.draw()
	Flip
Wend
End

Type TPieClock
	Field pfX#
	Field pfY#
	Field pfR#
	Field pfMargin#
	Field pfAngle# = 0
	Field pfCircumference#	'I use this to calculate the correct number of lines to be drawn in the drawSegment() method

	'	background color
	Field piR1% = 70
	Field piG1% = 90
	Field piB1% = 90

	'	segment color
	Field piR2% =  30
	Field piG2% = 170
	Field piB2% =  80

	'	timer values
	Field piStart%
	Field pfDuration#		'in milliseconds
	
	Function Create:TPieClock(fx#, fy#, fr#, fm# = 0, fDur# = 1000)
		Local p:TPieClock = New TPieClock
		p.pfX = fx
		p.pfY = fy
		p.pfR = fr
		p.pfMargin = Min(fm, fr-1)
		p.pfCircumference = 2 * Pi * (fr-fm)
		p.pfDuration = fDur
		Return p
	End Function

	Method start()
		piStart = MilliSecs()
	End Method

	Method update()
		Local p# = eTime()
		If p>=1 Then 
			'ok, time's up
			p = 1
		End If
		pfAngle = p * 360
	End Method
	
	Method draw()
		SetColor(piR1, piG1, piB1)
		DrawOval(pfX - pfR, pfY - pfR, pfR * 2, pfR * 2)
		drawSegment() 
	End Method
	
	'	draws the segment
	Method drawSegment()
		SetColor(piR2, piG2, piB2)
		Local r# = pfR - pfMargin
		Local s# = pfAngle / ((pfAngle/360) * pfCircumference)
		'		s# = angle Step size; To be sure the While-Wend loop will draw exactly enough lines To cover the segment 
		
		Local a# = 0
		While a < pfAngle
			DrawLine(pfX-0.5, pfY-0.5, pfX-0.5 + Cos(a-90) * r, pfY-0.5 + Sin(a-90) * r, 0)
			a:+s
		Wend
		DrawLine(pfX-0.5, pfY-0.5, pfX-0.5 + Cos(pfAngle-90) * r, pfY-0.5 + Sin(pfAngle-90) * r, 0)
		'		I draw this line at the exact angle location 
	End Method

	Method eTime#()
		Return (MilliSecs() - piStart) / pfDuration
	End Method
	
End Type


If you need anti-aliased graphics, I'd say there are two easy options.

1. Use an animated image.

2. Create a small sliver (aliased, of course) of the circle, and then rotate and display the sliver a certain number of times according to the percentage of the circle that is filled.

Maybe this is something for you (but only OpenGL)


SuperStrict
SetGraphicsDriver GLMax2DDriver()
Graphics 800, 600, 0

Local pie1:TPieClock = TPieClock.Create(300, 300, 24, 3, 20000)'	centerX, centerY, radius, margin, duration
pie1.start()
Local pie2:TPieClock = TPieClock.Create(500, 300, 124, 10, 10000)'	centerX, centerY, radius, margin, duration
pie2.start()

While Not KeyHit(KEY_ESCAPE)
	Cls
	pie1.update()
	pie2.update()

	pie1.draw()
	pie2.draw()
	Flip
Wend
End

Type TPieClock
	Field pfX#
	Field pfY#
	Field pfR#
	Field pfMargin#
	Field pfAngle# = 0
	Field pfCircumference#	'I use this to calculate the correct number of lines to be drawn in the drawSegment() method

	'	background color
	Field piR1% = 70
	Field piG1% = 90
	Field piB1% = 90

	'	segment color
	Field piR2% =  30
	Field piG2% = 170
	Field piB2% =  80

	'	timer values
	Field piStart%
	Field pfDuration#		'in milliseconds
	
	Function Create:TPieClock(fx#, fy#, fr#, fm# = 0, fDur# = 1000)
		Local p:TPieClock = New TPieClock
		p.pfX = fx
		p.pfY = fy
		p.pfR = fr
		p.pfMargin = Min(fm, fr-1)
		p.pfCircumference = 2 * Pi * (fr-fm)
		p.pfDuration = fDur
		Return p
	End Function

	Method start()
		piStart = MilliSecs()
	End Method

	Method update()
		Local p# = eTime()
		If p>=1 Then 
			'ok, time's up
			p = 1
		End If
		pfAngle = p * 360
	End Method
	
	Method draw()
		SetColor(piR1, piG1, piB1)
		DrawOval(pfX - pfR , pfY - pfR , pfR * 2 , pfR * 2)
		SetColor(piR2, piG2, piB2)
		DrawPie(pfX - 0.5, pfY -0.5,pfR - pfMargin,0,pfAngle)
	End Method
	
	Method eTime#()
		Return (MilliSecs() - piStart) / pfDuration
	End Method
	
End Type

Rem
x , y 	- centerposition
r		- radius
angleS	 - Startangle
angleE	 - EndAngle
Stepper - Smoothness ( > rougher , faster | | < smoother , slower)
End Rem
	
Function DrawPie(X# , y# , r# , angleS# = 0,angleE# = 360,Stepper#=1.0)
	Local x1# , y1#
	Local multi:Int = 1
	Local a# = angleS

	glBegin GL_TRIANGLE_FAN
	glVertex2f x , y
	' a - 90 to have the 0 angle on north direction
	If AngleS > AngleE Then 
		multi = - 1
		Repeat	
			x1 = x + (Cos(a-90) * r)	
			y1 = y + (Sin(a-90) * r)
			glVertex2f x1 , y1
			a:+(Abs(Stepper)*multi)
		Until a < angleE	
	Else
		Repeat	
			x1 = x + (Cos(a-90) * r)	
			y1 = y + (Sin(a-90) * r)
			glVertex2f x1 , y1
			a:+(Abs(Stepper)*multi)
		Until a > angleE
	EndIf
	glEnd
End Function


here's a modified draw function that doesn't require the use of OpenGL.

Function DrawPie(X# , y# , r# , angleS# = 0,angleE# = 360,Stepper#=30)
	Local arr:Float[] = New Float[Stepper*6]
	Local st:Float = (angleE-angleS)/Stepper
	For Local i:Int= 0 To (Stepper-1)*6 Step 6
		arr[i+2] = x+r*Cos(i*st/6.0)
		arr[i+3] = y+r*Sin(i*st/6.0)
		arr[i+4] = x+r*Cos((i/6.0+1)*st)
		arr[i+5] = y+r*Sin((i/6.0+1)*st)
		arr[i] = x
		arr[i+1] = y
	Next
	DrawPoly(arr)
End Function


Thanks everyone!
That is all very inspiring!