Skip to content

Instantly share code, notes, and snippets.

@DominatorVbN
Last active October 24, 2018 13:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DominatorVbN/29ce9a73f5e768e7433b2bfd1095f20f to your computer and use it in GitHub Desktop.
Save DominatorVbN/29ce9a73f5e768e7433b2bfd1095f20f to your computer and use it in GitHub Desktop.
Custom UIView Graph.
//
// GraphView.swift
// Flo
//
// Created by mac on 24/10/18.
// Copyright © 2018 Dominator. All rights reserved.
//
import UIKit
//Weekly sample data
@IBDesignable class GraphView: UIView {
private struct Constants {
static let cornerRadiusSize = CGSize(width: 8.0, height: 8.0)
static let margin: CGFloat = 20.0
static let topBorder: CGFloat = 60
static let bottomBorder: CGFloat = 50
static let colorAlpha: CGFloat = 0.3
static let circleDiameter: CGFloat = 5.0
}
var graphPoints = [400, 210, 600, 400, 500, 800, 3]
var measureindicators : [String] = ["s","m","t","w","t","f","s"]
// 1
@IBInspectable var startColor: UIColor = .red
@IBInspectable var endColor: UIColor = .green
@IBInspectable var graphTitle : String = "Title"
@IBInspectable var titleShadowcolor : UIColor = .clear
@IBInspectable var averageShadowcolor : UIColor = .clear
public var averageValue: Double {
return Double(round(100*graphPoints.average)/100)
}
var titleLabel = UILabel()
var averageTextLabel = UILabel()
var averageValueLabel = UILabel()
var maxPointLabel = UILabel()
var minPointLabel = UILabel()
var numberLineStackView = UIStackView()
override func draw(_ rect: CGRect) {
let width = rect.width
let height = rect.height
let path = UIBezierPath(roundedRect: rect,
byRoundingCorners: .allCorners,
cornerRadii: Constants.cornerRadiusSize)
path.addClip()
// 2
let context = UIGraphicsGetCurrentContext()!
let colors = [startColor.cgColor, endColor.cgColor]
// 3
let colorSpace = CGColorSpaceCreateDeviceRGB()
// 4
let colorLocations: [CGFloat] = [0.0, 1.0]
// 5
let gradient = CGGradient(colorsSpace: colorSpace,
colors: colors as CFArray,
locations: colorLocations)!
// 6
let startPoint = CGPoint.zero
let endPoint = CGPoint(x: 0, y: bounds.height)
context.drawLinearGradient(gradient,
start: startPoint,
end: endPoint,
options: [])
//calculate the x point
let margin = Constants.margin
let graphWidth = width - margin * 2 - 4
let columnXPoint = { (column: Int) -> CGFloat in
//Calculate the gap between points
let spacing = graphWidth / CGFloat(self.graphPoints.count - 1)
return CGFloat(column) * spacing + margin + 2
}
// calculate the y point
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
// draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
// set up the points line
let graphPath = UIBezierPath()
// go to start of line
graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
// add points for each item in the graphPoints array
// at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
graphPath.addLine(to: nextPoint)
}
//Create the clipping path for the graph gradient
//1 - save the state of the context (commented out for now)
context.saveGState()
//2 - make a copy of the path
let clippingPath = graphPath.copy() as! UIBezierPath
//3 - add lines to the copied path to complete the clip area
clippingPath.addLine(to: CGPoint(x: columnXPoint(graphPoints.count - 1), y:height))
clippingPath.addLine(to: CGPoint(x:columnXPoint(0), y:height))
clippingPath.close()
//4 - add the clipping path to the context
clippingPath.addClip()
let highestYPoint = columnYPoint(maxValue)
let graphStartPoint = CGPoint(x: margin, y: highestYPoint)
let graphEndPoint = CGPoint(x: margin, y: bounds.height)
context.drawLinearGradient(gradient, start: graphStartPoint, end: graphEndPoint, options: [])
context.restoreGState()
//draw the line on top of the clipped gradient
graphPath.lineWidth = 2.0
graphPath.stroke()
//Draw the circles on top of the graph stroke
for i in 0..<graphPoints.count {
var point = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
point.x -= Constants.circleDiameter / 2
point.y -= Constants.circleDiameter / 2
let circle = UIBezierPath(ovalIn: CGRect(origin: point, size: CGSize(width: Constants.circleDiameter, height: Constants.circleDiameter)))
circle.fill()
}
//Draw horizontal graph lines on the top of everything
let linePath = UIBezierPath()
//top line
linePath.move(to: CGPoint(x: margin, y: topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: topBorder))
//center line
linePath.move(to: CGPoint(x: margin, y: graphHeight/2 + topBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: graphHeight/2 + topBorder))
//bottom line
linePath.move(to: CGPoint(x: margin, y:height - bottomBorder))
linePath.addLine(to: CGPoint(x: width - margin, y: height - bottomBorder))
let color = UIColor(white: 1.0, alpha: Constants.colorAlpha)
color.setStroke()
linePath.lineWidth = 1.0
linePath.stroke()
//set up title label
titleLabel.frame = CGRect(x: 5, y: 5, width: 100, height: UIFont.systemFontSize + 2)
titleLabel.textAlignment = .left
titleLabel.textColor = .white
titleLabel.text = graphTitle
titleLabel.font = UIFont(name: "AvenirNextCondensed-Medium", size: UIFont.systemFontSize)
titleLabel.shadowColor = titleShadowcolor
titleLabel.shadowOffset = CGSize(width: 3, height: 3)
addSubview(titleLabel)
//set up average label
averageTextLabel.frame = CGRect(x: 5, y: titleLabel.bounds.maxY + 5 , width:50, height: UIFont.systemFontSize + 2)
averageTextLabel.textAlignment = .left
averageTextLabel.textColor = .white
averageTextLabel.text = "Average : "
averageTextLabel.font = UIFont(name: "AvenirNextCondensed-Medium", size: UIFont.systemFontSize)
averageTextLabel.shadowColor = averageShadowcolor
averageTextLabel.shadowOffset = CGSize(width: 3, height: 3)
addSubview(averageTextLabel)
//set up average value label
averageValueLabel.frame = CGRect(x: averageTextLabel.bounds.maxX + 5, y: titleLabel.bounds.maxY + 5 , width: 100, height: UIFont.systemFontSize + 2)
averageValueLabel.textAlignment = .left
averageValueLabel.textColor = .white
averageValueLabel.text = "\(averageValue)"
averageValueLabel.font = UIFont(name: "AvenirNextCondensed-Medium", size: UIFont.systemFontSize)
averageValueLabel.shadowColor = averageShadowcolor
averageValueLabel.shadowOffset = CGSize(width: 3, height: 3)
addSubview(averageValueLabel)
//set up max label
maxPointLabel.frame = CGRect(x: width - margin - 15 , y: topBorder - (UIFont.systemFontSize + 2) , width: 50, height: UIFont.systemFontSize + 2)
maxPointLabel.textAlignment = .left
maxPointLabel.textColor = .white
maxPointLabel.text = "\(maxValue)"
maxPointLabel.font = UIFont(name: "AvenirNextCondensed-Medium", size: UIFont.systemFontSize)
maxPointLabel.shadowColor = averageShadowcolor
maxPointLabel.shadowOffset = CGSize(width: 3, height: 3)
addSubview(maxPointLabel)
minPointLabel.frame = CGRect(x: width - margin + 5 , y: (height - bottomBorder) - (UIFont.systemFontSize + 2)/2 , width: 50, height: UIFont.systemFontSize + 2)
minPointLabel.textAlignment = .left
minPointLabel.textColor = .white
minPointLabel.text = "\(0)"
minPointLabel.font = UIFont(name: "AvenirNextCondensed-Medium", size: UIFont.systemFontSize)
minPointLabel.shadowColor = averageShadowcolor
minPointLabel.shadowOffset = CGSize(width: 3, height: 3)
addSubview(minPointLabel)
//set up stackView
numberLineStackView.frame = CGRect(x: margin, y: (height - bottomBorder) + 5, width: self.bounds.width - (2 * margin), height: (UIFont.systemFontSize + 2))
numberLineStackView.alignment = .center
numberLineStackView.distribution = .equalSpacing
numberLineStackView.spacing = 8
for i in measureindicators{
let lab = UILabel()
lab.textColor = .white
lab.frame = CGRect(x: 0, y: 0, width: 20, height: 20)
lab.text = i
numberLineStackView.addArrangedSubview(lab)
}
addSubview(numberLineStackView)
}
}
extension Collection where Element: BinaryInteger {
/// Returns the average of all elements in the array
var average: Double {
return isEmpty ? 0 : Double(Int(total)) / Double(count)
}
}
extension Collection where Element: Numeric {
/// Returns the total sum of all elements in the array
var total: Element { return reduce(0, +) }
}
func setupGraphView(){
}
@DominatorVbN
Copy link
Author

screen shot 2018-10-24 at 6 39 15 pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment