|
@@ -1,112 +1,11 @@
|
|
|
import Charts
|
|
import Charts
|
|
|
import SwiftUI
|
|
import SwiftUI
|
|
|
|
|
|
|
|
-private enum PredictionType: Hashable {
|
|
|
|
|
- case iob
|
|
|
|
|
- case cob
|
|
|
|
|
- case zt
|
|
|
|
|
- case uam
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-struct DotInfo {
|
|
|
|
|
- let rect: CGRect
|
|
|
|
|
- let value: Decimal
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-struct AnnouncementDot {
|
|
|
|
|
- let rect: CGRect
|
|
|
|
|
- let value: Decimal
|
|
|
|
|
- let note: String
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-typealias GlucoseYRange = (minValue: Int, minY: CGFloat, maxValue: Int, maxY: CGFloat)
|
|
|
|
|
-
|
|
|
|
|
struct MainChartView2: View {
|
|
struct MainChartView2: View {
|
|
|
- private enum Config {
|
|
|
|
|
- static let endID = "End"
|
|
|
|
|
- static let basalHeight: CGFloat = 80
|
|
|
|
|
- static let topYPadding: CGFloat = 20
|
|
|
|
|
- static let bottomYPadding: CGFloat = 80
|
|
|
|
|
- static let minAdditionalWidth: CGFloat = 150
|
|
|
|
|
- static let maxGlucose = 270
|
|
|
|
|
- static let minGlucose = 45
|
|
|
|
|
- static let yLinesCount = 5
|
|
|
|
|
- static let glucoseScale: CGFloat = 2 // default 2
|
|
|
|
|
- static let bolusSize: CGFloat = 8
|
|
|
|
|
- static let bolusScale: CGFloat = 2.5
|
|
|
|
|
- static let carbsSize: CGFloat = 10
|
|
|
|
|
- static let fpuSize: CGFloat = 5
|
|
|
|
|
- static let carbsScale: CGFloat = 0.3
|
|
|
|
|
- static let fpuScale: CGFloat = 1
|
|
|
|
|
- static let announcementSize: CGFloat = 8
|
|
|
|
|
- static let announcementScale: CGFloat = 2.5
|
|
|
|
|
- static let owlSeize: CGFloat = 25
|
|
|
|
|
- static let owlOffset: CGFloat = 80
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
// MARK: BINDINGS
|
|
// MARK: BINDINGS
|
|
|
|
|
|
|
|
@Binding var glucose: [BloodGlucose]
|
|
@Binding var glucose: [BloodGlucose]
|
|
|
- @Binding var isManual: [BloodGlucose]
|
|
|
|
|
- @Binding var suggestion: Suggestion?
|
|
|
|
|
- @Binding var tempBasals: [PumpHistoryEvent]
|
|
|
|
|
- @Binding var boluses: [PumpHistoryEvent]
|
|
|
|
|
- @Binding var suspensions: [PumpHistoryEvent]
|
|
|
|
|
- @Binding var announcement: [Announcement]
|
|
|
|
|
- @Binding var hours: Int
|
|
|
|
|
- @Binding var maxBasal: Decimal
|
|
|
|
|
- @Binding var autotunedBasalProfile: [BasalProfileEntry]
|
|
|
|
|
- @Binding var basalProfile: [BasalProfileEntry]
|
|
|
|
|
- @Binding var tempTargets: [TempTarget]
|
|
|
|
|
- @Binding var carbs: [CarbsEntry]
|
|
|
|
|
- @Binding var timerDate: Date
|
|
|
|
|
- @Binding var units: GlucoseUnits
|
|
|
|
|
- @Binding var smooth: Bool
|
|
|
|
|
- @Binding var highGlucose: Decimal
|
|
|
|
|
- @Binding var lowGlucose: Decimal
|
|
|
|
|
@Binding var screenHours: Int16
|
|
@Binding var screenHours: Int16
|
|
|
- @Binding var displayXgridLines: Bool
|
|
|
|
|
- @Binding var displayYgridLines: Bool
|
|
|
|
|
- @Binding var thresholdLines: Bool
|
|
|
|
|
-
|
|
|
|
|
-// // MARK: STATEs
|
|
|
|
|
-//
|
|
|
|
|
-// @State private var glucoseDots: [CGRect] = []
|
|
|
|
|
-// @State private var glucoseYRange: Range<CGFloat> = 0 ..< 0
|
|
|
|
|
- @State var didAppearTrigger = false
|
|
|
|
|
- @State private var glucoseDots: [CGRect] = []
|
|
|
|
|
- @State private var manualGlucoseDots: [CGRect] = []
|
|
|
|
|
- @State private var announcementDots: [AnnouncementDot] = []
|
|
|
|
|
- @State private var announcementPath = Path()
|
|
|
|
|
- @State private var manualGlucoseDotsCenter: [CGRect] = []
|
|
|
|
|
- @State private var unSmoothedGlucoseDots: [CGRect] = []
|
|
|
|
|
- @State private var predictionDots: [PredictionType: [CGRect]] = [:]
|
|
|
|
|
- @State private var bolusDots: [DotInfo] = []
|
|
|
|
|
- @State private var bolusPath = Path()
|
|
|
|
|
- @State private var tempBasalPath = Path()
|
|
|
|
|
- @State private var regularBasalPath = Path()
|
|
|
|
|
- @State private var tempTargetsPath = Path()
|
|
|
|
|
- @State private var suspensionsPath = Path()
|
|
|
|
|
- @State private var carbsDots: [DotInfo] = []
|
|
|
|
|
- @State private var carbsPath = Path()
|
|
|
|
|
- @State private var fpuDots: [DotInfo] = []
|
|
|
|
|
- @State private var fpuPath = Path()
|
|
|
|
|
- @State private var glucoseYRange: GlucoseYRange = (0, 0, 0, 0)
|
|
|
|
|
- @State private var offset: CGFloat = 0
|
|
|
|
|
- @State private var cachedMaxBasalRate: Decimal?
|
|
|
|
|
-
|
|
|
|
|
- private var date24Formatter: DateFormatter {
|
|
|
|
|
- let formatter = DateFormatter()
|
|
|
|
|
- formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
|
|
|
- formatter.setLocalizedDateFormatFromTemplate("HH")
|
|
|
|
|
- return formatter
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private var dateFormatter: DateFormatter {
|
|
|
|
|
- let formatter = DateFormatter()
|
|
|
|
|
- formatter.timeStyle = .short
|
|
|
|
|
- return formatter
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
var body: some View {
|
|
var body: some View {
|
|
|
NavigationStack {
|
|
NavigationStack {
|
|
@@ -125,6 +24,13 @@ struct MainChartView2: View {
|
|
|
|
|
|
|
|
.frame(height: 350)
|
|
.frame(height: 350)
|
|
|
.chartXAxis {
|
|
.chartXAxis {
|
|
|
|
|
+// MARK: THIS PIECE OF CODE BELONGS TO THE UNIQUEHOURLABELS FUNC....BUT DOES NOT WORK
|
|
|
|
|
+
|
|
|
|
|
+// let uniqueHourLabels = self.uniqueHourLabels(for: filteredGlucose)
|
|
|
|
|
+// AxisMarks(values: uniqueHourLabels) { _ in
|
|
|
|
|
+// AxisValueLabel(format: .dateTime.hour())
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
AxisMarks(values: filteredGlucose.map(\.dateString)) { _ in
|
|
AxisMarks(values: filteredGlucose.map(\.dateString)) { _ in
|
|
|
AxisValueLabel(format: .dateTime.hour())
|
|
AxisValueLabel(format: .dateTime.hour())
|
|
|
}
|
|
}
|
|
@@ -136,6 +42,31 @@ struct MainChartView2: View {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// // MARK: THIS FUNCTION DOES NOT WORK BUT COULD MAYBE IMPROVED?
|
|
|
|
|
+//
|
|
|
|
|
+// private func uniqueHourLabels(for glucoseData: [BloodGlucose]) -> [String] {
|
|
|
|
|
+// var uniqueLabels: Set<String> = Set()
|
|
|
|
|
+// var result: [String] = []
|
|
|
|
|
+//
|
|
|
|
|
+// let dateFormatter = DateFormatter()
|
|
|
|
|
+// dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
|
|
|
|
+// dateFormatter.setLocalizedDateFormatFromTemplate("HH")
|
|
|
|
|
+//
|
|
|
|
|
+// let currentHour = Calendar.current.component(.hour, from: Date())
|
|
|
|
|
+//
|
|
|
|
|
+// for entry in glucoseData {
|
|
|
|
|
+// let hourLabel = dateFormatter.string(from: entry.dateString)
|
|
|
|
|
+// if !uniqueLabels.contains(hourLabel), currentHour == Calendar.current.component(.hour, from: entry.dateString) {
|
|
|
|
|
+// uniqueLabels.insert(hourLabel)
|
|
|
|
|
+// result.append(hourLabel)
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
+//
|
|
|
|
|
+// return result
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+ // MARK: WORKS NOW...
|
|
|
|
|
+
|
|
|
private func filterGlucoseData(for hours: Int16) -> [BloodGlucose] {
|
|
private func filterGlucoseData(for hours: Int16) -> [BloodGlucose] {
|
|
|
guard hours > 0 else {
|
|
guard hours > 0 else {
|
|
|
return glucose
|
|
return glucose
|
|
@@ -149,121 +80,6 @@ struct MainChartView2: View {
|
|
|
|
|
|
|
|
return glucose.filter { $0.dateString >= startDate }
|
|
return glucose.filter { $0.dateString >= startDate }
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-// // MARK: GLUCOSE FOR CHART
|
|
|
|
|
-//
|
|
|
|
|
- private func calculateGlucoseDots(fullSize: CGSize) {
|
|
|
|
|
- let dots = glucose.map { value -> CGRect in
|
|
|
|
|
- let position = glucoseToCoordinate(value, fullSize: fullSize)
|
|
|
|
|
- return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let range = getGlucoseYRange(fullSize: fullSize)
|
|
|
|
|
-
|
|
|
|
|
- DispatchQueue.main.async {
|
|
|
|
|
- glucoseYRange = range
|
|
|
|
|
- glucoseDots = dots
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func getGlucoseYRange(fullSize: CGSize) -> GlucoseYRange {
|
|
|
|
|
- let topYPaddint = Config.topYPadding + Config.basalHeight
|
|
|
|
|
- let bottomYPadding = Config.bottomYPadding
|
|
|
|
|
- let (minValue, maxValue) = minMaxYValues()
|
|
|
|
|
- let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
|
|
|
|
|
- let yOffset = CGFloat(minValue) * stepYFraction
|
|
|
|
|
- let maxY = fullSize.height - CGFloat(minValue) * stepYFraction + yOffset - bottomYPadding
|
|
|
|
|
- let minY = fullSize.height - CGFloat(maxValue) * stepYFraction + yOffset - bottomYPadding
|
|
|
|
|
- return (minValue: minValue, minY: minY, maxValue: maxValue, maxY: maxY)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func glucoseToCoordinate(_ glucoseEntry: BloodGlucose, fullSize: CGSize) -> CGPoint {
|
|
|
|
|
- let x = timeToXCoordinate(glucoseEntry.dateString.timeIntervalSince1970, fullSize: fullSize)
|
|
|
|
|
- let y = glucoseToYCoordinate(glucoseEntry.glucose ?? 0, fullSize: fullSize)
|
|
|
|
|
-
|
|
|
|
|
- return CGPoint(x: x, y: y)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func glucoseToYCoordinate(_ glucoseValue: Int, fullSize: CGSize) -> CGFloat {
|
|
|
|
|
- let topYPaddint = Config.topYPadding + Config.basalHeight
|
|
|
|
|
- let bottomYPadding = Config.bottomYPadding
|
|
|
|
|
- let (minValue, maxValue) = minMaxYValues()
|
|
|
|
|
- let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
|
|
|
|
|
- let yOffset = CGFloat(minValue) * stepYFraction
|
|
|
|
|
- let y = fullSize.height - CGFloat(glucoseValue) * stepYFraction + yOffset - bottomYPadding
|
|
|
|
|
- return y
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func timeToXCoordinate(_ time: TimeInterval, fullSize: CGSize) -> CGFloat {
|
|
|
|
|
- let xOffset = -Date().addingTimeInterval(-1.days.timeInterval).timeIntervalSince1970
|
|
|
|
|
- let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
|
|
|
|
|
- let x = CGFloat(time + xOffset) * stepXFraction
|
|
|
|
|
- return x
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func fullGlucoseWidth(viewWidth: CGFloat) -> CGFloat {
|
|
|
|
|
- viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func minMaxYValues() -> (min: Int, max: Int) {
|
|
|
|
|
- var maxValue = glucose.compactMap(\.glucose).max() ?? Config.maxGlucose
|
|
|
|
|
- if let maxPredValue = maxPredValue() {
|
|
|
|
|
- maxValue = max(maxValue, maxPredValue)
|
|
|
|
|
- }
|
|
|
|
|
- if let maxTargetValue = maxTargetValue() {
|
|
|
|
|
- maxValue = max(maxValue, maxTargetValue)
|
|
|
|
|
- }
|
|
|
|
|
- var minValue = glucose.compactMap(\.glucose).min() ?? Config.minGlucose
|
|
|
|
|
- if let minPredValue = minPredValue() {
|
|
|
|
|
- minValue = min(minValue, minPredValue)
|
|
|
|
|
- }
|
|
|
|
|
- if let minTargetValue = minTargetValue() {
|
|
|
|
|
- minValue = min(minValue, minTargetValue)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if minValue == maxValue {
|
|
|
|
|
- minValue = Config.minGlucose
|
|
|
|
|
- maxValue = Config.maxGlucose
|
|
|
|
|
- }
|
|
|
|
|
- // fix the grah y-axis as long as the min and max BG values are within set borders
|
|
|
|
|
- if minValue > Config.minGlucose {
|
|
|
|
|
- minValue = Config.minGlucose
|
|
|
|
|
- }
|
|
|
|
|
- if maxValue < Config.maxGlucose {
|
|
|
|
|
- maxValue = Config.maxGlucose
|
|
|
|
|
- }
|
|
|
|
|
- return (min: minValue, max: maxValue)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func maxTargetValue() -> Int? {
|
|
|
|
|
- tempTargets.map { $0.targetTop ?? 0 }.filter { $0 > 0 }.max().map(Int.init)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func minPredValue() -> Int? {
|
|
|
|
|
- [
|
|
|
|
|
- suggestion?.predictions?.cob ?? [],
|
|
|
|
|
- suggestion?.predictions?.iob ?? [],
|
|
|
|
|
- suggestion?.predictions?.zt ?? [],
|
|
|
|
|
- suggestion?.predictions?.uam ?? []
|
|
|
|
|
- ]
|
|
|
|
|
- .flatMap { $0 }
|
|
|
|
|
- .min()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func minTargetValue() -> Int? {
|
|
|
|
|
- tempTargets.map { $0.targetBottom ?? 0 }.filter { $0 > 0 }.min().map(Int.init)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private func maxPredValue() -> Int? {
|
|
|
|
|
- [
|
|
|
|
|
- suggestion?.predictions?.cob ?? [],
|
|
|
|
|
- suggestion?.predictions?.iob ?? [],
|
|
|
|
|
- suggestion?.predictions?.zt ?? [],
|
|
|
|
|
- suggestion?.predictions?.uam ?? []
|
|
|
|
|
- ]
|
|
|
|
|
- .flatMap { $0 }
|
|
|
|
|
- .max()
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// MARK: LEGEND PANEL FOR CHART
|
|
// MARK: LEGEND PANEL FOR CHART
|