Advertisement
Guest User

ChartViewController

a guest
Sep 19th, 2018
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 16.51 KB | None | 0 0
  1. //
  2. //  ChartViewController.swift
  3. //  Emiltonia
  4.  
  5. //Age    P3    P15    P50    P85    P97
  6.  
  7. import UIKit
  8. import Charts
  9.  
  10.  
  11. fileprivate let yLabelRectInset = CGFloat(4)
  12. fileprivate let yLabelForegroundColor = UIColor.white
  13. fileprivate let yLabelBackgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
  14. fileprivate let labelFont = Theme.Fonts.Controls.compact
  15. fileprivate let labelColor = Theme.Colors.Text.inputTextGray
  16. fileprivate let lineColorRed = Theme.Colors.Stroke.emiltoniaRed
  17. fileprivate let lineColorGreen = Theme.Colors.Stroke.emiltoniaGreen
  18. fileprivate let chartBackgroundColor = UIColor(red: 0.97, green: 0.97, blue: 0.97, alpha: 1)
  19. fileprivate let chartGridColor = UIColor(red: 0.80, green: 0.80, blue: 0.80, alpha: 1)
  20. fileprivate let chartBorderColor = UIColor(red: 0.60, green: 0.60, blue: 0.60, alpha: 1)
  21. fileprivate let statisticColor = UIColor(red: 0.60, green: 0.60, blue: 0.60, alpha: 1)
  22.  
  23. fileprivate let dashes = [
  24.     [CGFloat(4), CGFloat(8)],
  25.     [CGFloat(6), CGFloat(6)],
  26.     [CGFloat(8), CGFloat(4)],
  27.     [CGFloat(6), CGFloat(6)],
  28.     [CGFloat(4), CGFloat(8)]
  29. ]
  30. fileprivate let dashWidth = CGFloat(1.5)
  31. fileprivate let babyLineWidth = CGFloat(2)
  32.  
  33.  
  34.  
  35. class ChartViewController: UIViewController, ChartViewDelegate {
  36.    
  37.     private(set) var dataProvider:ChartDataProvider!
  38.    
  39.     var chartView:ChartView {
  40.         return self.view as! ChartView
  41.     }
  42.    
  43.     override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
  44.         return .portrait
  45.     }
  46.    
  47.     override func viewDidLoad() {
  48.         super.viewDidLoad()
  49.        
  50.         setupChart()
  51.         setupBabyLine()
  52.        
  53.         self.chartView.shareScreenButton.addTarget(self, action: #selector(didTapShare(_:)), for: .touchUpInside)
  54.     }
  55.    
  56.     override func viewWillAppear(_ animated: Bool) {
  57.         super.viewWillAppear(animated)
  58.        
  59.         setupLegend()
  60.     }
  61.    
  62.     fileprivate func setupLegend() {
  63.    
  64.         let legend = self.chartView.lineChartView.legend
  65.         legend.enabled = false
  66.    
  67.         let chv = self.chartView
  68.         let baby = self.dataProvider.baby
  69.        
  70.         chv.babyLegend.label.text = baby.name
  71.         chv.layoutIfNeeded()
  72.        
  73.         let wConst = chv.babyLegend.bounds.size.width * 0.8
  74.         if chv.babyLegend.label.bounds.size.width >= wConst {
  75.             chv.babyLegend.label.widthAnchor.constraint(equalToConstant: wConst).isActive = true
  76.             chv.layoutIfNeeded()
  77.         }
  78.        
  79.         chv.babyLegend.addDashedLine(width: babyLineWidth, pattern: [CGFloat](), color: self.babyChartColor(baby).first!)
  80.         chv.p3Legend.addDashedLine(width: dashWidth, pattern: dashes[0], color: statisticColor)
  81.         chv.p15Legend.addDashedLine(width: dashWidth, pattern: dashes[1], color: statisticColor)
  82.         chv.p50Legend.addDashedLine(width: dashWidth, pattern: dashes[2], color: statisticColor)
  83.         chv.p85Legend.addDashedLine(width: dashWidth, pattern: dashes[3], color: statisticColor)
  84.         chv.p97Legend.addDashedLine(width: dashWidth, pattern: dashes[4], color: statisticColor)
  85.     }
  86.    
  87.     fileprivate func setupChart() {
  88.    
  89.         self.chartView.backgroundColor = chartBackgroundColor
  90.        
  91.         let lineChartView = self.chartView.lineChartView!
  92.         lineChartView.backgroundColor = chartBackgroundColor
  93.         lineChartView.borderColor = chartBorderColor
  94.         //lineChartView.borderLineWidth = 1
  95.         lineChartView.drawBordersEnabled = true
  96.         lineChartView.dragXEnabled = true
  97.         lineChartView.dragYEnabled = true//false
  98.         lineChartView.scaleXEnabled = true
  99.         lineChartView.scaleYEnabled = true//false
  100.         lineChartView.viewPortHandler.setMaximumScaleX(4)
  101.         lineChartView.viewPortHandler.setMaximumScaleY(2)
  102.         lineChartView.delegate = self
  103.        
  104.         let xValueRenderer = XValueRenderer(
  105.             viewPortHandler: lineChartView.viewPortHandler,
  106.             xAxis: lineChartView.xAxis,
  107.             transformer: lineChartView.xAxisRenderer.transformer
  108.         )
  109.         let baby = self.dataProvider.baby
  110.         xValueRenderer.birthColor = self.babyChartColor(baby).first!
  111.         lineChartView.xAxisRenderer = xValueRenderer
  112.        
  113.         let xAxis = lineChartView.xAxis
  114.         xAxis.labelPosition = .bottom
  115.         xAxis.gridColor = chartGridColor
  116.         xAxis.labelFont = labelFont
  117.         xAxis.labelTextColor = labelColor
  118.         xAxis.valueFormatter = XValueFormatter()
  119.         xAxis.labelCount = UIScreen.main.bounds.size.width > 375 ? 5 : 4
  120.         xAxis.axisMinimum = self.dataProvider.leftXLimit
  121.         xAxis.axisMaximum = self.dataProvider.rightXLimit
  122.        
  123.         lineChartView.getAxis(.right).enabled = false
  124.        
  125.         let yValueRenderer = YValueRenderer(
  126.             viewPortHandler:  lineChartView.viewPortHandler,
  127.             yAxis: lineChartView.getAxis(.left),
  128.             transformer: lineChartView.xAxisRenderer.transformer
  129.         )
  130.         lineChartView.leftYAxisRenderer = yValueRenderer
  131.        
  132.         let yAxis = lineChartView.getAxis(.left)
  133.         yAxis.labelPosition = .insideChart
  134.         yAxis.drawTopYLabelEntryEnabled = false
  135.         yAxis.drawBottomYLabelEntryEnabled = false
  136.         yAxis.axisMinimum = self.dataProvider.lowerYLimit
  137.         yAxis.axisMaximum = self.dataProvider.upperYLimit
  138.         yAxis.labelCount = Int((yAxis.axisMaximum - yAxis.axisMinimum) / self.dataProvider.yStep)
  139.         yAxis.gridColor = chartGridColor
  140.         yAxis.labelFont = labelFont
  141.         yAxis.labelTextColor = yLabelForegroundColor
  142.         yAxis.valueFormatter = YValueFormatter()
  143.         yAxis.xOffset = yAxis.xOffset + yLabelRectInset
  144.     }
  145.    
  146.    
  147.     fileprivate func setupBabyLine() {
  148.        
  149.         let baby = self.dataProvider.baby
  150.        
  151.         let line = LineChartDataSet(values: self.dataProvider.babyDataEntry, label: baby.name)
  152.         line.colors = babyChartColor(baby).map{ NSUIColor(cgColor: $0.cgColor) }
  153.         line.circleColors = [NSUIColor(cgColor: UIColor.white.cgColor)]
  154.         line.circleHoleColor = line.colors[0]
  155.         line.lineWidth = babyLineWidth
  156.         line.circleRadius = line.lineWidth * 3
  157.         line.circleHoleRadius = line.circleRadius * 0.8
  158.         line.axisDependency = .left
  159.         line.drawValuesEnabled = false
  160.         line.drawVerticalHighlightIndicatorEnabled = false
  161.         line.drawHorizontalHighlightIndicatorEnabled = false
  162.    
  163.         let data = LineChartData()
  164.         statisticLines().forEach {
  165.             data.addDataSet($0)
  166.         }
  167.         data.addDataSet(line)
  168.        
  169.         self.chartView.lineChartView.data = data
  170.         self.chartView.lineChartView.chartDescription = nil
  171.     }
  172.    
  173.     fileprivate func statisticLines() -> [LineChartDataSet] {
  174.        
  175.         let data = self.dataProvider!
  176.         let entries = [
  177.             data.statisticP3Entry,
  178.             data.statisticP15Entry,
  179.             data.statisticP50Entry,
  180.             data.statisticP85Entry,
  181.             data.statisticP97Entry
  182.         ]
  183.        
  184.         var chartDataSets = [LineChartDataSet]()
  185.        
  186.         for i in 0..<entries.count {
  187.            
  188.             let line = LineChartDataSet(values: entries[i], label: "")
  189.             line.colors = [NSUIColor(cgColor: statisticColor.cgColor)]
  190.             line.drawCircleHoleEnabled = false
  191.             line.drawCirclesEnabled = false
  192.             line.axisDependency = .left
  193.             line.drawValuesEnabled = false
  194.             line.drawVerticalHighlightIndicatorEnabled = false
  195.             line.drawHorizontalHighlightIndicatorEnabled = false
  196.             line.lineDashLengths = dashes[i]
  197.             line.lineWidth = dashWidth
  198.            
  199.             chartDataSets.append(line)
  200.         }
  201.         return chartDataSets
  202.     }
  203.    
  204.    
  205.     func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) {
  206.         recalculateXLimits()
  207.     }
  208.    
  209.     func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat) {
  210.         recalculateXLimits()
  211.     }
  212.    
  213.     func recalculateXLimits() {
  214.        
  215.         let lineChartView = self.chartView.lineChartView!
  216.         let minX = -Double(4.0 / lineChartView.scaleX)
  217.    
  218.         if lineChartView.lowestVisibleX < minX {
  219.             lineChartView.moveViewToX(minX)
  220.             lineChartView.setNeedsDisplay()
  221.         }
  222.     }
  223.    
  224.     fileprivate func babyChartColor(_ baby:Baby) -> [UIColor] {
  225.        
  226.         var colors = [UIColor]()
  227.         if baby.gender == .male {
  228.             colors.append(lineColorGreen)
  229.         } else {
  230.             colors.append(lineColorRed)
  231.         }
  232.         return colors
  233.     }
  234.    
  235.     class YValueFormatter: IndexAxisValueFormatter {
  236.        
  237.         override func stringForValue(_ value: Double, axis: AxisBase?) -> String {
  238.             var s = "\(Int(value.rounded()))"
  239.             if s.count > 3 {
  240.                 s.insert(".", at: String.Index(encodedOffset: s.count - 3))
  241.             }
  242.             return s
  243.         }
  244.     }
  245.    
  246.     class XValueFormatter: IndexAxisValueFormatter {
  247.        
  248.         let weeksStr = CommonStrings.Units.weeks.localized.lowercased()
  249.    
  250.         override func stringForValue(_ value: Double, axis: AxisBase?) -> String {
  251.            
  252.             if value == 0 {
  253.                 return "kBirthday"
  254.             }
  255.             if value < 1 {
  256.                 return ""
  257.             }
  258.             let i = Int(value.rounded())
  259.             let s = "\(i) \(weeksStr)"
  260.             return s
  261.         }
  262.     }
  263.    
  264.     class XValueRenderer: XAxisRenderer {
  265.        
  266.         let birthStr = CommonStrings.Units.birthday.localized
  267.         let birthImgSize = CGFloat(15)
  268.        
  269.         private(set) var birthImg:UIImage? = nil
  270.        
  271.         var birthColor: UIColor? = nil {
  272.             didSet {
  273.                 if let col = birthColor, let img = UIImage(named: "birth_image") {
  274.                     birthImg = img.imageWithColor(color: col)
  275.                 } else {
  276.                     birthImg = nil
  277.                 }
  278.             }
  279.         }
  280.        
  281.         override func drawLabel(context: CGContext, formattedLabel: String, x: CGFloat, y: CGFloat, attributes: [NSAttributedStringKey : Any], constrainedToSize: CGSize, anchor: CGPoint, angleRadians: CGFloat) {
  282.            
  283.             if formattedLabel == "kBirthday" {
  284.                
  285.                 var mAttributes = attributes
  286.                
  287.                 if let col = birthColor {
  288.                     mAttributes[NSAttributedStringKey.foregroundColor] = col
  289.                 }
  290.                 if let font = mAttributes[NSAttributedStringKey.font] as? UIFont {
  291.                     let name = font.fontName.replacingOccurrences(of: "Regular", with: "SemiBold")
  292.                     mAttributes[NSAttributedStringKey.font] = UIFont(name: name, size: font.pointSize) ?? font
  293.                 }
  294.                
  295.                 if let img = birthImg {
  296.                     UIGraphicsPushContext(context)
  297.                     img.draw(in: CGRect(x: x - birthImgSize/2, y: viewPortHandler.contentBottom - birthImgSize/2, width: birthImgSize, height: birthImgSize))
  298.                     UIGraphicsPopContext()
  299.                 }
  300.                
  301.                 super.drawLabel(context: context, formattedLabel: birthStr, x: x, y: y, attributes: mAttributes, constrainedToSize: constrainedToSize, anchor: anchor, angleRadians: angleRadians)
  302.                 return
  303.             }
  304.             super.drawLabel(context: context, formattedLabel: formattedLabel, x: x, y: y, attributes: attributes, constrainedToSize: constrainedToSize, anchor: anchor, angleRadians: angleRadians)
  305.         }
  306.     }
  307.    
  308.    
  309.     class YValueRenderer: YAxisRenderer {
  310.        
  311.         override func renderAxisLabels(context: CGContext) {
  312.            
  313.             guard let yAxis = self.axis as? YAxis else {
  314.                 return
  315.             }
  316.             if !yAxis.isEnabled || !yAxis.isDrawLabelsEnabled {
  317.                 return
  318.             }
  319.            
  320.             let xoffset = yAxis.xOffset
  321.             let yoffset = yAxis.labelFont.lineHeight / 2.5 + yAxis.yOffset
  322.             let dependency = yAxis.axisDependency
  323.             let labelPosition = yAxis.labelPosition
  324.             var xPos = CGFloat(0.0)
  325.             var textAlign: NSTextAlignment
  326.            
  327.             if dependency == .left {
  328.                 if labelPosition == .outsideChart {
  329.                     textAlign = .right
  330.                     xPos = viewPortHandler.offsetLeft - xoffset
  331.                 } else {
  332.                     textAlign = .left
  333.                     xPos = viewPortHandler.offsetLeft + xoffset
  334.                 }
  335.             } else {
  336.                 if labelPosition == .outsideChart {
  337.                     textAlign = .left
  338.                     xPos = viewPortHandler.contentRight + xoffset
  339.                 } else {
  340.                     textAlign = .right
  341.                     xPos = viewPortHandler.contentRight - xoffset
  342.                 }
  343.             }
  344.            
  345.             let fixedPosition = xPos
  346.             let positions = transformedPositions()
  347.             let offset = yoffset - yAxis.labelFont.lineHeight
  348.            
  349.             let from = yAxis.isDrawBottomYLabelEntryEnabled ? 0 : 1
  350.             let to = yAxis.isDrawTopYLabelEntryEnabled ? yAxis.entryCount : (yAxis.entryCount - 1)
  351.             let attributes = [NSAttributedStringKey.font: labelFont]
  352.             let col = yLabelBackgroundColor
  353.            
  354.             for i in stride(from: from, to: to, by: 1) {
  355.                
  356.                 let text = yAxis.getFormattedLabel(i)
  357.                 var point = CGPoint(x: fixedPosition, y: positions[i].y + offset)
  358.                 let textSize = text.size(withAttributes: attributes)
  359.                
  360.                 if textAlign == .center {
  361.                     point.x -= textSize.width / 2.0
  362.                 } else if textAlign == .right {
  363.                     point.x -= textSize.width
  364.                 }
  365.                
  366.                 let rect = CGRect(origin: point, size: textSize).insetBy(dx: -yLabelRectInset, dy: 0)
  367.                 let path = UIBezierPath.init(roundedRect: rect, cornerRadius: yLabelRectInset)
  368.            
  369.                 UIGraphicsPushContext(context)
  370.                 col.set()
  371.                 path.fill()
  372.                 UIGraphicsPopContext()
  373.             }
  374.            
  375.             super.renderAxisLabels(context: context)
  376.         }
  377.     }
  378. }
  379.  
  380. extension ChartViewController: ExtendedViewController {
  381.    
  382.     static func viewController(with dataProvider: Any?) throws -> UIViewController {
  383.        
  384.         guard let dataProvider = dataProvider as? ChartDataProvider else {
  385.             throw ExtendedViewControllerError.unknownDataProvider
  386.         }
  387.        
  388.         let storyboard = UIStoryboard(name: "Chart", bundle: nil)
  389.         let vc = storyboard.instantiateInitialViewController() as! ChartViewController
  390.         vc.dataProvider = dataProvider
  391.        
  392.         return vc
  393.     }
  394.    
  395.     func present(at primaryViewController: UIViewController) throws {
  396.        
  397.         guard let navigationController = primaryViewController.navigationController else {
  398.             throw ExtendedViewControllerError.presentationFailed
  399.         }
  400.        
  401.         DispatchQueue.main.async {
  402.             navigationController.pushViewController(self, animated: primaryViewController.isActiveAndVisible)
  403.         }
  404.     }
  405.    
  406.     func dismiss(onClose:((_ dataProvider:Any?)->())?) throws {
  407.         throw ExtendedViewControllerError.emptyMethod
  408.     }
  409.    
  410.     func navigationTitleText() -> String {
  411.         return "ChartViewController".localized(tableName: "NavigationTitle")
  412.     }
  413.    
  414.     @objc func didTapShare(_ sender:Any?) {
  415.        
  416.         let v = self.chartView
  417.         let renderer = UIGraphicsImageRenderer(size: v.bounds.size)
  418.        
  419.         let image = renderer.image { ctx in
  420.             v.drawHierarchy(in: v.bounds, afterScreenUpdates: true)
  421.         }
  422.        
  423.         let vc = UIActivityViewController(
  424.             activityItems: [image.crop(rect: self.chartView.captureRect)],
  425.             applicationActivities: []
  426.         )
  427.         present(vc, animated: true)
  428.     }
  429. }
  430.  
  431. extension UIImage {
  432.     func crop( rect: CGRect) -> UIImage {
  433.         var rect = rect
  434.         rect.origin.x*=self.scale
  435.         rect.origin.y*=self.scale
  436.         rect.size.width*=self.scale
  437.         rect.size.height*=self.scale
  438.        
  439.         let imageRef = self.cgImage!.cropping(to: rect)
  440.         let image = UIImage(cgImage: imageRef!, scale: self.scale, orientation: self.imageOrientation)
  441.         return image
  442.     }
  443. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement