Created
October 3, 2017 15:52
-
-
Save rsaenzi/e90de5e9ae52374926f6c5ecae5b774e to your computer and use it in GitHub Desktop.
Custom view to show a simple pie chart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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