31

So far I have a filled in circle and that's about it. I'm trying to make a pie chart that represents the number of satisfied and unsatisfied customers and present it. I'm extremely new to CG and was wondering someone can crank out enough code to give me an idea or to guide me.

Should I have the bottom circle represent the number of satisfied customers and then add another circle on top of it to show the unsatisfied customers? Am I approaching it in the right way?

Here is my code so far.

override func drawRect(rect: CGRect) {

    // Get current context
    let context = UIGraphicsGetCurrentContext()

    // Set color
    CGContextSetStrokeColorWithColor(context,UIColor(red: 0.2, green: 0.4, blue: 1, alpha: 1.0).CGColor)

    let rectangle = CGRectMake((frame.size.width / 3) - 50, frame.size.height / 2 + 40,220,220)
    CGContextAddEllipseInRect(context,rectangle)

    CGContextSetFillColorWithColor(context, UIColor(red: 0.2, green: 0.4, blue: 1, alpha: 1.0).CGColor)
    CGContextFillPath(context)
    CGContextStrokePath(context)

}

EDIT

Also, now i'm starting to see that I might need to cover my circle with an arc based off of the total of dissatisfied customer. How can I increase or decrease the size of the covering arc based on the number of people?

Any help would be tremendously appreciated!

Mihado
  • 1,487
  • 3
  • 17
  • 30
  • Possibly the following thread can help you out: [pie-chart-plot-in-swift](http://stackoverflow.com/questions/28768550/pie-chart-plot-in-swift). – dfrib Mar 02 '16 at 16:52
  • I investigated that answer quite closely, his code doesn't produce anything more than an empty circle. But thanks. – Mihado Mar 02 '16 at 17:06
  • 1
    @Ah I didn't look into the specifics, hence the "possibly" :) Unless you really want to implement it yourself, you could look at (or be inspired by) `PieChart(...)` of [iOS-Charts](https://github.com/danielgindi/ios-charts) (see [this tutorial](http://www.appcoda.com/ios-charts-api-tutorial/)) or e.g. [Swift-PieChart](https://github.com/zemirco/swift-piechart). – dfrib Mar 02 '16 at 17:16
  • I genuinely appreciate the effort though. I have looked at a variety of libraries that accomplish this, extensively. From what I've gathered, It shouldn't take more than 20 lines of code if I know what I'm doing. I'm just going to try and learn. :) – Mihado Mar 02 '16 at 17:18
  • Hopefully someone else can help you out; I'm not very well-versed in CoreGraphics myself, so tips about existing libraries is the best I can do here :) Good luck! – dfrib Mar 02 '16 at 17:20
  • The fact you took the time to try and find me something is beyond appreciated. – Mihado Mar 02 '16 at 17:21
  • Pie chart is simple, the difficult part are things like animations, touch detection, label placement, color selection for adjacent slices... – Sulthan Mar 04 '16 at 19:21
  • @Sulthan while you were telling me it's simple someone else answered the question instead of lecturing me. – Mihado Mar 05 '16 at 15:47
  • If you need to create pie Chart with smooth circle animation try this link https://stackoverflow.com/questions/44785085/swift-3-animate-color-fill-of-arc-added-to-uibezierpath/71073461#71073461 – Hussain Shabbir Feb 11 '22 at 05:16

1 Answers1

82

You'll want to use the CGContextAddArc() function (CGContext.addArc() in Swift 3). This will let you create multiple segments for your pie chart by drawing an arc for each segment of your pie chart.

Something like this should do the trick:

import UIKit

struct Segment {

    // the color of a given segment
    var color: UIColor

    // the value of a given segment – will be used to automatically calculate a ratio
    var value: CGFloat
}

class PieChartView: UIView {

