GlucoseTargetsView.swift 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import Charts
  2. import Foundation
  3. import SwiftUI
  4. struct GlucoseTargetsView: ChartContent {
  5. let startMarker: Date
  6. let endMarker: Date
  7. let units: GlucoseUnits
  8. let bgTargets: BGTargets
  9. var body: some ChartContent {
  10. drawGlucoseTargets()
  11. }
  12. /**
  13. Draws glucose target ranges on the chart
  14. - Returns: A ChartContent containing line marks representing target glucose ranges
  15. The function:
  16. - Creates target profiles for two consecutive days
  17. - Converts values between mg/dL and mmol/L based on user settings
  18. - Draws green lines to visualize the target ranges
  19. */
  20. private func drawGlucoseTargets() -> some ChartContent {
  21. // Array to store target profiles for visualization
  22. var targetProfiles: [TargetProfile] = []
  23. let targets = bgTargets.targets
  24. // Generate profiles for today and tomorrow, because otherwise the targets would be cut off at midnight
  25. // TODO: maybe theres a better solution than introducing a second for loop?
  26. let days = [0, 1]
  27. for dayOffset in days {
  28. // Calculate base date for current day offset
  29. // it should be the start of the day of the startMarker
  30. let baseDate = Calendar.current.startOfDay(for: startMarker)
  31. .addingTimeInterval(TimeInterval(dayOffset * 24 * 60 * 60))
  32. for (index, target) in targets.enumerated() {
  33. // Calculate start time by adding target offset
  34. let startTime = baseDate.addingTimeInterval(TimeInterval(target.offset * 60))
  35. // Calculate end time - either next target or end of day
  36. let endTime: Date = {
  37. if index + 1 < targets.count {
  38. return baseDate.addingTimeInterval(TimeInterval(targets[index + 1].offset * 60))
  39. } else {
  40. return baseDate.addingTimeInterval(24 * 60 * 60)
  41. }
  42. }()
  43. // append target profile to array
  44. targetProfiles.append(
  45. TargetProfile(
  46. value: units == .mgdL ? target.low : target.low.asMmolL,
  47. startTime: startTime.timeIntervalSinceReferenceDate,
  48. endTime: endTime.timeIntervalSinceReferenceDate
  49. )
  50. )
  51. }
  52. }
  53. // Draw target lines for each profile
  54. return ForEach(targetProfiles, id: \.self) { profile in
  55. LineMark(
  56. x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.startTime)),
  57. y: .value("Target", profile.value)
  58. )
  59. .lineStyle(.init(lineWidth: 0.5))
  60. .foregroundStyle(Color.green.opacity(0.8))
  61. LineMark(
  62. x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.endTime)),
  63. y: .value("Target", profile.value)
  64. )
  65. .lineStyle(.init(lineWidth: 0.5))
  66. .foregroundStyle(Color.green.opacity(0.8))
  67. }
  68. }
  69. }
  70. struct TargetProfile: Hashable {
  71. let value: Decimal
  72. let startTime: TimeInterval
  73. let endTime: TimeInterval
  74. }
  75. private extension Date {
  76. var startOfDay: Date {
  77. Calendar.current.startOfDay(for: self)
  78. }
  79. }