MainChartView2.swift 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import Charts
  2. import SwiftUI
  3. struct MainChartView2: View {
  4. @Binding var tempBasals: [PumpHistoryEvent]
  5. @Binding var glucose: [BloodGlucose]
  6. @Binding var screenHours: Int16
  7. @Binding var highGlucose: Decimal
  8. @Binding var lowGlucose: Decimal
  9. @Binding var carbs: [CarbsEntry]
  10. @Binding var basalProfile: [BasalProfileEntry]
  11. var body: some View {
  12. VStack(alignment: .center, spacing: 8, content: {
  13. ZStack {
  14. GlucoseChart(glucose: $glucose, screenHours: $screenHours, highGlucose: $highGlucose, lowGlucose: $lowGlucose)
  15. VStack {
  16. Spacer()
  17. .frame(height: 280)
  18. CarbsChart(carbs: $carbs, screenHours: $screenHours)
  19. }
  20. }
  21. // GlucoseChart(glucose: $glucose, screenHours: $screenHours, highGlucose: $highGlucose, lowGlucose: $lowGlucose)
  22. // .padding(.bottom, 20)
  23. // CarbsChart(carbs: $carbs, screenHours: $screenHours)
  24. // .padding(.bottom, 8)
  25. BasalChart(tempBasals: $tempBasals, screenHours: $screenHours)
  26. .padding(.bottom, 8)
  27. // ZStack {
  28. // BasalChart(tempBasals: $tempBasals, basalProfile: $basalProfile, screenHours: $screenHours)
  29. // BasalProfileChart(basalProfile: $basalProfile)
  30. // }
  31. Legend()
  32. })
  33. }
  34. }
  35. // MARK: GLUCOSE FOR CHART
  36. struct GlucoseChart: View {
  37. @Binding var glucose: [BloodGlucose]
  38. @Binding var screenHours: Int16
  39. @Binding var highGlucose: Decimal
  40. @Binding var lowGlucose: Decimal
  41. var body: some View {
  42. VStack {
  43. let filteredGlucose: [BloodGlucose] = filterGlucoseData(for: screenHours)
  44. Chart(filteredGlucose) {
  45. RuleMark(y: .value("High", highGlucose))
  46. .foregroundStyle(Color.loopYellow)
  47. .lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
  48. RuleMark(y: .value("Low", lowGlucose))
  49. .foregroundStyle(Color.loopRed)
  50. .lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
  51. // MARK: TO DO -> at the moment this rule mark is not visible because the chart is not scrollable
  52. if let currentTime = getCurrentTime() {
  53. RuleMark(x: .value("Current Time", currentTime))
  54. .foregroundStyle(Color.gray)
  55. .lineStyle(StrokeStyle(lineWidth: 1, dash: [5]))
  56. }
  57. PointMark(
  58. x: .value("Time", $0.dateString),
  59. y: .value("Value", $0.value)
  60. )
  61. .foregroundStyle(
  62. $0.value > Double(highGlucose) ? Color.yellow.gradient :
  63. $0.value < Double(lowGlucose) ? Color.red.gradient : Color.green.gradient
  64. )
  65. .symbolSize(12)
  66. }
  67. .frame(height: 250)
  68. .chartXAxis(.hidden)
  69. }
  70. }
  71. private func filterGlucoseData(for hours: Int16) -> [BloodGlucose] {
  72. guard hours > 0 else {
  73. return glucose
  74. }
  75. let currentDate = Date()
  76. let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
  77. return glucose.filter { $0.dateString >= startDate }
  78. }
  79. private func getCurrentTime() -> String? {
  80. let dateFormatter = DateFormatter()
  81. dateFormatter.dateFormat = "HH:mm"
  82. return dateFormatter.string(from: Date())
  83. }
  84. }
  85. // MARK: BASAL FOR CHART
  86. struct BasalChart: View {
  87. @Binding var tempBasals: [PumpHistoryEvent]
  88. @Binding var screenHours: Int16
  89. var body: some View {
  90. VStack {
  91. let filteredBasal: [PumpHistoryEvent] = filterBasalData(for: screenHours)
  92. Chart(filteredBasal) {
  93. BarMark(
  94. x: .value("Time", $0.timestamp),
  95. y: .value("Value", $0.rate ?? 0)
  96. )
  97. .foregroundStyle(Color.blue.gradient)
  98. .cornerRadius(0)
  99. }
  100. .frame(height: 80)
  101. // .rotationEffect(.degrees(180))
  102. // .chartXAxis(.hidden)
  103. .chartYAxis(.hidden)
  104. .chartPlotStyle { plotArea in
  105. plotArea.background(.blue.gradient.opacity(0.1))
  106. }
  107. }
  108. }
  109. private func filterBasalData(for hours: Int16) -> [PumpHistoryEvent] {
  110. guard hours > 0 else {
  111. return tempBasals
  112. }
  113. let currentDate = Date()
  114. let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
  115. return tempBasals.filter { $0.timestamp >= startDate }
  116. }
  117. }
  118. struct BasalProfileChart: View {
  119. @Binding var basalProfile: [BasalProfileEntry]
  120. @Binding var screenHours: Int16
  121. var body: some View {
  122. let filteredBasalProfile: [BasalProfileEntry] = filterBasalProfileData(for: screenHours)
  123. VStack {
  124. // MARK: DOES NOT WORK
  125. // filtering function seems not to work...displays nothing
  126. Chart(filteredBasalProfile) {
  127. LineMark(
  128. x: .value("start", $0.minutes),
  129. y: .value("rate", $0.rate)
  130. ).foregroundStyle(Color.blue.gradient)
  131. }
  132. }
  133. }
  134. private func filterBasalProfileData(for hours: Int16) -> [BasalProfileEntry] {
  135. guard hours > 0 else {
  136. return basalProfile
  137. }
  138. let currentDate = Date()
  139. let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
  140. return basalProfile.filter { entry in
  141. if let entryDate = dateFormatter.date(from: entry.start) {
  142. return entryDate >= startDate
  143. } else {
  144. return false
  145. }
  146. }
  147. }
  148. private let dateFormatter: DateFormatter = {
  149. let formatter = DateFormatter()
  150. formatter.dateFormat = "HH:mm:ss"
  151. return formatter
  152. }()
  153. }
  154. // MARK: COB
  155. struct CarbsChart: View {
  156. @Binding var carbs: [CarbsEntry]
  157. @Binding var screenHours: Int16
  158. static var triangle: BasicChartSymbolShape { .triangle }
  159. var body: some View {
  160. VStack {
  161. // MARK: DOES NOT WORK PROPERLY
  162. // scaling is not correct because of swift charts automatic scaling...
  163. let filteredCarbs: [CarbsEntry] = filterCarbData(for: screenHours)
  164. Chart(filteredCarbs) {
  165. PointMark(
  166. x: .value("Time", $0.createdAt),
  167. y: .value("Value", 10)
  168. )
  169. .foregroundStyle(Color.loopYellow.gradient)
  170. .symbolSize(50)
  171. .symbol(CarbsChart.triangle)
  172. }
  173. .frame(height: 80)
  174. .chartYAxis(.hidden)
  175. .chartXAxis(.hidden)
  176. }
  177. }
  178. private func filterCarbData(for hours: Int16) -> [CarbsEntry] {
  179. guard hours > 0 else {
  180. return carbs
  181. }
  182. let currentDate = Date()
  183. let startDate = Calendar.current.date(byAdding: .hour, value: -Int(hours), to: currentDate) ?? currentDate
  184. return carbs.filter { $0.createdAt >= startDate }
  185. }
  186. }
  187. // MARK: LEGEND PANEL FOR CHART
  188. struct Legend: View {
  189. var body: some View {
  190. HStack {
  191. Image(systemName: "line.diagonal")
  192. .rotationEffect(Angle(degrees: 45))
  193. .foregroundColor(.green)
  194. Text("BG")
  195. .foregroundColor(.secondary)
  196. Spacer()
  197. Image(systemName: "line.diagonal")
  198. .rotationEffect(Angle(degrees: 45))
  199. .foregroundColor(.insulin)
  200. Text("IOB")
  201. .foregroundColor(.secondary)
  202. Spacer()
  203. Image(systemName: "line.diagonal")
  204. .rotationEffect(Angle(degrees: 45))
  205. .foregroundColor(.purple)
  206. Text("ZT")
  207. .foregroundColor(.secondary)
  208. Spacer()
  209. Image(systemName: "line.diagonal")
  210. .frame(height: 10)
  211. .rotationEffect(Angle(degrees: 45))
  212. .foregroundColor(.loopYellow)
  213. Text("COB")
  214. .foregroundColor(.secondary)
  215. Spacer()
  216. Image(systemName: "line.diagonal")
  217. .rotationEffect(Angle(degrees: 45))
  218. .foregroundColor(.orange)
  219. Text("UAM")
  220. .foregroundColor(.secondary)
  221. }
  222. .font(.caption2)
  223. .padding(.horizontal, 40)
  224. .padding(.vertical, 1)
  225. }
  226. }