ForeCastChart.swift 4.8 KB

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