| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- import Charts
- import SwiftUI
- struct GlucoseDistributionChart: View {
- @Binding var selectedDuration: Stat.StateModel.StatsTimeInterval
- let state: Stat.StateModel
- let glucose: [GlucoseStored]
- let highLimit: Decimal
- let lowLimit: Decimal
- let units: GlucoseUnits
- let glucoseRangeStats: [Stat.GlucoseRangeStats]
- @State private var scrollPosition = Date()
- @State private var selection: Date?
- private var visibleDateRange: (start: Date, end: Date) {
- let calendar = Calendar.current
- return (
- calendar.startOfDay(for: scrollPosition),
- calendar.startOfDay(for: scrollPosition).addingTimeInterval(24 * 3600)
- )
- }
- private func formatVisibleDateRange() -> String {
- let calendar = Calendar.current
- let today = Date()
- switch selectedDuration {
- case .Day:
- let isToday = calendar.isDate(scrollPosition, inSameDayAs: today)
- let isYesterday = calendar.isDate(
- scrollPosition,
- inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!
- )
- return if isToday {
- "Today"
- } else if isYesterday {
- "Yesterday"
- } else {
- scrollPosition.formatted(date: .numeric, time: .omitted)
- }
- case .Week:
- let weekStart = calendar
- .date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: scrollPosition))!
- let weekEnd = calendar.date(byAdding: .day, value: 6, to: weekStart)!
- return "\(weekStart.formatted(date: .numeric, time: .omitted)) - \(weekEnd.formatted(date: .numeric, time: .omitted))"
- case .Month:
- let monthStart = calendar.date(
- from: calendar.dateComponents([.year, .month], from: scrollPosition)
- )!
- let monthEnd = calendar.date(byAdding: .month, value: 1, to: monthStart)!
- let lastDayOfMonth = calendar.date(byAdding: .day, value: -1, to: monthEnd)!
- return "\(monthStart.formatted(date: .numeric, time: .omitted)) - \(lastDayOfMonth.formatted(date: .numeric, time: .omitted))"
- case .Total:
- let endDate = scrollPosition
- let startDate = calendar.date(byAdding: .month, value: -3, to: endDate)!
- return "\(startDate.formatted(date: .numeric, time: .omitted)) - \(endDate.formatted(date: .numeric, time: .omitted))"
- }
- }
- var body: some View {
- VStack(alignment: .leading, spacing: 8) {
- HStack(alignment: .top) {
- Text("Glucose Distribution")
- .font(.headline)
- Spacer()
- Text(formatVisibleDateRange())
- .font(.subheadline)
- .foregroundStyle(.secondary)
- }
- Chart(glucoseRangeStats) { range in
- ForEach(range.values, id: \.hour) { value in
- AreaMark(
- x: .value("Hour", Calendar.current.dateForChartHour(value.hour)),
- y: .value("Count", value.count),
- stacking: .normalized
- )
- .foregroundStyle(by: .value("Range", range.name))
- }
- }
- .chartForegroundStyleScale([
- "<54": .purple.opacity(0.7),
- "54-70": .red.opacity(0.7),
- "70-140": .green,
- "140-180": .green.opacity(0.7),
- "180-200": .yellow.opacity(0.7),
- "200-220": .orange.opacity(0.7),
- ">220": .orange.opacity(0.8)
- ])
- .chartYAxis {
- AxisMarks(position: .trailing)
- }
- .chartXAxis {
- AxisMarks(values: .stride(by: .hour, count: 3)) { _ in
- AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
- AxisGridLine()
- }
- }
- .chartScrollableAxes(.horizontal)
- .chartScrollPosition(x: $scrollPosition)
- .chartXSelection(value: $selection)
- .chartXVisibleDomain(length: 24 * 3600)
- .chartScrollTargetBehavior(
- .valueAligned(
- matching: DateComponents(minute: 0),
- majorAlignment: .matching(DateComponents(hour: 0))
- )
- )
- .frame(height: 200)
- }
- .onChange(of: scrollPosition) {
- state.glucoseScrollPosition = scrollPosition
- state.updateDisplayedStats(for: .distribution)
- }
- }
- }
|