[UPDATE 26. April 2016] Fixed some transparency issues if background needs to be some colour with transparency and inner radius is used.[/UPDATE]
I looked for a pie chart module and was not satisfied with the ones from the forum. So I did my own.
Always use RGBA() colours, otherwise it is seen as fully transparent colour!
Code: Select all
; PIE CHART MODULE
;
; PureBasic 5.24 (Windows, Linux, MacOS)
DeclareModule PieChart
Structure pieEntry
value.f
color.i
percentage.f
EndStructure
Structure pieChart
List values.pieEntry()
innerRadius.f
imageId.i
backgroundColor.i
EndStructure
Declare.i init(innerRadiusPercent.f = 0, backgroundColor.i = 0)
Declare addValue(*chart.pieChart,value.f, color.i)
Declare free(*chart.pieChart)
Declare.i getImage(*chart.pieChart, width.i, height.i)
EndDeclareModule
Module PieChart
#smoothFact = 4
; The radius should be between 0 an 95 percent. More does not make any sense.
Procedure.i init(innerRadiusPercent.f = 0, backgroundColor.i = 0)
Protected *chart.PieChart = AllocateMemory(SizeOf(pieChart))
InitializeStructure(*chart, pieChart)
*chart\innerRadius = innerRadiusPercent.f
*chart\backgroundColor = backgroundColor.i
ProcedureReturn *chart
EndProcedure
Procedure addValue(*chart.pieChart, value.f, color.i)
If color.i = -1
; PB is not capable to divide between transparent (constant points -1)
; and white with no transparency (RGBA(255,255,255,255) = -1
color.i = RGBA(254,255,255,255)
EndIf
If value.f > 0
AddElement(*chart\values())
*chart\values()\value = value.f
*chart\values()\color = color.i
*chart\values()\percentage = 0 ; init
EndIf
EndProcedure
Procedure free(*chart.PieChart)
FreeList(*chart\values())
If IsImage(*chart\imageId)
FreeImage(*chart\imageId)
EndIf
FreeMemory(*chart)
EndProcedure
Procedure.i getImage(*chart.pieChart, width.i, height.i)
Protected aStart.f
Protected x.f
Protected stp.f
Protected pX.i, pY.i
If ListSize(*chart\values()) < 1
Debug "Need values to draw a pie chart!"
ProcedureReturn 0
EndIf
Protected sum.f = 0
ForEach *chart\values()
sum.f = sum.f + *chart\values()\value
Next
; pre-calculate percentage values
Protected fact.f = 100 / sum.f
ForEach *chart\values()
*chart\values()\percentage = *chart\values()\value * fact.f
Next
Protected dWidth.i = width.i * #smoothFact
Protected dHeight.i = height.i * #smoothFact
Protected dRadius.f = dWidth / 2 - #smoothFact
Protected iRadius.f = (dRadius / 100 * *chart\innerRadius)
If dRadius > dHeight / 2: dRadius = dHeight / 2: EndIf ; use smallest value
If *chart\innerRadius > 95 Or *chart\innerRadius < 0
Debug "Inner radius is to big or outside of chart radius!"
iRadius = 0
EndIf
Protected img.i = CreateImage(#PB_Any,
dWidth.i,
dHeight.i,
32,
#PB_Image_Transparent)
StartDrawing(ImageOutput(img.i))
If *chart\backgroundColor <> 0
DrawingMode(#PB_2DDrawing_AlphaBlend)
Box(1, 1, dWidth.i, dHeight.i, *chart\backgroundColor)
EndIf
; draw initial circle
DrawingMode(#PB_2DDrawing_Outlined | #PB_2DDrawing_AlphaBlend)
Circle(dWidth / 2,
dHeight / 2,
dRadius,
RGBA(1,1,1,255))
DrawingMode(#PB_2DDrawing_AlphaBlend)
; draw all separators
aStart.f = 270
ForEach *chart\values()
a.f = 360 / 100 * *chart\values()\percentage
pX.i = Cos(Radian(aStart.f)) * (dRadius + #smoothFact)
pY.i = Sin(Radian(aStart.f)) * (dRadius + #smoothFact)
If pX.i = 0: pX.i = 1: EndIf
If pY.i = 0: pY.i = 1: EndIf
Line(dWidth / 2,
dHeight / 2,
pX.i,
pY.i,
*chart\values()\color)
pX.i = Cos(Radian(aStart.f + a.f)) * (dRadius + #smoothFact)
pY.i = Sin(Radian(aStart.f + a.f)) * (dRadius + #smoothFact)
If pX.i = 0: pX.i = 1: EndIf
If pY.i = 0: pY.i = 1: EndIf
Line(dWidth / 2,
dHeight / 2,
pX.i,
pY.i,
*chart\values()\color)
aStart.f = aStart.f + a.f
Next
; fill parts
aStart.f = 270
ForEach *chart\values()
a.f = 360 / 100 * *chart\values()\percentage
Protected decisionFact.f = ((dRadius + iRadius) / 2) / 10
If a.f >= decisionFact.f
pX.i = Cos(Radian(aStart.f + a.f - 3)) * ((dRadius + iRadius) * 0.58)
pY.i = Sin(Radian(aStart.f + a.f - 3)) * ((dRadius + iRadius) * 0.58)
If pX.i = 0: pX.i = 1: EndIf
If pY.i = 0: pY.i = 1: EndIf
FillArea(dWidth / 2 + pX.i, dHeight / 2 + pY.i, -1, *chart\values()\color)
; draw helping fill position (debug only)
; Circle(dWidth / 2 + pX.i, dHeight / 2 + pY.i, #smoothFact * 2, RGBA(255,0,0,155))
Else
x.f = 0
Repeat
pX.i = Cos(Radian(aStart.f + x.f)) * dRadius
pY.i = Sin(Radian(aStart.f + x.f)) * dRadius
If pX.i = 0: pX.i = 1: EndIf
If pY.i = 0: pY.i = 1: EndIf
Line(dWidth / 2,
dHeight / 2,
pX.i,
pY.i,
*chart\values()\color)
x.f = x.f + 0.005
Until x.f => a.f
EndIf
aStart.f = aStart.f + a.f
Next
; remove outer circle
DrawingMode(#PB_2DDrawing_Outlined | #PB_2DDrawing_AlphaChannel)
Circle(dWidth / 2,
dHeight / 2,
dRadius,
RGBA(0,0,0,0))
; respect inner radius (if given)
If iRadius > 0
If *chart\backgroundColor = 0
DrawingMode(#PB_2DDrawing_AlphaChannel)
Circle(dWidth / 2, dHeight / 2, iRadius, RGBA(0,0,0,0))
Else
DrawingMode(#PB_2DDrawing_AlphaChannel)
Circle(dWidth / 2, dHeight / 2, iRadius, RGBA(0,0,0,0))
DrawingMode(#PB_2DDrawing_AlphaBlend)
Circle(dWidth / 2, dHeight / 2, iRadius, *chart\backgroundColor)
EndIf
EndIf
StopDrawing()
ResizeImage(img.i, width.i, height.i, #PB_Image_Smooth)
*chart\imageId = img.i
ProcedureReturn img.i
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
Procedure test()
Protected y.i = 0
If OpenWindow(0, 0, 0, 220, 220, "Pie Chart Demo", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 200, 200)
Protected myChart.i = PieChart::init(50)
PieChart::addValue(myChart.i, 12, RGBA(155, 155, 255, 255))
PieChart::addValue(myChart.i, 7, RGBA(0,255,0,255))
PieChart::addValue(myChart.i, 0.5, RGBA(0,0,255,255))
PieChart::addValue(myChart.i, 16.8, RGBA(0,255,255,255))
Protected image.i = PieChart::getImage(myChart.i, 190, 190)
StartDrawing(CanvasOutput(0))
FillArea(1, 1, -1, RGBA(255,255,255,255))
; draw some background
DrawingMode(#PB_2DDrawing_AlphaBlend)
For y.i = 1 To 200 Step 10
Line(1, y.i, 200, 1, RGBA(0, 0, 255, 128))
Next
If image.i <> 0
; draw the chart
DrawImage(ImageID(image.i), 5, 5)
EndIf
StopDrawing()
PieChart::free(myChart.i) ; free data and image!
Repeat
Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
EndIf
EndProcedure
test() ; do some pie chart test
CompilerEndIf
The test function creates a line background to show functionality.