MainChartView.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import Charts
  2. import CoreData
  3. import SwiftUI
  4. let screenSize: CGRect = UIScreen.main.bounds
  5. let calendar = Calendar.current
  6. struct MainChartView: View {
  7. var geo: GeometryProxy
  8. var safeAreaSize: CGFloat
  9. var units: GlucoseUnits
  10. var hours: Int
  11. var tempTargets: [TempTarget]
  12. var highGlucose: Decimal
  13. var lowGlucose: Decimal
  14. var currentGlucoseTarget: Decimal
  15. var glucoseColorScheme: GlucoseColorScheme
  16. var screenHours: Int16
  17. var displayXgridLines: Bool
  18. var displayYgridLines: Bool
  19. var thresholdLines: Bool
  20. var state: Home.StateModel
  21. @State var basalProfiles: [BasalProfile] = []
  22. @State var preparedTempBasals: [(start: Date, end: Date, rate: Double)] = []
  23. @State var startMarker =
  24. Date(timeIntervalSinceNow: TimeInterval(hours: -24))
  25. @State var endMarker = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
  26. @State var selection: Date? = nil
  27. @State var mainChartHasInitialized = false
  28. let now = Date.now
  29. private let context = CoreDataStack.shared.persistentContainer.viewContext
  30. @Environment(\.colorScheme) var colorScheme
  31. @Environment(\.calendar) var calendar
  32. var upperLimit: Decimal {
  33. units == .mgdL ? 400 : 22.2
  34. }
  35. private var selectedGlucose: GlucoseStored? {
  36. guard let selection = selection else { return nil }
  37. let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
  38. return state.glucoseFromPersistence.first { $0.date.map(range.contains) ?? false }
  39. }
  40. private func findDetermination(in range: ClosedRange<Date>) -> OrefDetermination? {
  41. state.enactedAndNonEnactedDeterminations.first {
  42. $0.deliverAt ?? now >= range.lowerBound && $0.deliverAt ?? now <= range.upperBound
  43. }
  44. }
  45. var selectedCOBValue: OrefDetermination? {
  46. guard let selection = selection else { return nil }
  47. let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
  48. return findDetermination(in: range)
  49. }
  50. var selectedIOBValue: OrefDetermination? {
  51. guard let selection = selection else { return nil }
  52. let range = selection.addingTimeInterval(-150) ... selection.addingTimeInterval(150)
  53. return findDetermination(in: range)
  54. }
  55. var body: some View {
  56. VStack {
  57. ZStack {
  58. VStack(spacing: 5) {
  59. dummyBasalChart
  60. staticYAxisChart
  61. Spacer()
  62. dummyCobChart
  63. }
  64. ScrollViewReader { scroller in
  65. ScrollView(.horizontal, showsIndicators: false) {
  66. VStack(spacing: 5) {
  67. basalChart
  68. mainChart
  69. Spacer()
  70. cobIobChart
  71. }.onChange(of: screenHours) {
  72. scroller.scrollTo("MainChart", anchor: .trailing)
  73. }
  74. .onChange(of: state.glucoseFromPersistence.last?.glucose) {
  75. scroller.scrollTo("MainChart", anchor: .trailing)
  76. updateStartEndMarkers()
  77. }
  78. .onChange(of: state.enactedAndNonEnactedDeterminations.first?.deliverAt) {
  79. scroller.scrollTo("MainChart", anchor: .trailing)
  80. }
  81. .onChange(of: units) {
  82. // TODO: - Refactor this to only update the Y Axis Scale
  83. state.setupGlucoseArray()
  84. }
  85. .onAppear {
  86. if !mainChartHasInitialized {
  87. scroller.scrollTo("MainChart", anchor: .trailing)
  88. updateStartEndMarkers()
  89. calculateTempBasalsInBackground()
  90. mainChartHasInitialized = true
  91. }
  92. }
  93. }
  94. }
  95. }
  96. }
  97. }
  98. }
  99. // MARK: - Main Chart with selection Popover
  100. extension MainChartView {
  101. private var mainChart: some View {
  102. VStack {
  103. Chart {
  104. drawStartRuleMark()
  105. drawEndRuleMark()
  106. drawCurrentTimeMarker()
  107. OverrideView(
  108. state: state,
  109. overrides: state.overrides,
  110. overrideRunStored: state.overrideRunStored,
  111. units: state.units,
  112. viewContext: context
  113. )
  114. TempTargetView(
  115. tempTargetStored: state.tempTargetStored,
  116. tempTargetRunStored: state.tempTargetRunStored,
  117. units: state.units,
  118. viewContext: context
  119. )
  120. GlucoseChartView(
  121. glucoseData: state.glucoseFromPersistence,
  122. units: state.units,
  123. highGlucose: state.highGlucose,
  124. lowGlucose: state.lowGlucose,
  125. currentGlucoseTarget: state.currentGlucoseTarget,
  126. isSmoothingEnabled: state.isSmoothingEnabled,
  127. glucoseColorScheme: state.glucoseColorScheme
  128. )
  129. InsulinView(
  130. glucoseData: state.glucoseFromPersistence,
  131. insulinData: state.insulinFromPersistence,
  132. units: state.units
  133. )
  134. CarbView(
  135. glucoseData: state.glucoseFromPersistence,
  136. units: state.units,
  137. carbData: state.carbsFromPersistence,
  138. fpuData: state.fpusFromPersistence,
  139. minValue: state.minYAxisValue
  140. )
  141. ForecastView(
  142. preprocessedData: state.preprocessedData,
  143. minForecast: state.minForecast,
  144. maxForecast: state.maxForecast,
  145. units: state.units,
  146. maxValue: state.maxYAxisValue,
  147. forecastDisplayType: state.forecastDisplayType
  148. )
  149. /// show glucose value when hovering over it
  150. if let selectedGlucose {
  151. SelectionPopoverView(
  152. selectedGlucose: selectedGlucose,
  153. selectedIOBValue: selectedIOBValue,
  154. selectedCOBValue: selectedCOBValue,
  155. units: units,
  156. highGlucose: highGlucose,
  157. lowGlucose: lowGlucose,
  158. currentGlucoseTarget: currentGlucoseTarget,
  159. glucoseColorScheme: glucoseColorScheme
  160. )
  161. }
  162. }
  163. .id("MainChart")
  164. .onChange(of: state.insulinFromPersistence) {
  165. state.roundedTotalBolus = state.calculateTINS()
  166. }
  167. .frame(
  168. minHeight: geo.size.height * (0.28 - safeAreaSize)
  169. )
  170. .frame(width: fullWidth(viewWidth: screenSize.width))
  171. .chartXScale(domain: startMarker ... endMarker)
  172. .chartXAxis { mainChartXAxis }
  173. .chartYAxis { mainChartYAxis }
  174. .chartYAxis(.hidden)
  175. .chartXSelection(value: $selection)
  176. .chartYScale(
  177. domain: units == .mgdL ? state.minYAxisValue ... state.maxYAxisValue : state.minYAxisValue
  178. .asMmolL ... state.maxYAxisValue.asMmolL
  179. )
  180. .backport.chartForegroundStyleScale(state: state)
  181. }
  182. }
  183. }