ForeCastChart.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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. forecastChart
  16. }
  17. private var forecastChart: some View {
  18. Chart {
  19. drawGlucose()
  20. drawCurrentTimeMarker()
  21. drawForecasts()
  22. }
  23. .chartXAxis { forecastChartXAxis }
  24. .chartXScale(domain: startMarker ... endMarker)
  25. .chartYAxis { forecastChartYAxis }
  26. }
  27. private func drawGlucose() -> some ChartContent {
  28. ForEach(state.glucoseFromPersistence) { item in
  29. if item.glucose > Int(state.highGlucose) {
  30. PointMark(
  31. x: .value("Time", item.date ?? Date(), unit: .second),
  32. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  33. )
  34. .foregroundStyle(Color.orange.gradient)
  35. .symbolSize(20)
  36. } else if item.glucose < Int(state.lowGlucose) {
  37. PointMark(
  38. x: .value("Time", item.date ?? Date(), unit: .second),
  39. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  40. )
  41. .foregroundStyle(Color.red.gradient)
  42. .symbolSize(20)
  43. } else {
  44. PointMark(
  45. x: .value("Time", item.date ?? Date(), unit: .second),
  46. y: .value("Value", Decimal(item.glucose) * conversionFactor)
  47. )
  48. .foregroundStyle(Color.green.gradient)
  49. .symbolSize(20)
  50. }
  51. }
  52. }
  53. private func drawForecasts() -> some ChartContent {
  54. ForEach(state.preprocessedData, id: \.id) { tuple in
  55. let forecastValue = tuple.forecastValue
  56. let forecast = tuple.forecast
  57. let valueAsDecimal = Decimal(forecastValue.value)
  58. let displayValue = units == .mmolL ? valueAsDecimal.asMmolL : valueAsDecimal
  59. LineMark(
  60. x: .value("Time", timeForIndex(forecastValue.index)),
  61. y: .value("Value", displayValue)
  62. )
  63. .foregroundStyle(by: .value("Predictions", forecast.type ?? ""))
  64. }
  65. }
  66. private func timeForIndex(_ index: Int32) -> Date {
  67. let currentTime = Date()
  68. let timeInterval = TimeInterval(index * 300)
  69. return currentTime.addingTimeInterval(timeInterval)
  70. }
  71. private func drawCurrentTimeMarker() -> some ChartContent {
  72. RuleMark(
  73. x: .value(
  74. "",
  75. Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
  76. unit: .second
  77. )
  78. ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
  79. }
  80. private var forecastChartXAxis: some AxisContent {
  81. AxisMarks(values: .stride(by: .hour, count: 2)) { _ in
  82. AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
  83. AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
  84. .font(.footnote)
  85. .foregroundStyle(Color.primary)
  86. }
  87. }
  88. private var forecastChartYAxis: some AxisContent {
  89. AxisMarks(position: .trailing) { _ in
  90. AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
  91. AxisTick(length: 3, stroke: .init(lineWidth: 3)).foregroundStyle(Color.secondary)
  92. AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
  93. }
  94. }
  95. }