GlucoseDistributionChart.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import Charts
  2. import SwiftUI
  3. struct GlucoseDistributionChart: View {
  4. let glucose: [GlucoseStored]
  5. let highLimit: Decimal
  6. let lowLimit: Decimal
  7. let units: GlucoseUnits
  8. let glucoseRangeStats: [GlucoseRangeStats]
  9. var body: some View {
  10. VStack(alignment: .leading, spacing: 8) {
  11. Text("Glucose Distribution")
  12. .font(.headline)
  13. Chart(glucoseRangeStats) { range in
  14. ForEach(range.values, id: \.hour) { value in
  15. AreaMark(
  16. x: .value("Hour", Calendar.current.dateForChartHour(value.hour)),
  17. y: .value("Count", value.count),
  18. stacking: .normalized
  19. )
  20. .foregroundStyle(by: .value("Range", range.name))
  21. }
  22. }
  23. .chartForegroundStyleScale([
  24. "<54": .purple.opacity(0.7),
  25. "54-70": .red.opacity(0.7),
  26. "70-140": .green,
  27. "140-180": .green.opacity(0.7),
  28. "180-200": .yellow.opacity(0.7),
  29. "200-220": .orange.opacity(0.7),
  30. ">220": .orange.opacity(0.8)
  31. ])
  32. .chartLegend(position: .bottom, alignment: .leading, spacing: 12) {
  33. let legendItems: [(String, Color)] = [
  34. ("<\(units == .mgdL ? Decimal(54) : 54.asMmolL)", .purple.opacity(0.7)),
  35. (
  36. "\(units == .mgdL ? Decimal(54) : 54.asMmolL)-\(units == .mgdL ? Decimal(70) : 70.asMmolL)",
  37. .red.opacity(0.7)
  38. ),
  39. ("\(units == .mgdL ? Decimal(70) : 70.asMmolL)-\(units == .mgdL ? Decimal(140) : 140.asMmolL)", .green),
  40. (
  41. "\(units == .mgdL ? Decimal(140) : 140.asMmolL)-\(units == .mgdL ? Decimal(180) : 180.asMmolL)",
  42. .green.opacity(0.7)
  43. ),
  44. (
  45. "\(units == .mgdL ? Decimal(180) : 180.asMmolL)-\(units == .mgdL ? Decimal(200) : 200.asMmolL)",
  46. .yellow.opacity(0.7)
  47. ),
  48. (
  49. "\(units == .mgdL ? Decimal(200) : 200.asMmolL)-\(units == .mgdL ? Decimal(220) : 220.asMmolL)",
  50. .orange.opacity(0.7)
  51. ),
  52. (">\(units == .mgdL ? Decimal(220) : 220.asMmolL)", .orange.opacity(0.8))
  53. ]
  54. let columns = [GridItem(.adaptive(minimum: 65), spacing: 4)]
  55. LazyVGrid(columns: columns, alignment: .leading, spacing: 4) {
  56. ForEach(legendItems, id: \.0) { item in
  57. StatChartUtils.legendItem(label: item.0, color: item.1)
  58. }
  59. }
  60. }
  61. .chartYAxis {
  62. AxisMarks(position: .trailing) { value in
  63. if let percentage = value.as(Double.self) {
  64. AxisValueLabel {
  65. Text((percentage / 100).formatted(.percent.precision(.fractionLength(0))))
  66. .font(.footnote)
  67. }
  68. AxisGridLine()
  69. }
  70. }
  71. }
  72. .chartYAxisLabel(alignment: .trailing) {
  73. Text("Percentage")
  74. .foregroundStyle(.primary)
  75. .font(.footnote)
  76. .padding(.vertical, 3)
  77. }
  78. .chartXAxis {
  79. AxisMarks(values: .stride(by: .hour, count: 3)) { value in
  80. if let date = value.as(Date.self) {
  81. let hour = Calendar.current.component(.hour, from: date)
  82. switch hour {
  83. case 0,
  84. 12:
  85. AxisValueLabel(format: .dateTime.hour())
  86. default:
  87. AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .omitted)))
  88. }
  89. AxisGridLine()
  90. }
  91. }
  92. }
  93. .frame(height: 200)
  94. }
  95. }
  96. }