ForeCastChart.swift 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import Charts
  2. import CoreData
  3. import Foundation
  4. import SwiftUI
  5. struct ForeCastChart: View {
  6. @StateObject var state: Bolus.StateModel
  7. @Environment(\.colorScheme) var colorScheme
  8. @Binding var units: GlucoseUnits
  9. @State private var startMarker = Date(timeIntervalSinceNow: -4 * 60 * 60)
  10. @State private var endMarker = Date(timeIntervalSinceNow: 3 * 60 * 60)
  11. private var conversionFactor: Decimal {
  12. units == .mmolL ? 0.0555 : 1
  13. }
  14. var body: some View {
  15. VStack {
  16. forecastChart
  17. .padding(.vertical, 3)
  18. HStack {
  19. Spacer()
  20. Text("evBG").font(.footnote).foregroundStyle(.primary)
  21. Image(systemName: "arrow.right").font(.footnote).foregroundStyle(.secondary)
  22. if let eventualBG = state.simulatedDetermination?.eventualBG {
  23. HStack {
  24. Text("\(eventualBG)")
  25. .font(.footnote)
  26. .foregroundStyle(.primary)
  27. Text("\(units.rawValue)")
  28. .font(.footnote)
  29. .foregroundStyle(.secondary)
  30. }
  31. } else {
  32. Text("")
  33. .font(.footnote)
  34. .foregroundStyle(.primary)
  35. }
  36. }
  37. }
  38. }
  39. private var forecastChart: some View {
  40. Chart {
  41. drawGlucose()
  42. drawCurrentTimeMarker()
  43. // drawForecasts()
  44. drawForecastArea()
  45. }
  46. .chartXAxis { forecastChartXAxis }
  47. .chartXScale(domain: startMarker ... endMarker)
  48. .chartYAxis { forecastChartYAxis }
  49. .chartYScale(domain: 0 ... 300 * conversionFactor)
  50. }
  51. private func drawGlucose() -> some ChartContent {
  52. ForEach(state.glucoseFromPersistence) { item in
  53. if item.glucose > Int(state.highGlucose) {
  54. PointMark(
  55. x: .value("Time", item.date ?? Date(), unit: .second),
  56. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  57. )
  58. .foregroundStyle(Color.orange.gradient)
  59. .symbolSize(20)
  60. } else if item.glucose < Int(state.lowGlucose) {
  61. PointMark(
  62. x: .value("Time", item.date ?? Date(), unit: .second),
  63. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  64. )
  65. .foregroundStyle(Color.red.gradient)
  66. .symbolSize(20)
  67. } else {
  68. PointMark(
  69. x: .value("Time", item.date ?? Date(), unit: .second),
  70. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  71. )
  72. .foregroundStyle(Color.green.gradient)
  73. .symbolSize(20)
  74. }
  75. }
  76. }
  77. private func timeForIndex(_ index: Int32) -> Date {
  78. let currentTime = Date()
  79. let timeInterval = TimeInterval(index * 300)
  80. return currentTime.addingTimeInterval(timeInterval)
  81. }
  82. private func drawForecastArea() -> some ChartContent {
  83. ForEach(state.minForecast.indices, id: \.self) { index in
  84. AreaMark(
  85. x: .value("Time", timeForIndex(Int32(index))),
  86. yStart: .value("Min Value", Decimal(state.minForecast[index]) * conversionFactor),
  87. yEnd: .value("Max Value", Decimal(state.maxForecast[index]) * conversionFactor)
  88. )
  89. .foregroundStyle(Color.blue.opacity(0.3))
  90. }
  91. }
  92. // private func drawForecasts() -> some ChartContent {
  93. // let predictions = state.predictionsForChart
  94. //
  95. // let predictionData = [
  96. // ("IOB", predictions?.iob),
  97. // ("ZT", predictions?.zt),
  98. // ("COB", predictions?.cob),
  99. // ("UAM", predictions?.uam)
  100. // ]
  101. //
  102. // return ForEach(predictionData, id: \.0) { name, values in
  103. // if let values = values {
  104. // ForEach(values.indices, id: \.self) { index in
  105. // LineMark(
  106. // x: .value("Time", timeForIndex(Int32(index))),
  107. // y: .value("Value", Decimal(values[index]) * conversionFactor)
  108. // )
  109. // .foregroundStyle(by: .value("Prediction Type", name))
  110. // }
  111. // }
  112. // }
  113. // }
  114. private func drawCurrentTimeMarker() -> some ChartContent {
  115. RuleMark(
  116. x: .value(
  117. "",
  118. Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
  119. unit: .second
  120. )
  121. ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
  122. }
  123. private var forecastChartXAxis: some AxisContent {
  124. AxisMarks(values: .stride(by: .hour, count: 2)) { _ in
  125. AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
  126. AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
  127. .font(.footnote)
  128. .foregroundStyle(Color.primary)
  129. }
  130. }
  131. private var forecastChartYAxis: some AxisContent {
  132. AxisMarks(position: .trailing) { _ in
  133. AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
  134. AxisTick(length: 3, stroke: .init(lineWidth: 3)).foregroundStyle(Color.secondary)
  135. AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
  136. }
  137. }
  138. }