It is currently Tue Jan 19, 2021 6:10 am

All times are UTC + 1 hour




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: PieChart Module (AntiAliased, Cross Platform, Thread Safe)
PostPosted: Mon Apr 25, 2016 3:08 pm 
Offline
Addict
Addict
User avatar

Joined: Mon Jun 06, 2005 2:35 pm
Posts: 1259
Location: germany
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:
; 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.

Top
 Profile  
Reply with quote  
 Post subject: Re: PieChart Module (Cross Platform, Thread Safe)
PostPosted: Mon Apr 25, 2016 3:28 pm 
Offline
Addict
Addict
User avatar

Joined: Sun Nov 05, 2006 11:42 pm
Posts: 4836
Location: Lyon - France
Works well
Thanks for sharing 8)

_________________
ImageThe happiness is a road...
Not a destination


Top
 Profile  
Reply with quote  
 Post subject: Re: PieChart Module (Cross Platform, Thread Safe)
PostPosted: Mon Apr 25, 2016 4:39 pm 
Offline
Addict
Addict

Joined: Fri Nov 09, 2012 11:04 pm
Posts: 1807
Location: Uttoxeter, UK
@Kukulkan,
Nice one!
Thank you for sharing.

You've made something that looks quite complex, very easy to use.:D

_________________
DE AA EB


Top
 Profile  
Reply with quote  
 Post subject: Re: PieChart Module (AntiAliased, Cross Platform, Thread Saf
PostPosted: Sun Jul 16, 2017 11:01 pm 
Offline
PureBasic Team
PureBasic Team
User avatar

Joined: Fri Apr 25, 2003 6:14 pm
Posts: 1872
Location: Germany (Saxony, Deutscheinsiedel)
Very nice, thank you! :mrgreen:

_________________
Bye,
...André
(PureBasicTeam::Docs & Support - PureArea.net | Order:: PureBasic | PureVisionXP)


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: SkyManager and 19 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye