GlucoseChartView.swift 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import Charts
  2. import SwiftDate
  3. import SwiftUI
  4. extension DateFormatter: AxisValueFormatter {
  5. public func stringForValue(_ value: Double, axis _: AxisBase?) -> String {
  6. timeStyle = .short
  7. return string(from: Date(timeIntervalSince1970: value))
  8. }
  9. }
  10. struct GlucoseChartView: UIViewRepresentable {
  11. @Binding var glucose: [BloodGlucose]
  12. @Binding var suggestion: Suggestion?
  13. func makeUIView(context _: Context) -> LineChartView {
  14. let view = LineChartView()
  15. makeDataPointsFor(view: view)
  16. view.xAxis.valueFormatter = DateFormatter()
  17. return view
  18. }
  19. func updateUIView(_ view: LineChartView, context _: Context) {
  20. makeDataPointsFor(view: view)
  21. }
  22. private func makeDataPointsFor(view: LineChartView) {
  23. guard !glucose.isEmpty else {
  24. return
  25. }
  26. let dataPoints = glucose.map {
  27. ChartDataEntry(x: $0.dateString.timeIntervalSince1970, y: Double($0.sgv ?? 0))
  28. }
  29. let data = MyLineChartDataSet(entries: dataPoints, label: "BG")
  30. data.drawCirclesEnabled = true
  31. data.circleRadius = 2
  32. data.setCircleColor(.green)
  33. data.setColor(.green)
  34. data.lineWidth = 0
  35. data.drawValuesEnabled = false
  36. var series = [data]
  37. let lastDate = suggestion?.deliverAt ?? Date()
  38. if let iob = suggestion?.predictions?.iob {
  39. let dataPoints = iob.enumerated().map {
  40. ChartDataEntry(
  41. x: lastDate.addingTimeInterval(Double($0 * 300)).timeIntervalSince1970,
  42. y: Double($1)
  43. )
  44. }
  45. let data = MyLineChartDataSet(entries: dataPoints, label: "IOB")
  46. data.drawCirclesEnabled = true
  47. data.circleRadius = 2
  48. data.setCircleColor(.blue)
  49. data.setColor(.blue)
  50. data.lineWidth = 0
  51. data.drawValuesEnabled = false
  52. series.append(data)
  53. }
  54. if let zt = suggestion?.predictions?.zt {
  55. let dataPoints = zt.enumerated().map {
  56. ChartDataEntry(
  57. x: lastDate.addingTimeInterval(Double($0 * 300)).timeIntervalSince1970,
  58. y: Double($1)
  59. )
  60. }
  61. let data = MyLineChartDataSet(entries: dataPoints, label: "ZT")
  62. data.drawCirclesEnabled = true
  63. data.circleRadius = 2
  64. data.setCircleColor(.cyan)
  65. data.setColor(.cyan)
  66. data.lineWidth = 0
  67. data.drawValuesEnabled = false
  68. series.append(data)
  69. }
  70. if let cob = suggestion?.predictions?.cob {
  71. let dataPoints = cob.enumerated().map {
  72. ChartDataEntry(
  73. x: lastDate.addingTimeInterval(Double($0 * 300)).timeIntervalSince1970,
  74. y: Double($1)
  75. )
  76. }
  77. let data = MyLineChartDataSet(entries: dataPoints, label: "COB")
  78. data.drawCirclesEnabled = true
  79. data.circleRadius = 2
  80. data.setCircleColor(.orange)
  81. data.setColor(.orange)
  82. data.lineWidth = 0
  83. data.drawValuesEnabled = false
  84. series.append(data)
  85. }
  86. if let uam = suggestion?.predictions?.uam {
  87. let dataPoints = uam.enumerated().map {
  88. ChartDataEntry(
  89. x: lastDate.addingTimeInterval(Double($0 * 300)).timeIntervalSince1970,
  90. y: Double($1)
  91. )
  92. }
  93. let data = MyLineChartDataSet(entries: dataPoints, label: "UAM")
  94. data.drawCirclesEnabled = true
  95. data.circleRadius = 2
  96. data.setCircleColor(.yellow)
  97. data.setColor(.yellow)
  98. data.lineWidth = 0
  99. data.drawValuesEnabled = false
  100. series.append(data)
  101. }
  102. view.data = LineChartData(dataSets: series)
  103. }
  104. }
  105. class MyLineChartDataSet: LineChartDataSet {
  106. override func entryIndex(x xValue: Double, closestToY yValue: Double, rounding: ChartDataSetRounding) -> Int {
  107. var closest = partitioningIndex { $0.x >= xValue }
  108. if closest >= endIndex {
  109. closest = endIndex - 1
  110. }
  111. let closestXValue = self[closest].x
  112. switch rounding {
  113. case .up:
  114. // If rounding up, and found x-value is lower than specified x, and we can go upper...
  115. if closestXValue < xValue, closest < index(before: endIndex)
  116. {
  117. formIndex(after: &closest)
  118. }
  119. case .down:
  120. // If rounding down, and found x-value is upper than specified x, and we can go lower...
  121. if closestXValue > xValue, closest > startIndex
  122. {
  123. formIndex(before: &closest)
  124. }
  125. case .closest:
  126. break
  127. }
  128. // Search by closest to y-value
  129. if !yValue.isNaN
  130. {
  131. while closest > startIndex, self[index(before: closest)].x == closestXValue
  132. {
  133. formIndex(before: &closest)
  134. }
  135. var closestYValue = self[closest].y
  136. var closestYIndex = closest
  137. while closest < index(before: endIndex)
  138. {
  139. formIndex(after: &closest)
  140. let value = self[closest]
  141. if value.x != closestXValue { break }
  142. if abs(value.y - yValue) <= abs(closestYValue - yValue)
  143. {
  144. closestYValue = yValue
  145. closestYIndex = closest
  146. }
  147. }
  148. closest = closestYIndex
  149. }
  150. return closest
  151. }
  152. }