GlucoseChartView.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import Charts
  2. import Foundation
  3. import SwiftUI
  4. struct GlucoseChartView: ChartContent {
  5. let glucoseData: [GlucoseStored]
  6. let units: GlucoseUnits
  7. let highGlucose: Decimal
  8. let lowGlucose: Decimal
  9. let currentGlucoseTarget: Decimal
  10. let isSmoothingEnabled: Bool
  11. let glucoseColorScheme: GlucoseColorScheme
  12. var body: some ChartContent {
  13. drawGlucoseChart()
  14. }
  15. /// Dynamic point color for a CGM reading. Manual readings render red and skip this.
  16. private func pointColor(for item: GlucoseStored) -> Color {
  17. // TODO: workaround for now: set low value to 55, to have dynamic color shades between 55 and user-set low (approx. 70); same for high glucose
  18. let hardCodedLow = Decimal(55)
  19. let hardCodedHigh = Decimal(220)
  20. let isDynamicColorScheme = glucoseColorScheme == .dynamicColor
  21. return Trio.getDynamicGlucoseColor(
  22. glucoseValue: Decimal(item.glucose),
  23. highGlucoseColorValue: isDynamicColorScheme ? hardCodedHigh : highGlucose,
  24. lowGlucoseColorValue: isDynamicColorScheme ? hardCodedLow : lowGlucose,
  25. targetGlucose: currentGlucoseTarget,
  26. glucoseColorScheme: glucoseColorScheme
  27. )
  28. }
  29. private func drawGlucoseChart() -> some ChartContent {
  30. ForEach(glucoseData) { item in
  31. let glucoseToDisplay = units == .mgdL ? Decimal(item.glucose) : Decimal(item.glucose).asMmolL
  32. if item.isManual {
  33. PointMark(
  34. x: .value("Time", item.date ?? Date(), unit: .second),
  35. y: .value("Value", glucoseToDisplay)
  36. )
  37. .symbolSize(20)
  38. .symbol {
  39. Image(systemName: "drop.fill")
  40. .font(.caption2)
  41. .symbolRenderingMode(.monochrome)
  42. .bold()
  43. .foregroundStyle(.red)
  44. }
  45. } else {
  46. PointMark(
  47. x: .value("Time", item.date ?? Date(), unit: .second),
  48. y: .value("Value", glucoseToDisplay)
  49. )
  50. .foregroundStyle(pointColor(for: item))
  51. .symbolSize(20)
  52. .symbol(.circle)
  53. }
  54. if isSmoothingEnabled, let smoothedGlucose = item.smoothedGlucose, smoothedGlucose != 0 {
  55. let smoothedGlucoseForDisplay: Decimal = units == .mgdL ? smoothedGlucose.decimalValue : smoothedGlucose
  56. .decimalValue.asMmolL
  57. LineMark(
  58. x: .value("Time", item.date ?? Date(), unit: .second),
  59. y: .value("Value", smoothedGlucoseForDisplay),
  60. series: .value("Type", "Smoothed")
  61. )
  62. .foregroundStyle(Color.secondary)
  63. }
  64. }
  65. }
  66. }
  67. #Preview {
  68. struct PreviewWrapper: View {
  69. @State private var previewStack: CoreDataStack? = nil
  70. @State private var glucoseData: [GlucoseStored] = []
  71. @State private var isLoading = true
  72. var body: some View {
  73. NavigationView {
  74. Group {
  75. if isLoading {
  76. ProgressView("Loading data...")
  77. } else {
  78. VStack {
  79. Chart {
  80. GlucoseChartView(
  81. glucoseData: glucoseData,
  82. units: .mgdL,
  83. highGlucose: 180,
  84. lowGlucose: 70,
  85. currentGlucoseTarget: 100,
  86. isSmoothingEnabled: false,
  87. glucoseColorScheme: .dynamicColor
  88. )
  89. }
  90. .frame(height: 200)
  91. .padding()
  92. }
  93. }
  94. }
  95. .navigationTitle("Glucose Chart")
  96. .task {
  97. // Use the preview stack that's initialized asynchronously in CoreDataStack
  98. previewStack = try? await CoreDataStack.preview()
  99. // Now you can safely create preview data
  100. if let stack = previewStack {
  101. glucoseData = GlucoseStored.makePreviewGlucose(count: 24, provider: stack)
  102. isLoading = false
  103. }
  104. }
  105. }
  106. }
  107. }
  108. return PreviewWrapper()
  109. }