ForecastView.swift 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import Charts
  2. import Foundation
  3. import SwiftUI
  4. struct ForecastView: ChartContent {
  5. let preprocessedData: [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)]
  6. let minForecast: [Int]
  7. let maxForecast: [Int]
  8. let units: GlucoseUnits
  9. let maxValue: Decimal
  10. let forecastDisplayType: ForecastDisplayType
  11. var body: some ChartContent {
  12. if forecastDisplayType == .lines {
  13. drawForecastsLines()
  14. } else {
  15. drawForecastsCone()
  16. }
  17. }
  18. private func timeForIndex(_ index: Int32) -> Date {
  19. let currentTime = Date()
  20. let timeInterval = TimeInterval(index * 300)
  21. return currentTime.addingTimeInterval(timeInterval)
  22. }
  23. private func drawForecastsCone() -> some ChartContent {
  24. // Draw AreaMark for the forecast bounds
  25. ForEach(0 ..< max(minForecast.count, maxForecast.count), id: \.self) { index in
  26. if index < minForecast.count, index < maxForecast.count {
  27. let yMinMaxDelta = Decimal(minForecast[index] - maxForecast[index])
  28. let xValue = timeForIndex(Int32(index))
  29. // if distance between respective min and max is 0, provide a default range
  30. if yMinMaxDelta == 0 {
  31. let yMinValue = units == .mgdL ? Decimal(minForecast[index] - 1) :
  32. Decimal(minForecast[index] - 1)
  33. .asMmolL
  34. let yMaxValue = units == .mgdL ? Decimal(minForecast[index] + 1) :
  35. Decimal(minForecast[index] + 1)
  36. .asMmolL
  37. if xValue <= Date(timeIntervalSinceNow: TimeInterval(hours: 2.5)) {
  38. AreaMark(
  39. x: .value("Time", xValue),
  40. // maxValue is already parsed to user units, no need to parse
  41. yStart: .value("Min Value", yMinValue <= maxValue ? yMinValue : maxValue),
  42. yEnd: .value("Max Value", yMaxValue <= maxValue ? yMaxValue : maxValue)
  43. )
  44. .foregroundStyle(Color.blue.opacity(0.5))
  45. .interpolationMethod(.catmullRom)
  46. }
  47. } else {
  48. let yMinValue = units == .mgdL ? Decimal(minForecast[index]) : Decimal(minForecast[index])
  49. .asMmolL
  50. let yMaxValue = units == .mgdL ? Decimal(maxForecast[index]) : Decimal(maxForecast[index])
  51. .asMmolL
  52. if xValue <= Date(timeIntervalSinceNow: TimeInterval(hours: 2.5)) {
  53. AreaMark(
  54. x: .value("Time", xValue),
  55. // maxValue is already parsed to user units, no need to parse
  56. yStart: .value("Min Value", yMinValue <= maxValue ? yMinValue : maxValue),
  57. yEnd: .value("Max Value", yMaxValue <= maxValue ? yMaxValue : maxValue)
  58. )
  59. .foregroundStyle(Color.blue.opacity(0.5))
  60. .interpolationMethod(.catmullRom)
  61. }
  62. }
  63. }
  64. }
  65. }
  66. private func drawForecastsLines() -> some ChartContent {
  67. ForEach(preprocessedData, id: \.id) { tuple in
  68. let forecastValue = tuple.forecastValue
  69. let forecast = tuple.forecast
  70. let valueAsDecimal = Decimal(forecastValue.value)
  71. let displayValue = units == .mmolL ? valueAsDecimal.asMmolL : valueAsDecimal
  72. let xValue = timeForIndex(forecastValue.index)
  73. if xValue <= Date(timeIntervalSinceNow: TimeInterval(hours: 2.5)) {
  74. LineMark(
  75. x: .value("Time", xValue),
  76. y: .value("Value", displayValue)
  77. )
  78. .foregroundStyle(by: .value("Predictions", forecast.type ?? ""))
  79. }
  80. }
  81. }
  82. }