mvm_core_ui/MVMCoreUI/Molecules/DoughnutChart.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()
}
}