AGPGraphView.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. // LoopFollow
  2. // AGPGraphView.swift
  3. import Charts
  4. import SwiftUI
  5. struct AGPGraphView: UIViewRepresentable {
  6. let agpData: [AGPDataPoint]
  7. func makeCoordinator() -> Coordinator {
  8. Coordinator()
  9. }
  10. func makeUIView(context _: Context) -> UIView {
  11. let containerView = NonInteractiveContainerView()
  12. containerView.backgroundColor = .systemBackground
  13. let chartView = LineChartView()
  14. chartView.rightAxis.enabled = true
  15. chartView.leftAxis.enabled = false
  16. chartView.xAxis.labelPosition = .bottom
  17. chartView.rightAxis.drawGridLinesEnabled = false
  18. chartView.leftAxis.drawGridLinesEnabled = false
  19. chartView.xAxis.drawGridLinesEnabled = false
  20. chartView.rightAxis.valueFormatter = ChartYMMOLValueFormatter()
  21. chartView.legend.enabled = false
  22. chartView.chartDescription.enabled = false
  23. chartView.isUserInteractionEnabled = false
  24. containerView.addSubview(chartView)
  25. chartView.translatesAutoresizingMaskIntoConstraints = false
  26. NSLayoutConstraint.activate([
  27. chartView.topAnchor.constraint(equalTo: containerView.topAnchor),
  28. chartView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
  29. chartView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
  30. chartView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
  31. ])
  32. return containerView
  33. }
  34. class Coordinator {}
  35. func updateUIView(_ containerView: UIView, context _: Context) {
  36. guard let chartView = containerView.subviews.first as? LineChartView else { return }
  37. guard !agpData.isEmpty else { return }
  38. var p5Entries: [ChartDataEntry] = []
  39. var p25Entries: [ChartDataEntry] = []
  40. var p50Entries: [ChartDataEntry] = []
  41. var p75Entries: [ChartDataEntry] = []
  42. var p95Entries: [ChartDataEntry] = []
  43. for point in agpData {
  44. let x = Double(point.timeOfDay) / 60.0
  45. p5Entries.append(ChartDataEntry(x: x, y: point.p5))
  46. p25Entries.append(ChartDataEntry(x: x, y: point.p25))
  47. p50Entries.append(ChartDataEntry(x: x, y: point.p50))
  48. p75Entries.append(ChartDataEntry(x: x, y: point.p75))
  49. p95Entries.append(ChartDataEntry(x: x, y: point.p95))
  50. }
  51. let sortedP5 = p5Entries.sorted { $0.x < $1.x }
  52. let sortedP25 = p25Entries.sorted { $0.x < $1.x }
  53. let sortedP50 = p50Entries.sorted { $0.x < $1.x }
  54. let sortedP75 = p75Entries.sorted { $0.x < $1.x }
  55. let sortedP95 = p95Entries.sorted { $0.x < $1.x }
  56. guard !sortedP5.isEmpty, !sortedP25.isEmpty, !sortedP50.isEmpty,
  57. !sortedP75.isEmpty, !sortedP95.isEmpty
  58. else {
  59. return
  60. }
  61. let p5DataSet = LineChartDataSet(entries: sortedP5, label: "5th")
  62. p5DataSet.colors = [NSUIColor.systemGray.withAlphaComponent(0.6)]
  63. p5DataSet.lineWidth = 1.5
  64. p5DataSet.drawCirclesEnabled = false
  65. p5DataSet.drawValuesEnabled = false
  66. p5DataSet.drawFilledEnabled = false
  67. p5DataSet.mode = .linear
  68. let p25DataSet = LineChartDataSet(entries: sortedP25, label: "25th")
  69. p25DataSet.colors = [NSUIColor.systemBlue.withAlphaComponent(0.7)]
  70. p25DataSet.lineWidth = 1.5
  71. p25DataSet.drawCirclesEnabled = false
  72. p25DataSet.drawValuesEnabled = false
  73. p25DataSet.drawFilledEnabled = false
  74. p25DataSet.mode = .linear
  75. let p50DataSet = LineChartDataSet(entries: sortedP50, label: "Median")
  76. p50DataSet.colors = [NSUIColor.systemBlue]
  77. p50DataSet.lineWidth = 3
  78. p50DataSet.drawCirclesEnabled = false
  79. p50DataSet.drawValuesEnabled = false
  80. p50DataSet.drawFilledEnabled = false
  81. p50DataSet.mode = .linear
  82. let p75DataSet = LineChartDataSet(entries: sortedP75, label: "75th")
  83. p75DataSet.colors = [NSUIColor.systemBlue.withAlphaComponent(0.7)]
  84. p75DataSet.lineWidth = 1.5
  85. p75DataSet.drawCirclesEnabled = false
  86. p75DataSet.drawValuesEnabled = false
  87. p75DataSet.drawFilledEnabled = false
  88. p75DataSet.mode = .linear
  89. let p95DataSet = LineChartDataSet(entries: sortedP95, label: "95th")
  90. p95DataSet.colors = [NSUIColor.systemGray.withAlphaComponent(0.6)]
  91. p95DataSet.lineWidth = 1.5
  92. p95DataSet.drawCirclesEnabled = false
  93. p95DataSet.drawValuesEnabled = false
  94. p95DataSet.drawFilledEnabled = false
  95. p95DataSet.mode = .linear
  96. let maxY = max(sortedP95.map { $0.y }.max() ?? 300, 300) + 10
  97. let hourMinY = min(sortedP5.map { $0.y }.min() ?? 0, 0) - 10
  98. var hourLines: [ChartDataEntry] = []
  99. for hour in 0 ... 24 {
  100. let x = Double(hour)
  101. hourLines.append(ChartDataEntry(x: x, y: hourMinY))
  102. hourLines.append(ChartDataEntry(x: x, y: maxY))
  103. if hour < 24 {
  104. hourLines.append(ChartDataEntry(x: x + 0.0001, y: hourMinY))
  105. }
  106. }
  107. let hourLinesDataSet = LineChartDataSet(entries: hourLines, label: "Hours")
  108. hourLinesDataSet.colors = [NSUIColor.label.withAlphaComponent(0.3)]
  109. hourLinesDataSet.lineWidth = 1
  110. hourLinesDataSet.drawCirclesEnabled = false
  111. hourLinesDataSet.drawValuesEnabled = false
  112. hourLinesDataSet.drawFilledEnabled = false
  113. let data = LineChartData()
  114. data.append(p5DataSet)
  115. data.append(p25DataSet)
  116. data.append(p50DataSet)
  117. data.append(p75DataSet)
  118. data.append(p95DataSet)
  119. data.append(hourLinesDataSet)
  120. chartView.data = data
  121. chartView.notifyDataSetChanged()
  122. chartView.setNeedsDisplay()
  123. }
  124. }