183 lines
7.1 KiB
Swift
183 lines
7.1 KiB
Swift
//
|
|
// DoughnutChart.swift
|
|
// MVMCoreUI
|
|
//
|
|
// Created by Murugan, Vimal on 07/01/20.
|
|
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
open class DoughnutChart: View, MVMCoreUIViewConstrainingProtocol {
|
|
|
|
var containerView = MVMCoreUICommonViewsUtility.commonView()
|
|
var containerLayer = CALayer()
|
|
var titleLabel = Label.commonLabelH3(true)
|
|
var subTitleLabel = Label.commonLabelB2(true)
|
|
var doughnutChartModel: DoughnutChartModel? {
|
|
get { return model as? DoughnutChartModel }
|
|
}
|
|
var labelContainer = ViewConstrainingView.empty()
|
|
var heightConstraint: NSLayoutConstraint?
|
|
|
|
private var sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: 150)!
|
|
|
|
var height: CGFloat = 150 {
|
|
didSet {
|
|
if height != oldValue {
|
|
sizeObject = MFStyler.sizeObjectGeneric(forCurrentDevice: height)!
|
|
updateContainer()
|
|
drawGraph()
|
|
}
|
|
}
|
|
}
|
|
|
|
open override func updateView(_ size: CGFloat) {
|
|
super.updateView(size)
|
|
titleLabel.updateView(size)
|
|
subTitleLabel.updateView(size)
|
|
updateContainer()
|
|
drawGraph()
|
|
}
|
|
|
|
open override func reset() {
|
|
super.reset()
|
|
titleLabel.reset()
|
|
subTitleLabel.reset()
|
|
clearLayers()
|
|
}
|
|
|
|
public override func setAsMolecule() {
|
|
titleLabel.setAsMolecule()
|
|
subTitleLabel.setAsMolecule()
|
|
}
|
|
|
|
open override func setupView() {
|
|
super.setupView()
|
|
guard containerView.superview == nil else {
|
|
return
|
|
}
|
|
addSubview(containerView)
|
|
addSubview(labelContainer)
|
|
|
|
labelContainer.addSubview(titleLabel)
|
|
labelContainer.addSubview(subTitleLabel)
|
|
titleLabel.textAlignment = .center
|
|
subTitleLabel.textAlignment = .center
|
|
|
|
//Make label font size to adjust width if label content is high
|
|
titleLabel.numberOfLines = 1
|
|
titleLabel.adjustsFontSizeToFitWidth = true
|
|
|
|
containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
|
|
containerView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
|
bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
|
|
trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
|
|
containerView.layer.addSublayer(containerLayer)
|
|
heightConstraint = containerView.heightAnchor.constraint(equalToConstant:
|
|
sizeObject.getValueBasedOnApplicationWidth())
|
|
heightConstraint?.isActive = true
|
|
containerView.widthAnchor.constraint(equalTo: containerView.heightAnchor).isActive = true
|
|
|
|
labelContainer.leftPin = labelContainer.leftAnchor.constraint(greaterThanOrEqualTo: containerView.leftAnchor, constant: lineWidth())
|
|
labelContainer.leftPin?.isActive = true
|
|
labelContainer.topPin = labelContainer.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor, constant: lineWidth())
|
|
labelContainer.topPin?.isActive = true
|
|
labelContainer.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
|
|
labelContainer.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
|
|
|
|
NSLayoutConstraint.constraintPinSubview(titleLabel, pinTop: true, pinBottom: false, pinLeft: true, pinRight: true)
|
|
NSLayoutConstraint.constraintPinSubview(subTitleLabel, pinTop: false, pinBottom: true, pinLeft: true, pinRight: true)
|
|
_ = NSLayoutConstraint(pinFirstView: titleLabel, toSecondView: subTitleLabel, withConstant: 0, directionVertical: true)
|
|
//Rotate view for initial draw
|
|
containerLayer.transform = CATransform3DMakeRotation(1 * .pi, 0.0, 0.0, 1.0)
|
|
|
|
}
|
|
|
|
open override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
|
|
super.setWithModel(model, delegateObject, additionalData)
|
|
clearLayers()
|
|
guard let doughnutChartModel = model as? DoughnutChartModel else {
|
|
return
|
|
}
|
|
titleLabel.setWithModel(doughnutChartModel.title, delegateObject, additionalData)
|
|
subTitleLabel.setWithModel(doughnutChartModel.subtitle, delegateObject, additionalData)
|
|
titleLabel.textAlignment = .center
|
|
subTitleLabel.textAlignment = .center
|
|
updateLabelContainer()
|
|
drawGraph()
|
|
}
|
|
|
|
func drawGraph() {
|
|
clearLayers()
|
|
let widthHeight = sizeObject.getValueBasedOnApplicationWidth()
|
|
containerLayer.frame = CGRect(x: 0, y: 0,
|
|
width: widthHeight,
|
|
height: widthHeight)
|
|
if let doughnutChart = doughnutChartModel {
|
|
var prevPercent: CGFloat = 0.0
|
|
for model in doughnutChart.sections {
|
|
prevPercent += drawBar(model, prevPercent)
|
|
}
|
|
}
|
|
}
|
|
|
|
func drawBar(_ chartModel: ChartItemModel, _ prevPercent: CGFloat) -> CGFloat {
|
|
|
|
let shapeLayer = CAShapeLayer()
|
|
shapeLayer.frame = containerLayer.frame
|
|
shapeLayer.lineWidth = lineWidth()
|
|
shapeLayer.fillColor = nil
|
|
shapeLayer.strokeColor = UIColor.mfGet(forHex: chartModel.color).cgColor
|
|
|
|
let arcCenter = shapeLayer.position
|
|
let radius = shapeLayer.bounds.size.width / 2.0 - lineWidth()/2.0
|
|
//lineSizeObject.getValueBasedOnApplicationWidth() / 2.0
|
|
let clockwise = true
|
|
|
|
let value: CGFloat = chartModel.percent
|
|
let gap: CGFloat = spaceReuired() ? lineGap() : 0.0
|
|
let startAngle = ((prevPercent / 100.0) * 2 * .pi) - (0.5 * .pi)
|
|
let endAngle = ((value / 100.0) * 2 * .pi) + startAngle - gap
|
|
let circlePath = UIBezierPath(arcCenter: arcCenter,
|
|
radius: radius,
|
|
startAngle: startAngle,
|
|
endAngle: endAngle,
|
|
clockwise: clockwise)
|
|
|
|
shapeLayer.path = circlePath.cgPath
|
|
containerLayer.addSublayer(shapeLayer)
|
|
return value
|
|
}
|
|
|
|
func clearLayers() {
|
|
containerLayer.sublayers?.forEach({ $0.removeFromSuperlayer() })
|
|
}
|
|
|
|
func updateContainer() {
|
|
heightConstraint?.constant = sizeObject.getValueBasedOnApplicationWidth()
|
|
updateLabelContainer()
|
|
setNeedsDisplay()
|
|
}
|
|
|
|
func lineWidth() -> CGFloat {
|
|
return doughnutChartModel?.lineWidth ?? 30
|
|
}
|
|
|
|
func lineGap() -> CGFloat {
|
|
//If array is having the single item then no space required
|
|
return doughnutChartModel?.sections.count == 1 ? 0.0 : 0.05
|
|
}
|
|
|
|
func spaceReuired() -> Bool {
|
|
return doughnutChartModel?.spaceRequired ?? true
|
|
}
|
|
|
|
func updateLabelContainer() {
|
|
labelContainer.leftPin?.constant = lineWidth()
|
|
labelContainer.topPin?.constant = lineWidth()
|
|
labelContainer.setNeedsDisplay()
|
|
}
|
|
|
|
}
|