    /// An array of structs representing the segments of the pie chart
    var segments = [Segment]() {
        didSet {
            setNeedsDisplay() // re-draw view when the values get set
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        isOpaque = false // when overriding drawRect, you must specify this to maintain transparency.
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func draw(_ rect: CGRect) {

        // get current context
        let ctx = UIGraphicsGetCurrentContext()

        // radius is the half the frame's width or height (whichever is smallest)
        let radius = min(frame.size.width, frame.size.height) * 0.5

        // center of the view
        let viewCenter = CGPoint(x: bounds.size.width * 0.5, y: bounds.size.height * 0.5)

        // enumerate the total value of the segments by using reduce to sum them
        let valueCount = segments.reduce(0, {$0 + $1.value})

        // the starting angle is -90 degrees (top of the circle, as the context is flipped). By default, 0 is the right hand side of the circle, with the positive angle being in an anti-clockwise direction (same as a unit circle in maths).
        var startAngle = -CGFloat.pi * 0.5

        for segment in segments { // loop through the values array

            // set fill color to the segment color
            ctx?.setFillColor(segment.color.cgColor)

            // update the end angle of the segment
            let endAngle = startAngle + 2 * .pi * (segment.value / valueCount)

            // move to the center of the pie chart
            ctx?.move(to: viewCenter)

            // add arc from the center for each segment (anticlockwise is specified for the arc, but as the view flips the context, it will produce a clockwise arc)
            ctx?.addArc(center: viewCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)

            // fill segment
            ctx?.fillPath()

            // update starting angle of the next segment to the ending angle of this segment
            startAngle = endAngle
        }
    }
}

You can input your pie chart data as an array of Segment structs, where each Segment represents the color and value of that segment.

The value can be any float, and will automatically be reduced down to a ratio to be used in the pie chart. So for example, if you want your pie chart to represent the number of unsatisfied vs. number of satisfied customers, you can just pass the values directly in.

Example of usage:

let pieChartView = PieChartView()
pieChartView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: 400)
pieChartView.segments = [
    Segment(color: .red, value: 57),
    Segment(color: .blue, value: 30),
    Segment(color: .green, value: 25),
    Segment(color: .yellow, value: 40)
]
view.addSubview(pieChartView)

Output:

enter image description here


Full project (with some extra functionality): https://github.com/hamishknight/Pie-Chart-View

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Awesome, I'll check this out as soon as I get back home from work and see if I have any additional questions. Thank you SO much! – Mihado Mar 04 '16 at 17:41
  • @Mihado happy to help :) arcs can sometimes be quite confusing in Core Graphics when working with UIViews, as the y-axis is flipped, so specifying a clockwise arc produces an anti-clockwise arc, and the angles are reversed etc. Happy to answer any questions you might have. – Hamish Mar 04 '16 at 19:43
  • This is PERFECT. I don't know how to thank you. I'll award you the bounty as soon as it lets me. I'd love to show you what I'm building if you'd be interested @originaluser2 – Mihado Mar 05 '16 at 03:30
  • Sure, always interested to see what people are working on! Not sure if you're interested, but I added some text functionality to the `PieChartView` class, so you can display text on each segment. It's on the github project if you're interested. As you didn't ask for it your question, I'll avoid cluttering the answer with it. – Hamish Mar 05 '16 at 14:41
  • I was actually just discovering how to do that, I just didn't want to be a nuisance and ask you for more work. I don't think a person can be any more awesome than you. You can reach me on twitter @ mihadoooo – Mihado Mar 05 '16 at 15:23
  • Awesome, Thanks a lot – Raj Joshi Dec 17 '16 at 05:47
  • @ Hamish ... i would like to rotate the pie chart with the finger touch ...can you suggest me some link....i am using pan gesture ...but not able to accurately rotate the pie chart according to finger movement – Pulkit Kumar Singh Oct 27 '17 at 08:08
  • 1
    @PulkitKumarSingh https://stackoverflow.com/questions/48193915/make-rotation-with-one-finger-in-swift-4/48196975?s=2|61.4109#48196975 – Leo Dabus Feb 03 '18 at 03:16
  • I wanted the more complex pie with text and percentage, I have cloned the repo from the link above where it runs flawlessly but not compatible with swift 4, any one figured out how to make swift 4 compatible? – Nasz Njoka Sr. Aug 19 '18 at 14:13
  • can we animate the pie chart when the value changes? – Sanket Ray Mar 20 '19 at 08:28