polscm32 2 лет назад
Родитель
Сommit
52b6f516c2

+ 8 - 4
FreeAPS.xcodeproj/project.pbxproj

@@ -175,7 +175,6 @@
 		38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */; };
 		38A9260525F012D8009E3739 /* CarbRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A9260425F012D8009E3739 /* CarbRatios.swift */; };
 		38AAF85525FFF846004AF583 /* CurrentGlucoseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */; };
-		38AAF8712600C1B0004AF583 /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AAF8702600C1B0004AF583 /* MainChartView.swift */; };
 		38AEE73D25F0200C0013F05B /* FreeAPSSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AEE73C25F0200C0013F05B /* FreeAPSSettings.swift */; };
 		38AEE75225F022080013F05B /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AEE75125F022080013F05B /* SettingsManager.swift */; };
 		38AEE75725F0F18E0013F05B /* CarbsStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AEE75625F0F18E0013F05B /* CarbsStorage.swift */; };
@@ -308,6 +307,8 @@
 		BA90041DC8991147E5C8C3AA /* CalibrationsRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500371C09F54F89A97D65FDB /* CalibrationsRootView.swift */; };
 		BD2B464E0745FBE7B79913F4 /* NightscoutConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */; };
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
+		BD3CC0722B0B89D50013189E /* MainChartView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView2.swift */; };
+		BD3CC0742B0B8C430013189E /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0732B0B8C430013189E /* DataModel.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
@@ -700,7 +701,6 @@
 		38A5049125DD9C4000C5B9E8 /* UserDefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtensions.swift; sourceTree = "<group>"; };
 		38A9260425F012D8009E3739 /* CarbRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbRatios.swift; sourceTree = "<group>"; };
 		38AAF85425FFF846004AF583 /* CurrentGlucoseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentGlucoseView.swift; sourceTree = "<group>"; };
-		38AAF8702600C1B0004AF583 /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		38AEE73C25F0200C0013F05B /* FreeAPSSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreeAPSSettings.swift; sourceTree = "<group>"; };
 		38AEE75125F022080013F05B /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; };
 		38AEE75625F0F18E0013F05B /* CarbsStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbsStorage.swift; sourceTree = "<group>"; };
@@ -839,6 +839,8 @@
 		BA49538D56989D8DA6FCF538 /* TargetsEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorDataFlow.swift; sourceTree = "<group>"; };
 		BC210C0F3CB6D3C86E5DED4E /* LibreConfigRootView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LibreConfigRootView.swift; sourceTree = "<group>"; };
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
+		BD3CC0712B0B89D50013189E /* MainChartView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView2.swift; sourceTree = "<group>"; };
+		BD3CC0732B0B8C430013189E /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
@@ -1512,7 +1514,8 @@
 		3833B51E260264AC003021B3 /* Chart */ = {
 			isa = PBXGroup;
 			children = (
-				38AAF8702600C1B0004AF583 /* MainChartView.swift */,
+				BD3CC0712B0B89D50013189E /* MainChartView2.swift */,
+				BD3CC0732B0B8C430013189E /* DataModel.swift */,
 			);
 			path = Chart;
 			sourceTree = "<group>";
@@ -2597,6 +2600,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				3811DE2325C9D48300A708ED /* MainDataFlow.swift in Sources */,
+				BD3CC0722B0B89D50013189E /* MainChartView2.swift in Sources */,
 				3811DEEB25CA063400A708ED /* PersistedProperty.swift in Sources */,
 				38E44537274E411700EC9A94 /* Disk+Helpers.swift in Sources */,
 				388E5A6025B6F2310019842D /* Autosens.swift in Sources */,
@@ -2609,6 +2613,7 @@
 				38D0B3B625EBE24900CB6E88 /* Battery.swift in Sources */,
 				38C4D33725E9A1A300D30B77 /* DispatchQueue+Extensions.swift in Sources */,
 				F90692CF274B999A0037068D /* HealthKitDataFlow.swift in Sources */,
+				BD3CC0742B0B8C430013189E /* DataModel.swift in Sources */,
 				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
 				195D80B72AF697B800D25097 /* DynamicDataFlow.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
@@ -2754,7 +2759,6 @@
 				CE7CA34E2A064973004BE681 /* AppShortcuts.swift in Sources */,
 				38C4D33A25E9A1ED00D30B77 /* NSObject+AssociatedValues.swift in Sources */,
 				38DF179027733EAD00B3528F /* SnowScene.swift in Sources */,
-				38AAF8712600C1B0004AF583 /* MainChartView.swift in Sources */,
 				19DC677F29CA675700FD9EC4 /* OverrideProfilesDataFlow.swift in Sources */,
 				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
 				CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,

+ 5 - 2
FreeAPS/Sources/Models/BasalProfileEntry.swift

@@ -1,6 +1,7 @@
 import Foundation
 
-struct BasalProfileEntry: JSON, Equatable {
+struct BasalProfileEntry: JSON, Equatable, Identifiable {
+    let id: String?
     let start: String
     let minutes: Int
     let rate: Decimal
@@ -12,6 +13,7 @@ protocol BasalProfileObserver {
 
 extension BasalProfileEntry {
     private enum CodingKeys: String, CodingKey {
+        case id = "_id"
         case start
         case minutes
         case rate
@@ -19,10 +21,11 @@ extension BasalProfileEntry {
 
     init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
+        let id = try container.decode(String.self, forKey: .id)
         let start = try container.decode(String.self, forKey: .start)
         let minutes = try container.decode(Int.self, forKey: .minutes)
         let rate = try container.decode(Double.self, forKey: .rate).decimal ?? .zero
 
-        self = BasalProfileEntry(start: start, minutes: minutes, rate: rate)
+        self = BasalProfileEntry(id: id, start: start, minutes: minutes, rate: rate)
     }
 }

+ 1 - 1
FreeAPS/Sources/Models/CarbsEntry.swift

@@ -1,6 +1,6 @@
 import Foundation
 
-struct CarbsEntry: JSON, Equatable, Hashable {
+struct CarbsEntry: JSON, Equatable, Hashable, Identifiable {
     let id: String?
     let createdAt: Date
     let actualDate: Date?

+ 1 - 1
FreeAPS/Sources/Models/PumpHistoryEvent.swift

@@ -1,6 +1,6 @@
 import Foundation
 
-struct PumpHistoryEvent: JSON, Equatable {
+struct PumpHistoryEvent: JSON, Equatable, Identifiable {
     let id: String
     let type: EventType
     let timestamp: Date

+ 1 - 1
FreeAPS/Sources/Modules/AutotuneConfig/AutotuneConfigStateModel.swift

@@ -67,7 +67,7 @@ extension AutotuneConfig {
                 let basals = autotunedBasals.basalProfile
                     .map { basal -> BasalProfileEntry in
                         BasalProfileEntry(
-                            start: String(basal.start.prefix(5)),
+                            id: "_id", start: String(basal.start.prefix(5)),
                             minutes: basal.minutes,
                             rate: basal.rate
                         )

+ 2 - 2
FreeAPS/Sources/Modules/BasalProfileEditor/BasalProfileEditorStateModel.swift

@@ -34,7 +34,7 @@ extension BasalProfileEditor {
                 let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex])
                 let minutes = Int(date.timeIntervalSince1970 / 60)
                 let rate = self.rateValues[item.rateIndex]
-                return BasalProfileEntry(start: fotmatter.string(from: date), minutes: minutes, rate: rate)
+                return BasalProfileEntry(id: "_id", start: fotmatter.string(from: date), minutes: minutes, rate: rate)
             }
 
             var profileWith24hours = profile.map(\.minutes)
@@ -66,7 +66,7 @@ extension BasalProfileEditor {
                 let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex])
                 let minutes = Int(date.timeIntervalSince1970 / 60)
                 let rate = self.rateValues[item.rateIndex]
-                return BasalProfileEntry(start: fotmatter.string(from: date), minutes: minutes, rate: rate)
+                return BasalProfileEntry(id: "_id", start: fotmatter.string(from: date), minutes: minutes, rate: rate)
             }
             provider.saveProfile(profile)
                 .receive(on: DispatchQueue.main)

+ 2 - 2
FreeAPS/Sources/Modules/Home/HomeProvider.swift

@@ -85,12 +85,12 @@ extension Home {
         func autotunedBasalProfile() -> [BasalProfileEntry] {
             storage.retrieve(OpenAPS.Settings.profile, as: Autotune.self)?.basalProfile
                 ?? storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile
-                ?? [BasalProfileEntry(start: "00:00", minutes: 0, rate: 1)]
+                ?? [BasalProfileEntry(id: "_id", start: "00:00", minutes: 0, rate: 1)]
         }
 
         func basalProfile() -> [BasalProfileEntry] {
             storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile
-                ?? [BasalProfileEntry(start: "00:00", minutes: 0, rate: 1)]
+                ?? [BasalProfileEntry(id: "_id", start: "00:00", minutes: 0, rate: 1)]
         }
     }
 }

+ 7 - 2
FreeAPS/Sources/Modules/Home/HomeStateModel.swift

@@ -62,9 +62,12 @@ extension Home {
         @Published var timeZone: TimeZone?
         @Published var hours: Int16 = 6
         @Published var totalBolus: Decimal = 0
+
         @Published var isStatusPopupPresented: Bool = false
         @Published var tins: Bool = false
 
+        @Published var cob: Decimal = 0
+
         let coredataContext = CoreDataStack.shared.persistentContainer.viewContext
 
         override func subscribe() {
@@ -104,6 +107,8 @@ extension Home {
             thresholdLines = settingsManager.settings.rulerMarks
             tins = settingsManager.settings.tins
 
+            cob = provider.suggestion?.cob ?? 0
+
             broadcaster.register(GlucoseObserver.self, observer: self)
             broadcaster.register(SuggestionObserver.self, observer: self)
             broadcaster.register(SettingsObserver.self, observer: self)
@@ -284,8 +289,8 @@ extension Home {
             offsetComponents.hour = -Int(offset)
 
             let startTime = calendar.date(byAdding: offsetComponents, to: date)!
-            print("******************")
-            print("die voll krasse start time ist: \(startTime)")
+//            print("******************")
+//            print("die voll krasse start time ist: \(startTime)")
 
             let bolusesForCurrentDay = boluses.filter { $0.timestamp >= startTime && $0.type == .bolus }
 

+ 1 - 0
FreeAPS/Sources/Modules/Home/View/Chart/DataModel.swift

@@ -0,0 +1 @@
+import Foundation

+ 583 - 0
FreeAPS/Sources/Modules/Home/View/Chart/MainChartView2.swift

@@ -0,0 +1,583 @@
+import Charts
+import SwiftUI
+
+let screenSize: CGRect = UIScreen.main.bounds
+let calendar = Calendar.current
+
+private struct BasalProfile: Hashable {
+    let amount: Double
+    var isOverwritten: Bool
+    let startDate: Date
+    let endDate: Date?
+    init(amount: Double, isOverwritten: Bool, startDate: Date, endDate: Date? = nil) {
+        self.amount = amount
+        self.isOverwritten = isOverwritten
+        self.startDate = startDate
+        self.endDate = endDate
+    }
+}
+
+private struct Prediction: Hashable {
+    let amount: Int
+    let timestamp: Date
+    let type: PredictionType
+}
+
+private enum PredictionType: Hashable {
+    case iob
+    case cob
+    case zt
+    case uam
+}
+
+struct MainChartView2: View {
+    private enum Config {
+        static let bolusSize: CGFloat = 8
+        static let bolusScale: CGFloat = 2.5
+        static let carbsSize: CGFloat = 10
+        static let carbsScale: CGFloat = 0.3
+    }
+
+    @Binding var glucose: [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 smooth: Bool
+    @Binding var highGlucose: Decimal
+    @Binding var lowGlucose: Decimal
+    @Binding var screenHours: Int16
+    @Binding var displayXgridLines: Bool
+    @Binding var displayYgridLines: Bool
+    @Binding var thresholdLines: Bool
+
+    @State private var BasalProfiles: [BasalProfile] = []
+    @State private var TempBasals: [PumpHistoryEvent] = []
+    @State private var startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
+    @State private var endMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 + 10800))
+
+    private var bolusFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumIntegerDigits = 0
+        formatter.maximumFractionDigits = 2
+        formatter.decimalSeparator = "."
+        return formatter
+    }
+
+    private var carbsFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 0
+        return formatter
+    }
+
+    var body: some View {
+        VStack(alignment: .center, spacing: 8, content: {
+            ScrollViewReader { scroller in
+                ScrollView(.horizontal, showsIndicators: false) {
+                    VStack {
+                        MainChart()
+                        BasalChart()
+                            .padding(.bottom, 8)
+                    }.onChange(of: screenHours) { _ in
+                        scroller.scrollTo("MainChart", anchor: .trailing)
+                    }.onAppear {
+                        scroller.scrollTo("MainChart", anchor: .trailing)
+                        calculateTempBasals()
+                    }
+                }
+            }
+            Legend()
+        })
+    }
+}
+
+// MARK: Components
+
+extension MainChartView2 {
+    private func MainChart() -> some View {
+        VStack {
+            Chart {
+                if thresholdLines {
+                    RuleMark(y: .value("High", highGlucose)).foregroundStyle(Color.loopYellow)
+                        .lineStyle(.init(lineWidth: 1, dash: [2]))
+                    RuleMark(y: .value("Low", lowGlucose)).foregroundStyle(Color.loopRed)
+                        .lineStyle(.init(lineWidth: 1, dash: [2]))
+                }
+                RuleMark(
+                    x: .value(
+                        "",
+                        startMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                RuleMark(
+                    x: .value(
+                        "",
+                        Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
+                        unit: .second
+                    )
+                ).lineStyle(.init(lineWidth: 1, dash: [2]))
+                RuleMark(
+                    x: .value(
+                        "",
+                        endMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                ForEach(carbs) { carb in
+                    let glucose = timeToNearestGlucose(time: carb.createdAt.timeIntervalSince1970)
+                    let carbAmount = carb.carbs
+                    PointMark(
+                        x: .value("Time", carb.createdAt, unit: .second),
+                        y: .value("Value", glucose.sgv ?? 120)
+                    )
+                    .symbolSize((Config.carbsSize + CGFloat(carb.carbs) * Config.carbsScale) * 10)
+                    .foregroundStyle(Color.orange)
+                    .annotation(position: .top) {
+                        Text(bolusFormatter.string(from: carbAmount as NSNumber)!).font(.caption2)
+                    }
+                }
+                ForEach(boluses) { bolus in
+                    let glucose = timeToNearestGlucose(time: bolus.timestamp.timeIntervalSince1970)
+                    let bolusAmount = bolus.amount ?? 0
+                    PointMark(
+                        x: .value("Time", bolus.timestamp, unit: .second),
+                        y: .value("Value", glucose.sgv ?? 120)
+                    )
+                    .symbolSize((Config.bolusSize + CGFloat(bolus.amount ?? 0) * Config.bolusScale) * 10)
+                    .foregroundStyle(Color.insulin)
+                    .annotation(position: .bottom) {
+                        Text(bolusFormatter.string(from: bolusAmount as NSNumber)!).font(.caption2)
+                    }
+                }
+                ForEach(calculatePredictions(), id: \.self) { info in
+                    if info.type == .uam, info.timestamp.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("uam", "uam")
+                        ).foregroundStyle(Color.uam).symbolSize(16)
+                    }
+                    if info.type == .cob {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("cob", "cob")
+                        ).foregroundStyle(Color.orange).symbolSize(16)
+                    }
+                    if info.type == .iob {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("iob", "iob")
+                        ).foregroundStyle(Color.insulin).symbolSize(16)
+                    }
+                    if info.type == .zt {
+                        LineMark(
+                            x: .value("Time", info.timestamp, unit: .second),
+                            y: .value("Value", info.amount),
+                            series: .value("zt", "zt")
+                        ).foregroundStyle(Color.zt).symbolSize(16)
+                    }
+                }
+                ForEach(glucose) {
+                    if $0.sgv != nil {
+                        PointMark(
+                            x: .value("Time", $0.dateString, unit: .second),
+                            y: .value("Value", $0.sgv!)
+                        ).foregroundStyle(Color.green).symbolSize(16)
+                        if smooth {
+                            LineMark(
+                                x: .value("Time", $0.dateString, unit: .second),
+                                y: .value("Value", $0.sgv!),
+                                series: .value("glucose", "glucose")
+                            ).foregroundStyle(Color.green)
+                        }
+                    }
+                }
+            }.id("MainChart")
+                .frame(
+                    width: max(0, screenSize.width - 20, fullWidth(viewWidth: screenSize.width)),
+                    height: min(screenSize.height, 200)
+                )
+                .chartYScale(domain: 0 ... 450)
+                .chartXAxis {
+                    AxisMarks(values: .stride(by: .hour, count: 2)) { _ in
+                        if displayXgridLines {
+                            AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                        } else {
+                            AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                        }
+                    }
+                }.chartYAxis {
+                    AxisMarks(position: .trailing, values: .stride(by: 100)) { value in
+                        if displayYgridLines {
+                            AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                        } else {
+                            AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                        }
+                        if let glucoseValue = value.as(Double.self), glucoseValue > 0 {
+                            AxisTick(length: 4, stroke: .init(lineWidth: 4))
+                                .foregroundStyle(Color.gray)
+                            AxisValueLabel()
+                        }
+                    }
+                }
+        }
+    }
+
+    func BasalChart() -> some View {
+        VStack {
+            Chart {
+                RuleMark(
+                    x: .value(
+                        "",
+                        startMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                RuleMark(
+                    x: .value(
+                        "",
+                        Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
+                        unit: .second
+                    )
+                ).lineStyle(.init(lineWidth: 1, dash: [2]))
+                RuleMark(
+                    x: .value(
+                        "",
+                        endMarker,
+                        unit: .second
+                    )
+                ).foregroundStyle(.clear)
+                ForEach(calculateTempBasals()) {
+                    BarMark(
+                        x: .value("Time", $0.timestamp),
+                        y: .value("Rate", $0.rate ?? 0)
+                    )
+                }
+                ForEach(BasalProfiles, id: \.self) { profile in
+                    LineMark(
+                        x: .value("Start Date", profile.startDate),
+                        y: .value("Amount", profile.amount),
+                        series: .value("profile", "profile")
+                    ).lineStyle(.init(lineWidth: 2, dash: [2, 3]))
+                    LineMark(
+                        x: .value("End Date", profile.endDate ?? endMarker),
+                        y: .value("Amount", profile.amount),
+                        series: .value("profile", "profile")
+                    ).lineStyle(.init(lineWidth: 2, dash: [2, 3]))
+                }
+            }
+            .frame(height: 80)
+            .chartYScale(domain: 0 ... maxBasal)
+            //            .rotationEffect(.degrees(180))
+            //            .chartXAxis(.hidden)
+            .chartXAxis {
+                AxisMarks(values: .stride(by: .hour, count: screenHours == 24 ? 4 : 2)) { _ in
+                    if displayXgridLines {
+                        AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                    } else {
+                        AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                    }
+                    AxisValueLabel(format: .dateTime.hour(.defaultDigits(amPM: .narrow)), anchor: .top)
+                }
+            }.chartYAxis {
+                AxisMarks(position: .trailing, values: .stride(by: 1)) { _ in
+                    if displayYgridLines {
+                        AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
+                    } else {
+                        AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
+                    }
+                    AxisTick(length: 30, stroke: .init(lineWidth: 4))
+                        .foregroundStyle(Color.clear)
+                }
+            }
+
+            .chartPlotStyle { plotArea in
+                plotArea.background(.blue.gradient.opacity(0.1))
+            }
+        }
+    }
+
+    private func Legend() -> some View {
+        HStack {
+            Image(systemName: "line.diagonal")
+                .rotationEffect(Angle(degrees: 45))
+                .foregroundColor(.green)
+            Text("BG")
+                .foregroundColor(.secondary)
+            Spacer()
+            Image(systemName: "line.diagonal")
+                .rotationEffect(Angle(degrees: 45))
+                .foregroundColor(.insulin)
+            Text("IOB")
+                .foregroundColor(.secondary)
+            Spacer()
+            Image(systemName: "line.diagonal")
+                .rotationEffect(Angle(degrees: 45))
+                .foregroundColor(.purple)
+            Text("ZT")
+                .foregroundColor(.secondary)
+            Spacer()
+            Image(systemName: "line.diagonal")
+                .frame(height: 10)
+                .rotationEffect(Angle(degrees: 45))
+                .foregroundColor(.loopYellow)
+            Text("COB")
+                .foregroundColor(.secondary)
+            Spacer()
+            Image(systemName: "line.diagonal")
+                .rotationEffect(Angle(degrees: 45))
+                .foregroundColor(.orange)
+            Text("UAM")
+                .foregroundColor(.secondary)
+            if suggestion?.predictions?.cob?.last != nil {
+                Text("⇢ " + String(suggestion?.predictions?.cob?.last ?? 0))
+            } else if suggestion?.predictions?.uam?.last != nil {
+                Text("⇢ " + String(suggestion?.predictions?.uam?.last ?? 0))
+            }
+        }
+        .font(.caption2)
+        .padding(.horizontal, 40)
+        .padding(.vertical, 1)
+        .onChange(of: basalProfile) { _ in
+            calculcateBasals()
+        }
+        .onChange(of: tempBasals) { _ in
+            calculateTempBasals()
+        }
+    }
+}
+
+// MARK: Calculations
+
+extension MainChartView2 {
+    private func timeToNearestGlucose(time: TimeInterval) -> BloodGlucose {
+        var nextIndex = 0
+        if glucose.last?.dateString.timeIntervalSince1970 ?? Date().timeIntervalSince1970 < time {
+            return glucose.last ?? BloodGlucose(
+                date: 0,
+                dateString: Date(),
+                unfiltered: nil,
+                filtered: nil,
+                noise: nil,
+                type: nil
+            )
+        }
+        for (index, value) in glucose.enumerated() {
+            if value.dateString.timeIntervalSince1970 > time {
+                nextIndex = index
+                print("Break", value.dateString.timeIntervalSince1970, time)
+                break
+            }
+            print("Glucose", value.dateString.timeIntervalSince1970, time)
+        }
+        return glucose[nextIndex]
+    }
+
+    private func fullWidth(viewWidth: CGFloat) -> CGFloat {
+        viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
+    }
+
+    private func calculatePredictions() -> [Prediction] {
+        var calculatedPredictions: [Prediction] = []
+        let uam = suggestion?.predictions?.uam ?? []
+        let iob = suggestion?.predictions?.iob ?? []
+        let cob = suggestion?.predictions?.cob ?? []
+        let zt = suggestion?.predictions?.zt ?? []
+        guard let deliveredAt = suggestion?.deliverAt else {
+            return []
+        }
+        uam.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: uam[index], timestamp: predTime, type: .uam)
+                )
+            }
+            print(
+                "Vergleich",
+                index,
+                predTime.timeIntervalSince1970,
+                endMarker.timeIntervalSince1970,
+                predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970
+            )
+        }
+        iob.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: iob[index], timestamp: predTime, type: .iob)
+                )
+            }
+        }
+        cob.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: cob[index], timestamp: predTime, type: .cob)
+                )
+            }
+        }
+        zt.indices.forEach { index in
+            let predTime = Date(
+                timeIntervalSince1970: deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes
+                    .timeInterval
+            )
+            if predTime.timeIntervalSince1970 < endMarker.timeIntervalSince1970 {
+                calculatedPredictions.append(
+                    Prediction(amount: zt[index], timestamp: predTime, type: .zt)
+                )
+            }
+        }
+
+        return calculatedPredictions
+    }
+
+    private func getLastUam() -> Int {
+        let uam = suggestion?.predictions?.uam ?? []
+        return uam.last ?? 0
+    }
+
+    private func calculateTempBasals() -> [PumpHistoryEvent] {
+        var basals = tempBasals
+        var returnTempBasalRates: [PumpHistoryEvent] = []
+        var finished: [Int: Bool] = [:]
+        basals.indices.forEach { i in
+            basals.indices.forEach { j in
+                if basals[i].timestamp == basals[j].timestamp, i != j, !(finished[i] ?? false), !(finished[j] ?? false) {
+                    let rate = basals[i].rate ?? basals[j].rate
+                    let durationMin = basals[i].durationMin ?? basals[j].durationMin
+                    finished[i] = true
+                    if rate != 0 || durationMin != 0 {
+                        returnTempBasalRates.append(
+                            PumpHistoryEvent(
+                                id: basals[i].id, type: FreeAPS.EventType.tempBasal,
+                                timestamp: basals[i].timestamp,
+                                durationMin: durationMin,
+                                rate: rate
+                            )
+                        )
+                    }
+                }
+            }
+        }
+        print("Temp Basals", returnTempBasalRates)
+        return returnTempBasalRates
+    }
+
+    private func findRegularBasalPoints(
+        timeBegin: TimeInterval,
+        timeEnd: TimeInterval,
+        autotuned: Bool
+    ) -> [BasalProfile] {
+        guard timeBegin < timeEnd else {
+            return []
+        }
+        let beginDate = Date(timeIntervalSince1970: timeBegin)
+        let calendar = Calendar.current
+        let startOfDay = calendar.startOfDay(for: beginDate)
+
+        let profile = autotuned ? autotunedBasalProfile : basalProfile
+
+        let basalNormalized = profile.map {
+            (
+                time: startOfDay.addingTimeInterval($0.minutes.minutes.timeInterval).timeIntervalSince1970,
+                rate: $0.rate
+            )
+        } + profile.map {
+            (
+                time: startOfDay.addingTimeInterval($0.minutes.minutes.timeInterval + 1.days.timeInterval)
+                    .timeIntervalSince1970,
+                rate: $0.rate
+            )
+        } + profile.map {
+            (
+                time: startOfDay.addingTimeInterval($0.minutes.minutes.timeInterval + 2.days.timeInterval)
+                    .timeIntervalSince1970,
+                rate: $0.rate
+            )
+        }
+
+        let basalTruncatedPoints = basalNormalized.windows(ofCount: 2)
+            .compactMap { window -> BasalProfile? in
+                let window = Array(window)
+                if window[0].time < timeBegin, window[1].time < timeBegin {
+                    return nil
+                }
+
+                if window[0].time < timeBegin, window[1].time >= timeBegin {
+                    let startDate = Date(timeIntervalSince1970: timeBegin)
+                    let rate = window[0].rate
+                    return BasalProfile(amount: Double(rate), isOverwritten: false, startDate: startDate)
+                }
+
+                if window[0].time >= timeBegin, window[0].time < timeEnd {
+                    let startDate = Date(timeIntervalSince1970: window[0].time)
+                    let rate = window[0].rate
+                    return BasalProfile(amount: Double(rate), isOverwritten: false, startDate: startDate)
+                }
+
+                return nil
+            }
+
+        return basalTruncatedPoints
+    }
+
+    private func calculcateBasals() {
+        let dayAgoTime = Date().addingTimeInterval(-1.days.timeInterval).timeIntervalSince1970
+        let firstTempTime = (tempBasals.first?.timestamp ?? Date()).timeIntervalSince1970
+
+        let regularPoints = findRegularBasalPoints(
+            timeBegin: dayAgoTime,
+            timeEnd: endMarker.timeIntervalSince1970,
+            autotuned: false
+        )
+
+        let autotunedBasalPoints = findRegularBasalPoints(
+            timeBegin: dayAgoTime,
+            timeEnd: endMarker.timeIntervalSince1970,
+            autotuned: true
+        )
+        var totalBasal = regularPoints + autotunedBasalPoints
+        totalBasal.sort {
+            $0.startDate.timeIntervalSince1970 < $1.startDate.timeIntervalSince1970
+        }
+        var basals: [BasalProfile] = []
+        totalBasal.indices.forEach { index in
+            basals.append(BasalProfile(
+                amount: totalBasal[index].amount,
+                isOverwritten: totalBasal[index].isOverwritten,
+                startDate: totalBasal[index].startDate,
+                endDate: totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker
+            ))
+            print(
+                "Basal",
+                totalBasal[index].startDate,
+                totalBasal.count > index + 1 ? totalBasal[index + 1].startDate : endMarker,
+                totalBasal[index].amount,
+                totalBasal[index].isOverwritten
+            )
+        }
+        BasalProfiles = basals
+    }
+}

