ForeCastChart.swift 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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. Text("\(units.rawValue)")
  36. .font(.footnote)
  37. .foregroundStyle(.secondary)
  38. }
  39. }
  40. }
  41. }
  42. private var forecastChart: some View {
  43. Chart {
  44. drawGlucose()
  45. drawCurrentTimeMarker()
  46. drawForecastArea()
  47. }
  48. .chartXAxis { forecastChartXAxis }
  49. .chartXScale(domain: startMarker ... endMarker)
  50. .chartYAxis { forecastChartYAxis }
  51. .chartYScale(domain: 0 ... 300 * conversionFactor)
  52. }
  53. private func drawGlucose() -> some ChartContent {
  54. ForEach(state.glucoseFromPersistence) { item in
  55. if item.glucose > Int(state.highGlucose) {
  56. PointMark(
  57. x: .value("Time", item.date ?? Date(), unit: .second),
  58. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  59. )
  60. .foregroundStyle(Color.orange.gradient)
  61. .symbolSize(20)
  62. } else if item.glucose < Int(state.lowGlucose) {
  63. PointMark(
  64. x: .value("Time", item.date ?? Date(), unit: .second),
  65. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  66. )
  67. .foregroundStyle(Color.red.gradient)
  68. .symbolSize(20)
  69. } else {
  70. PointMark(
  71. x: .value("Time", item.date ?? Date(), unit: .second),
  72. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  73. )
  74. .foregroundStyle(Color.green.gradient)
  75. .symbolSize(20)
  76. }
  77. }
  78. }
  79. private func timeForIndex(_ index: Int32) -> Date {
  80. let currentTime = Date()
  81. let timeInterval = TimeInterval(index * 300)
  82. return currentTime.addingTimeInterval(timeInterval)
  83. }
  84. private func drawForecastArea() -> some ChartContent {
  85. ForEach(state.minForecast.indices, id: \.self) { index in
  86. AreaMark(
  87. x: .value("Time", timeForIndex(Int32(index))),
  88. yStart: .value("Min Value", Decimal(state.minForecast[index]) * conversionFactor),
  89. yEnd: .value("Max Value", Decimal(state.maxForecast[index]) * conversionFactor)
  90. )
  91. .foregroundStyle(Color.blue.opacity(0.5))
  92. .interpolationMethod(.catmullRom)
  93. }
  94. }
  95. private func drawCurrentTimeMarker() -> some ChartContent {
  96. RuleMark(
  97. x: .value(
  98. "",
  99. Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
  100. unit: .second
  101. )
  102. ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
  103. }
  104. private var forecastChartXAxis: some AxisContent {
  105. AxisMarks(values: .stride(by: .hour, count: 2)) { _ in
  106. AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
  107. AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
  108. .font(.footnote)
  109. .foregroundStyle(Color.primary)
  110. }
  111. }
  112. private var forecastChartYAxis: some AxisContent {
  113. AxisMarks(position: .trailing) { _ in
  114. AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
  115. AxisTick(length: 3, stroke: .init(lineWidth: 3)).foregroundStyle(Color.secondary)
  116. AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
  117. }
  118. }
  119. }