GlucoseChartView.swift 3.1 KB

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