Skip to content

Instantly share code, notes, and snippets.

@rsaenzi
Created October 3, 2017 15:52
Show Gist options
  • Save rsaenzi/e90de5e9ae52374926f6c5ecae5b774e to your computer and use it in GitHub Desktop.
Save rsaenzi/e90de5e9ae52374926f6c5ecae5b774e to your computer and use it in GitHub Desktop.
Custom view to show a simple pie chart
//
// PieGraph.swift
//
// Created by Rigoberto Sáenz Imbacuán (https://www.linkedin.com/in/rsaenzi/)
// Copyright © 2017. All rights reserved.
//
import UIKit
@IBDesignable
class PieGraph: UIView {
private let pieColor = UIColor.red
private let lineColor = UIColor.white
private let sublineColor = UIColor.white.withAlphaComponent(0.5)
private var pie5s: PieSlice!
private var pie1m: PieSlice!
private var pie5m: PieSlice!
private var pie20m: PieSlice!
private var line5sBest: PieArc!
private var line5sVery: PieArc!
private var line5sGood: PieArc!
private var line5sNeeds: PieArc!
private var line1mBest: PieArc!
private var line1mVery: PieArc!
private var line1mGood: PieArc!
private var line1mNeeds: PieArc!
private var line5mBest: PieArc!
private var line5mVery: PieArc!
private var line5mGood: PieArc!
private var line5mNeeds: PieArc!
override init(frame: CGRect) { // From Code
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) { // From IB
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
pie5s = PieSlice(PieValues(start: 180, arc: 60, sliceRadius: 0.85, color: pieColor))
pie1m = PieSlice(PieValues(start: 240, arc: 60, sliceRadius: 0.62, color: pieColor))
pie5m = PieSlice(PieValues(start: 300, arc: 60, sliceRadius: 0.45, color: pieColor))
pie20m = PieSlice(PieValues(start: 0, arc: 180, sliceRadius: 0.6, color: pieColor))
line5sBest = PieArc(PieValues(start: 180, arc: 60, sliceRadius: 1.0, color: lineColor), lineWidth: 1)
line5sVery = PieArc(PieValues(start: 180, arc: 60, sliceRadius: 0.75, color: sublineColor), lineWidth: 1)
line5sGood = PieArc(PieValues(start: 180, arc: 60, sliceRadius: 0.5, color: sublineColor), lineWidth: 1)
line5sNeeds = PieArc(PieValues(start: 180, arc: 60, sliceRadius: 0.25, color: sublineColor), lineWidth: 1)
line1mBest = PieArc(PieValues(start: 240, arc: 60, sliceRadius: 1.0, color: lineColor), lineWidth: 1)
line1mVery = PieArc(PieValues(start: 240, arc: 60, sliceRadius: 0.75, color: sublineColor), lineWidth: 1)
line1mGood = PieArc(PieValues(start: 240, arc: 60, sliceRadius: 0.5, color: sublineColor), lineWidth: 1)
line1mNeeds = PieArc(PieValues(start: 240, arc: 60, sliceRadius: 0.25, color: sublineColor), lineWidth: 1)
line5mBest = PieArc(PieValues(start: 300, arc: 60, sliceRadius: 1.0, color: lineColor), lineWidth: 1)
line5mVery = PieArc(PieValues(start: 300, arc: 60, sliceRadius: 0.75, color: sublineColor), lineWidth: 1)
line5mGood = PieArc(PieValues(start: 300, arc: 60, sliceRadius: 0.5, color: sublineColor), lineWidth: 1)
line5mNeeds = PieArc(PieValues(start: 300, arc: 60, sliceRadius: 0.25, color: sublineColor), lineWidth: 1)
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
super.draw(rect)
// Order of drawing is really important
pie5s.draw(rect)
pie1m.draw(rect)
pie5m.draw(rect)
pie20m.draw(rect)
line5sBest.draw(rect)
line5sVery.draw(rect)
line5sGood.draw(rect)
line5sNeeds.draw(rect)
line1mBest.draw(rect)
line1mVery.draw(rect)
line1mGood.draw(rect)
line1mNeeds.draw(rect)
line5mBest.draw(rect)
line5mVery.draw(rect)
line5mGood.draw(rect)
line5mNeeds.draw(rect)
}
}
class PieValues {
let start: CGFloat
let arc: CGFloat
let sliceRadius: CGFloat
let color: UIColor
init?(start: Double, arc: Double, sliceRadius: Double, color: UIColor) {
guard start >= 0.0, start <= 360.0 else {
return nil
}
guard arc >= 0.0, arc <= 360.0 else {
return nil
}
// This value must be between 0 and 1
guard sliceRadius >= 0.0, sliceRadius <= 1.0 else {
return nil
}
self.start = PieValues.getRadians(from: start)
self.arc = PieValues.getRadians(from: arc)
self.sliceRadius = CGFloat(sliceRadius)
self.color = color
}
private static func getRadians(from degrees: Double) -> CGFloat {
let radians: Double = degrees * .pi / 180.0
return CGFloat(radians)
}
}
class PieSlice {
private let values: PieValues?
init(_ values: PieValues?) {
self.values = values
}
func draw(_ rect: CGRect) {
// Values object must be valid...
guard let values = values else {
return
}
// This view can be resized in any way, so we take the min length as diameter, to avoid graph being cut off
let graphSize = rect
let graphCenter = CGPoint(x: graphSize.width / 2.0, y: graphSize.height / 2.0)
let graphDiameter: CGFloat = min(graphSize.width, graphSize.height) * values.sliceRadius
let graphRadius: CGFloat = graphDiameter / 2.0
// Lets calculate the values to create the pie...
let pieRadius: CGFloat = graphRadius / 2.0
let pieStart = values.start
let pieEnd = values.start + values.arc
// Draws the pie
let pie = UIBezierPath(arcCenter: graphCenter, radius: pieRadius, startAngle: pieStart, endAngle: pieEnd, clockwise: true)
pie.lineWidth = graphRadius
values.color.setStroke()
pie.stroke()
}
}
class PieArc {
private let values: PieValues?
private let lineWidth: Int
init(_ values: PieValues?, lineWidth: Int) {
self.values = values
self.lineWidth = lineWidth
}
func draw(_ rect: CGRect) {
// Values object must be valid...
guard let values = values else {
return
}
// This view can be resized in any way, so we take the min length as diameter, to avoid graph being cut off
let graphSize = rect
let graphCenter = CGPoint(x: graphSize.width / 2.0, y: graphSize.height / 2.0)
let graphDiameter: CGFloat = min(graphSize.width, graphSize.height) * values.sliceRadius
let graphRadius: CGFloat = graphDiameter / 2.0
// Lets calculate the values to create the pie...
let pieRadius: CGFloat = graphRadius - CGFloat(lineWidth / 2)
let pieStart = values.start
let pieEnd = values.start + values.arc
// Draws the farthest arc
let pie = UIBezierPath(arcCenter: graphCenter, radius: pieRadius, startAngle: pieStart, endAngle: pieEnd, clockwise: true)
pie.lineWidth = CGFloat(lineWidth)
values.color.setStroke()
pie.stroke()
// We use the current point of the pie bezier path, to draw one of the radius lines
let lineEnd = UIBezierPath()
lineEnd.lineWidth = CGFloat(lineWidth)
lineEnd.move(to: graphCenter)
lineEnd.addLine(to: pie.currentPoint)
lineEnd.stroke()
// Now we create a path, similar to the one used to draw the pie, to position the draw point correctly
let pieStartPoint = UIBezierPath(arcCenter: graphCenter, radius: pieRadius, startAngle: pieStart, endAngle: pieStart, clockwise: true)
// Then we use that point to draw the second radius line
let lineStart = UIBezierPath()
lineStart.lineWidth = CGFloat(lineWidth)
lineStart.move(to: graphCenter)
lineStart.addLine(to: pieStartPoint.currentPoint)
lineStart.stroke()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment