GRIView.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // LoopFollow
  2. // GRIView.swift
  3. import SwiftUI
  4. struct GRIView: View {
  5. @ObservedObject var viewModel: GRIViewModel
  6. private let legendColumns = [GridItem(.adaptive(minimum: 90), spacing: 12, alignment: .leading)]
  7. var body: some View {
  8. VStack(alignment: .leading, spacing: 8) {
  9. HStack {
  10. Text("GRI (Glucose Risk Index)")
  11. .font(.caption)
  12. .foregroundColor(.secondary)
  13. Spacer()
  14. if let gri = viewModel.gri {
  15. VStack(alignment: .trailing, spacing: 2) {
  16. Text(String(format: "%.0f", gri))
  17. .font(.title2)
  18. .fontWeight(.semibold)
  19. .foregroundColor(griColor(gri))
  20. Text(griZone(gri))
  21. .font(.caption2)
  22. .foregroundColor(.secondary)
  23. }
  24. } else {
  25. Text("---")
  26. .font(.title2)
  27. .fontWeight(.semibold)
  28. .foregroundColor(.purple)
  29. }
  30. }
  31. if let hypo = viewModel.griHypoComponent, let hyper = viewModel.griHyperComponent {
  32. GRIRiskGridView(
  33. hypoComponent: hypo,
  34. hyperComponent: hyper,
  35. gri: viewModel.gri ?? 0
  36. )
  37. .frame(height: 250)
  38. .allowsHitTesting(false)
  39. .clipped()
  40. HStack {
  41. Text("Hypoglycemia Component (%)")
  42. .font(.caption2)
  43. .foregroundColor(.secondary)
  44. Spacer()
  45. Text("Hyperglycemia Component (%)")
  46. .font(.caption2)
  47. .foregroundColor(.secondary)
  48. }
  49. LazyVGrid(columns: legendColumns, alignment: .leading, spacing: 8) {
  50. ZoneLegendItem(color: .green, label: "A (0-20)")
  51. ZoneLegendItem(color: .yellow, label: "B (21-40)")
  52. ZoneLegendItem(color: .orange, label: "C (41-60)")
  53. ZoneLegendItem(color: .red, label: "D (61-80)")
  54. ZoneLegendItem(color: .red.opacity(0.8), label: "E (81-100)")
  55. }
  56. .font(.caption2)
  57. } else {
  58. Text("No data available")
  59. .font(.caption)
  60. .foregroundColor(.secondary)
  61. .frame(height: 250)
  62. }
  63. }
  64. .frame(maxWidth: .infinity, alignment: .leading)
  65. .padding()
  66. .background(Color(.systemGray6))
  67. .cornerRadius(12)
  68. }
  69. private func griColor(_ gri: Double) -> Color {
  70. if gri <= 20 {
  71. return .green
  72. } else if gri <= 40 {
  73. return .yellow
  74. } else if gri <= 60 {
  75. return .orange
  76. } else if gri <= 80 {
  77. return .red
  78. } else {
  79. return .red.opacity(0.8)
  80. }
  81. }
  82. private func griZone(_ gri: Double) -> String {
  83. if gri <= 20 {
  84. return "Zone A"
  85. } else if gri <= 40 {
  86. return "Zone B"
  87. } else if gri <= 60 {
  88. return "Zone C"
  89. } else if gri <= 80 {
  90. return "Zone D"
  91. } else {
  92. return "Zone E"
  93. }
  94. }
  95. }
  96. struct ZoneLegendItem: View {
  97. let color: Color
  98. let label: String
  99. var body: some View {
  100. HStack(spacing: 4) {
  101. Rectangle()
  102. .fill(color.opacity(0.3))
  103. .frame(width: 12, height: 12)
  104. Text(label)
  105. .foregroundColor(.secondary)
  106. .lineLimit(1)
  107. }
  108. }
  109. }