PieChart Module (AntiAliased, Cross Platform, Thread Safe)

Share your advanced PureBasic knowledge/code with the community.
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

PieChart Module (AntiAliased, Cross Platform, Thread Safe)

Post by Kukulkan »

Hi,

[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
Check the test function at the end for usage.

The test function creates a line background to show functionality.
Last edited by Kukulkan on Tue Apr 26, 2016 1:54 pm, edited 4 times in total.
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5350
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: PieChart Module (Cross Platform, Thread Safe)

Post by Kwai chang caine »

Works well
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: PieChart Module (Cross Platform, Thread Safe)

Post by davido »

@Kukulkan,
Nice one!
Thank you for sharing.

You've made something that looks quite complex, very easy to use.:D
DE AA EB
User avatar
Andre
PureBasic Team
PureBasic Team
Posts: 2057
Joined: Fri Apr 25, 2003 6:14 pm
Location: Germany (Saxony, Deutscheinsiedel)
Contact:

Re: PieChart Module (AntiAliased, Cross Platform, Thread Saf

Post by Andre »

Very nice, thank you! :mrgreen:
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)
Post Reply