| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- import Charts
- import SwiftUI
- struct GlucoseAreaChart: View {
- let glucose: [GlucoseStored]
- let highLimit: Decimal
- let lowLimit: Decimal
- let isTodayOrLast24h: Bool
- let units: GlucoseUnits
- let hourlyStats: [HourlyStats]
- var body: some View {
- VStack(alignment: .leading, spacing: 8) {
- Text("Glucose Distribution")
- .font(.headline)
- .foregroundStyle(.primary)
- Chart {
- if isTodayOrLast24h {
- // Single day line chart
- ForEach(glucose.sorted(by: { ($0.date ?? Date()) < ($1.date ?? Date()) }), id: \.id) { reading in
- LineMark(
- x: .value("Time", reading.date ?? Date()),
- y: .value("Glucose", Double(reading.glucose))
- )
- .lineStyle(StrokeStyle(lineWidth: 2))
- .foregroundStyle(.blue)
- }
- } else {
- // Statistical view for longer periods
- // 10-90 percentile area
- ForEach(hourlyStats, id: \.hour) { stats in
- AreaMark(
- x: .value("Hour", Calendar.current.dateForChartHour(stats.hour)),
- yStart: .value("10th Percentile", stats.percentile10),
- yEnd: .value("90th Percentile", stats.percentile90),
- series: .value("10-90", "10-90")
- )
- .foregroundStyle(.blue.opacity(0.2))
- }
- // 25-75 percentile area
- ForEach(hourlyStats, id: \.hour) { stats in
- AreaMark(
- x: .value("Hour", Calendar.current.dateForChartHour(stats.hour)),
- yStart: .value("25th Percentile", stats.percentile25),
- yEnd: .value("75th Percentile", stats.percentile75),
- series: .value("25-75", "25-75")
- )
- .foregroundStyle(.blue.opacity(0.3))
- }
- // Median line
- ForEach(hourlyStats, id: \.hour) { stats in
- LineMark(
- x: .value("Hour", Calendar.current.dateForChartHour(stats.hour)),
- y: .value("Median", stats.median),
- series: .value("Median", "Median")
- )
- .lineStyle(StrokeStyle(lineWidth: 2))
- .foregroundStyle(.blue)
- }
- }
- // High/Low limit lines
- RuleMark(y: .value("High Limit", Double(highLimit)))
- .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 5]))
- .foregroundStyle(.orange)
- RuleMark(y: .value("Low Limit", Double(lowLimit)))
- .lineStyle(StrokeStyle(lineWidth: 1, dash: [5, 5]))
- .foregroundStyle(.red)
- }
- .chartYScale(domain: 40 ... 400)
- .chartYAxis {
- AxisMarks(position: .leading)
- }
- .chartYAxisLabel(alignment: .leading) {
- Text("\(units.rawValue)")
- .foregroundStyle(.primary)
- .font(.caption)
- .padding(.vertical, 3)
- }
- .chartXAxis {
- AxisMarks(values: .stride(by: .hour, count: 3)) { _ in
- AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
- AxisGridLine()
- }
- }
- .frame(height: 200)
- // Legend
- if !isTodayOrLast24h {
- legend
- }
- }
- }
- private var legend: some View {
- HStack(spacing: 20) {
- VStack {
- // 10-90 Percentile
- HStack(spacing: 8) {
- Rectangle()
- .frame(width: 20, height: 8)
- .foregroundStyle(.blue.opacity(0.2))
- Text("10% - 90%")
- .font(.caption)
- .foregroundStyle(.secondary)
- }
- // 25-75 Percentile
- HStack(spacing: 8) {
- Rectangle()
- .frame(width: 20, height: 8)
- .foregroundStyle(.blue.opacity(0.3))
- Text("25% - 75%")
- .font(.caption)
- .foregroundStyle(.secondary)
- }
- }
- // Median
- HStack(spacing: 8) {
- Rectangle()
- .frame(width: 20, height: 2)
- .foregroundStyle(.blue)
- Text("Median")
- .font(.caption)
- .foregroundStyle(.secondary)
- }
- VStack {
- // High Limit
- HStack(spacing: 8) {
- Rectangle()
- .frame(width: 20, height: 1)
- .foregroundStyle(.orange)
- Text("High Limit")
- .font(.caption)
- .foregroundStyle(.secondary)
- }
- // Low Limit
- HStack(spacing: 8) {
- Rectangle()
- .frame(width: 20, height: 1)
- .foregroundStyle(.red)
- Text("Low Limit")
- .font(.caption)
- .foregroundStyle(.secondary)
- }
- }
- }
- .padding(.horizontal)
- }
- }
- private extension Calendar {
- func startOfHour(for date: Date) -> Date {
- let components = dateComponents([.year, .month, .day, .hour], from: date)
- return self.date(from: components) ?? date
- }
- }
|