0

I am trying to draw a pie chart which will be made up of equal sized segments, each with a different colour. I am basing my code off this SO: Draw a circular segment progress in SWIFT

let circlePath = UIBezierPath(ovalInRect: CGRect(x: 200, y: 200, width: 150, height: 150))
var segments: [CAShapeLayer] = []
let segmentAngle: CGFloat = 1.0 / CGFloat(totalSegments)

for var i = 0; i < totalSegments; i++ {
    let circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.CGPath

    // start angle is number of segments * the segment angle
    circleLayer.strokeStart = segmentAngle * CGFloat(i)

    //create a gap to show segments
    let gapSize: CGFloat = 0.008
    circleLayer.strokeEnd = circleLayer.strokeStart + segmentAngle - gapSize

    circleLayer.lineWidth = 10
    circleLayer.strokeColor = UIColor(red:0,  green:0.004,  blue:0.549, alpha:1).CGColor
    circleLayer.fillColor = UIColor.redColor().CGColor

    // add the segment to the segments array and to the view
    segments.insert(circleLayer, atIndex: i)
    view.layer.addSublayer(segments[i])
}

But using this I wanted to make it so I can colour each segment based on the index value i in the for loop. Initially I just tested with odd or even, but found the fill part would fill the entire circle with the last color used.

I suspect that is because the fill colour fills the entire circle, even though the stroke has a finite start and end. How could I create this same effect but enable separate fill color for each segment?

Community
  • 1
  • 1
Dan
  • 1,343
  • 2
  • 9
  • 12
  • on a side note using a lineWidth with the arc method creates an extremely wonky looking circle! – Dan Feb 16 '16 at 07:23

1 Answers1

7

Here's a pie chart drawing function I had fun creating in the playground. Perhaps you can use it for your requirements:

 import Foundation
 import UIKit


 // Pie Chart Drawing function
 // --------------------------
 // - Takes an array of tuples with relative value and color for slices
 // - draws at center coordinates with a givn radius
 //
 func drawPieChart(slices:[(value:CGFloat, color:UIColor)], at center:CGPoint, radius:CGFloat)
 {  
     // the calue component of the tuples are summed up to get the denominator for surface ratios
     let totalValues:CGFloat = slices.reduce(0, combine: {$0 + $1.value})

     // starting at noon (-90deg)
     var angle:CGFloat = -CGFloat(M_PI)/2

     // draw each slice going counter clockwise
     for (value,color) in slices
     {
        let path = UIBezierPath()

        // slice's angle is proportional to circumference 2π 
        let sliceAngle = CGFloat(M_PI)*2*value/totalValues 
        // select fill color from tuple  
        color.setFill()

        // draw pie slice using arc from center and closing path back to center
        path.moveToPoint(center)
        path.addArcWithCenter( center, 
                       radius: radius, 
                   startAngle: angle, 
                     endAngle: angle - sliceAngle, 
                    clockwise: false
                             )
        path.moveToPoint(center)
        path.closePath()

        // draw slice in current context
        path.fill()

        // offset angle for next slice
        angle -= sliceAngle
     }
 }

 // Pie chart Data
 let slices:[(value:CGFloat, color:UIColor)] =
   [ (3, UIColor.greenColor()),
     (5, UIColor.redColor()),
     (8, UIColor.blueColor()),
     (13,UIColor.yellowColor())
   ]  

 // UIView
 let viewSize    = CGSize(width: 300, height: 300)
 let view:UIView = UIView(frame: CGRect(origin: CGPointZero, size: viewSize))
 view.backgroundColor = UIColor.whiteColor()

 // CoreGraphics drawing
 UIGraphicsBeginImageContextWithOptions(viewSize, false, 0)

 // draw pie chart in Image context
 drawPieChart(slices, at:CGPointMake(150,150), radius:100 )

 // set image to view layer (you could also use it elsewhere)
 view.layer.contents = UIGraphicsGetImageFromCurrentImageContext().CGImage
 UIGraphicsEndImageContext()

 // Playground (quick look or Timeline)
 let preview = view

Note: to get equal size segments, just provide the same value in all entries of the tuple array.

UPDATE

Here's a variant of the function that draws only the perimeter (rim) of the pie chart:

 func drawPieRim(_ slices:[(value:CGFloat, color:UIColor)], at center:CGPoint, 
                     radius:CGFloat, thickness:CGFloat=30)
 {  
     let totalValues:CGFloat = slices.reduce(0){$0 + $1.value}
     let gapAngle      =  CGFloat(Double.pi) * 0.01
     var angle:CGFloat =  -CGFloat(Double.pi)/2
     for (value,color) in slices
     {
        let path       = UIBezierPath()
        let sliceAngle = CGFloat(Double.pi)*2*value/totalValues
        path.lineWidth = thickness
        color.setStroke()
        path.addArc( withCenter:center, 
                       radius: radius, 
                   startAngle: angle + sliceAngle - gapAngle, 
                     endAngle: angle, 
                    clockwise: false)
        path.stroke()
        angle += sliceAngle 
     }
 }
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • Thank you Alain, I managed to use this method of drawing to achieve my goal. Whilst not an exact answer to my problem it does provide something that will be more valuable in the long run so I will mark as the answer. – Dan Feb 16 '16 at 07:22
  • I was looking to do something similar to the original question. This example is great, and super helpful, but I don't want the rays to the centre drawn—only the circle/arcs. Is there a simple tweak I can add to just create a multi-coloured circle? I realize this is a pretty old question. Thanks. – jbm Apr 19 '19 at 03:09