// // 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.layoutIfNeeded() let radius = sizeObject.getValueBasedOnApplicationWidth()/2 - lineWidth() let labelheight = labelContainer.frame.height/2 let padding = sizeObject.getValueBasedOnApplicationWidth()/2 - sqrt(pow(radius, 2) - pow(labelheight, 2)) labelContainer.leftPin?.constant = padding labelContainer.topPin?.constant = padding labelContainer.setNeedsDisplay() } }