MainChartView.swift 7.8 KB

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