+ 2 - 5
FreeAPS/Sources/Modules/Home/View/HomeRootView.swift

@@ -451,9 +451,8 @@ extension Home {
                         .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                 }
 
-                MainChartView(
+                MainChartView2(
                     glucose: $state.glucose,
-                    isManual: $state.isManual,
                     suggestion: $state.suggestion,
                     tempBasals: $state.tempBasals,
                     boluses: $state.boluses,
@@ -465,8 +464,6 @@ extension Home {
                     basalProfile: $state.basalProfile,
                     tempTargets: $state.tempTargets,
                     carbs: $state.carbs,
-                    timerDate: $state.timerDate,
-                    units: $state.units,
                     smooth: $state.smooth,
                     highGlucose: $state.highGlucose,
                     lowGlucose: $state.lowGlucose,
@@ -693,7 +690,7 @@ extension Home {
 
                     Spacer()
 
-                    legendPanel
+//                    legendPanel
 
                     Spacer()
 

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift

@@ -194,7 +194,7 @@ extension NightscoutConfig {
                                     areBasalsOK = false
                                 }
                                 return BasalProfileEntry(
-                                    start: basal.time,
+                                    id: "_id", start: basal.time,
                                     minutes: (basal.timeAsSeconds ?? self.offset(basal.time)) / 60,
                                     rate: basal.value
                                 ) }

+ 1 - 1
FreeAPS/Sources/Modules/PumpConfig/PumpConfigProvider.swift

@@ -16,7 +16,7 @@ extension PumpConfig {
 
         func basalProfile() -> [BasalProfileEntry] {
             storage.retrieve(OpenAPS.Settings.pumpProfile, as: Autotune.self)?.basalProfile
-                ?? [BasalProfileEntry(start: "00:00", minutes: 0, rate: 1)]
+                ?? [BasalProfileEntry(id: "_id", start: "00:00", minutes: 0, rate: 1)]
         }
 
         func pumpSettings() -> PumpSettings {