SectorChart.swift 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import Charts
  2. import CoreData
  3. import SwiftDate
  4. import SwiftUI
  5. struct SectorChart: View {
  6. private enum Constants {
  7. static let chartHeight: CGFloat = 160
  8. static let spacing: CGFloat = 8
  9. static let labelSpacing: CGFloat = 4
  10. }
  11. let highLimit: Decimal
  12. let lowLimit: Decimal
  13. let units: GlucoseUnits
  14. let hbA1cDisplayUnit: HbA1cDisplayUnit
  15. let timeInRangeChartStyle: TimeInRangeChartStyle
  16. let glucose: [GlucoseStored]
  17. @Environment(\.colorScheme) var colorScheme
  18. var body: some View {
  19. HStack(alignment: .center, spacing: 20) {
  20. Chart {
  21. ForEach(timeInRangeData, id: \.string) { data in
  22. SectorMark(
  23. angle: .value("Percentage", data.decimal),
  24. innerRadius: .ratio(0.618), // Golden ratio
  25. angularInset: 1.5
  26. )
  27. .foregroundStyle(data.color.gradient)
  28. }
  29. }
  30. .padding(.vertical)
  31. .frame(height: Constants.chartHeight)
  32. // Legend
  33. VStack(spacing: Constants.spacing) {
  34. ForEach(timeInRangeData, id: \.string) { data in
  35. HStack(spacing: Constants.spacing) {
  36. Image(systemName: "circle.fill")
  37. .foregroundStyle(data.color)
  38. .font(.caption2)
  39. Text(data.string)
  40. .font(.footnote)
  41. Spacer()
  42. Text(formatPercentage(data.decimal))
  43. .font(.footnote)
  44. .bold()
  45. }
  46. }
  47. }
  48. }
  49. }
  50. // MARK: - Data Processing
  51. private var timeInRangeData: [(decimal: Decimal, string: String, color: Color)] {
  52. let total = glucose.count
  53. guard total > 0 else { return [] }
  54. let hyperArray = glucose.filter { $0.glucose > Int(highLimit) && $0.glucose <= 250 }
  55. let hyperPercentage = Decimal(hyperArray.count) / Decimal(total) * 100
  56. let severeHyperArray = glucose.filter { $0.glucose > 250 }
  57. let severeHyperPercentage = Decimal(severeHyperArray.count) / Decimal(total) * 100
  58. let hypoArray = glucose.filter { $0.glucose < Int(lowLimit) && $0.glucose > 54 }
  59. let hypoPercentage = Decimal(hypoArray.count) / Decimal(total) * 100
  60. let severeHypoArray = glucose.filter { $0.glucose <= 54 }
  61. let severeHypoPercentage = Decimal(severeHypoArray.count) / Decimal(total) * 100
  62. let normalPercentage = 100 - (hypoPercentage + severeHypoPercentage + severeHyperPercentage + hyperPercentage)
  63. let timeInTighterRangeArray = glucose.filter { $0.glucose >= Int(lowLimit) && $0.glucose <= 140 }
  64. let timeInTighterRangePercentage = Decimal(timeInTighterRangeArray.count) / Decimal(total) * 100
  65. return [
  66. (severeHyperPercentage, "Very High", .orange),
  67. (hyperPercentage, "High", .orange.opacity(0.6)),
  68. (normalPercentage, "In Range", .green.opacity(0.6)),
  69. (timeInTighterRangePercentage, "Tight Range", .green),
  70. (hypoPercentage, "Low", .red.opacity(0.6)),
  71. (severeHypoPercentage, "Very Low", .red)
  72. ]
  73. }
  74. private func formatPercentage(_ value: Decimal) -> String {
  75. let formatter = NumberFormatter()
  76. formatter.numberStyle = .percent
  77. formatter.maximumFractionDigits = 1
  78. return formatter.string(from: NSDecimalNumber(decimal: value / 100)) ?? "0%"
  79. }
  80. }