SectorChart.swift 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  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 = 200
  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(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. .frame(height: Constants.chartHeight)
  31. // Legend
  32. VStack(spacing: Constants.spacing) {
  33. ForEach(timeInRangeData, id: \.string) { data in
  34. HStack(spacing: Constants.spacing) {
  35. Circle()
  36. .fill(data.color)
  37. .frame(width: 12, height: 12)
  38. Text(data.string)
  39. .font(.subheadline)
  40. Spacer()
  41. Text(formatPercentage(data.decimal))
  42. .font(.subheadline)
  43. .bold()
  44. }
  45. }
  46. }
  47. .padding(.top, Constants.spacing)
  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) }
  55. let hyperReadings = hyperArray.count
  56. let hyperPercentage = Decimal(hyperReadings) / Decimal(total) * 100
  57. let hypoArray = glucose.filter { $0.glucose <= Int(lowLimit) }
  58. let hypoReadings = hypoArray.count
  59. let hypoPercentage = Decimal(hypoReadings) / Decimal(total) * 100
  60. let normalPercentage = 100 - (hypoPercentage + hyperPercentage)
  61. return [
  62. (normalPercentage, "In Range", .green),
  63. (hyperPercentage, "High", .yellow),
  64. (hypoPercentage, "Low", .red)
  65. ]
  66. }
  67. private func formatPercentage(_ value: Decimal) -> String {
  68. let formatter = NumberFormatter()
  69. formatter.numberStyle = .percent
  70. formatter.maximumFractionDigits = 1
  71. return formatter.string(from: NSDecimalNumber(decimal: value / 100)) ?? "0%"
  72. }
  73. }