GlucoseChartView.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import Charts
  2. import Foundation
  3. import SwiftUI
  4. // MARK: - Current Glucose View
  5. struct GlucoseChartView: View {
  6. let glucoseValues: [(date: Date, glucose: Double, color: Color)]
  7. @Binding var minYAxisValue: Decimal
  8. @Binding var maxYAxisValue: Decimal
  9. @State private var timeWindow: TimeWindow = .threeHours
  10. enum TimeWindow: Int {
  11. case threeHours = 3
  12. case sixHours = 6
  13. case twelveHours = 12
  14. case twentyFourHours = 24
  15. var next: TimeWindow {
  16. switch self {
  17. case .threeHours: return .sixHours
  18. case .sixHours: return .twelveHours
  19. case .twelveHours: return .twentyFourHours
  20. case .twentyFourHours: return .threeHours
  21. }
  22. }
  23. }
  24. // TODO: should we only change the x axis here like we do in the main chart instead of filtering the values?
  25. private var filteredValues: [(date: Date, glucose: Double, color: Color)] {
  26. let cutoffDate = Date().addingTimeInterval(-Double(timeWindow.rawValue) * 3600)
  27. return glucoseValues.filter { $0.date > cutoffDate }
  28. }
  29. var glucosePointSize: CGFloat {
  30. switch timeWindow {
  31. case .threeHours: return 18
  32. case .sixHours: return 14
  33. case .twelveHours: return 10
  34. case .twentyFourHours: return 6
  35. }
  36. }
  37. var body: some View {
  38. VStack(spacing: 8) {
  39. if filteredValues.isEmpty {
  40. Text("No glucose readings.").font(.headline)
  41. Text("Check phone and CGM connectivity.").font(.caption)
  42. } else {
  43. Chart {
  44. ForEach(filteredValues, id: \.date) { reading in
  45. PointMark(
  46. x: .value("Time", reading.date),
  47. y: .value("Glucose", reading.glucose)
  48. )
  49. .foregroundStyle(reading.color)
  50. .symbolSize(glucosePointSize)
  51. }
  52. }
  53. .chartXAxis(.hidden)
  54. .chartYAxisLabel("\(timeWindow.rawValue) h", alignment: .topLeading)
  55. .chartYAxis {
  56. AxisMarks(position: .trailing) { value in
  57. AxisGridLine(stroke: .init(lineWidth: 0.65, dash: [2, 3]))
  58. .foregroundStyle(Color.white.opacity(0.25))
  59. AxisValueLabel {
  60. if let glucose = value.as(Double.self) {
  61. Text("\(Int(glucose))")
  62. }
  63. }
  64. }
  65. }
  66. .chartYScale(
  67. domain: minYAxisValue ... maxYAxisValue
  68. )
  69. .chartPlotStyle { plotContent in
  70. plotContent
  71. .background(
  72. RoundedRectangle(cornerRadius: 12)
  73. .fill(Color.clear)
  74. )
  75. .clipShape(RoundedRectangle(cornerRadius: 12))
  76. }
  77. .padding(.bottom)
  78. }
  79. }
  80. .scenePadding()
  81. .onTapGesture {
  82. withAnimation {
  83. timeWindow = timeWindow.next
  84. }
  85. }
  86. }
  87. }