Ivan Valkou преди 5 години
родител
ревизия
d0067ffd22
променени са 2 файла, в които са добавени 74 реда и са изтрити 24 реда
  1. 2 1
      FreeAPS/Sources/Modules/Home/View/HomeRootView.swift
  2. 72 23
      FreeAPS/Sources/Modules/Home/View/MainChartView.swift

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

@@ -50,7 +50,8 @@ extension Home {
                     MainChartView(
                     MainChartView(
                         glucose: $viewModel.glucose,
                         glucose: $viewModel.glucose,
                         suggestion: $viewModel.suggestion,
                         suggestion: $viewModel.suggestion,
-                        hours: .constant(24)
+                        hours: .constant(24),
+                        units: viewModel.units
                     )
                     )
 
 
                     ZStack {
                     ZStack {

+ 72 - 23
FreeAPS/Sources/Modules/Home/View/MainChartView.swift

@@ -12,12 +12,17 @@ private enum PredictionType: Hashable {
 struct MainChartView: View {
 struct MainChartView: View {
     private enum Config {
     private enum Config {
         static let screenHours = 6
         static let screenHours = 6
-        static let basalHeight: CGFloat = 80
+        static let basalHeight: CGFloat = 60
+        static let topYPadding: CGFloat = 20
+        static let bottomYPadding: CGFloat = 50
+
+        static let yLinesCount = 5
     }
     }
 
 
     @Binding var glucose: [BloodGlucose]
     @Binding var glucose: [BloodGlucose]
     @Binding var suggestion: Suggestion?
     @Binding var suggestion: Suggestion?
     @Binding var hours: Int
     @Binding var hours: Int
+    let units: GlucoseUnits
 
 
     @State var didAppearTrigger = false
     @State var didAppearTrigger = false
     @State private var glucoseDots: [CGRect] = []
     @State private var glucoseDots: [CGRect] = []
@@ -29,35 +34,65 @@ struct MainChartView: View {
         return formatter
         return formatter
     }
     }
 
 
+    private var glucoseFormatter: NumberFormatter {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.maximumFractionDigits = 1
+        return formatter
+    }
+
     var body: some View {
     var body: some View {
         GeometryReader { geo in
         GeometryReader { geo in
-            ScrollView(.horizontal, showsIndicators: false) {
-                ScrollViewReader { scroll in
-                    VStack {
-                        mainChart(fullSize: geo.size)
-                            .drawingGroup(opaque: false, colorMode: .nonLinear)
-                            .onChange(of: glucose) { _ in
-                                scroll.scrollTo("End")
-                            }
-                            .onAppear {
-                                scroll.scrollTo("End")
-                                // add trigger to the end of main queue
-                                DispatchQueue.main.async {
-                                    didAppearTrigger = true
+            ZStack(alignment: .leading) {
+                Path { path in
+                    let range = glucoseYRange(fullSize: geo.size)
+                    let step = (range.maxY - range.minY) / CGFloat(Config.yLinesCount)
+                    for line in 0 ... Config.yLinesCount {
+                        path.move(to: CGPoint(x: 0, y: range.minY + CGFloat(line) * step))
+                        path.addLine(to: CGPoint(x: geo.size.width, y: range.minY + CGFloat(line) * step))
+                    }
+                }.stroke(Color.secondary, lineWidth: 0.2)
+
+                ScrollView(.horizontal, showsIndicators: false) {
+                    ScrollViewReader { scroll in
+                        ZStack(alignment: .top) {
+                            basalChart(fullSize: geo.size)
+                            mainChart(fullSize: geo.size)
+                                .onChange(of: glucose) { _ in
+                                    scroll.scrollTo("End")
                                 }
                                 }
-                            }
+                                .onAppear {
+                                    scroll.scrollTo("End")
+                                    // add trigger to the end of main queue
+                                    DispatchQueue.main.async {
+                                        didAppearTrigger = true
+                                    }
+                                }
+                        }
                     }
                     }
                 }
                 }
+                ForEach(0 ..< Config.yLinesCount) { line -> AnyView in
+                    let range = glucoseYRange(fullSize: geo.size)
+                    let yStep = (range.maxY - range.minY) / CGFloat(Config.yLinesCount)
+                    let valueStep = Double(range.maxValue - range.minValue) / Double(Config.yLinesCount)
+                    let value = round(Double(range.maxValue) - Double(line) * valueStep) *
+                        (units == .mmolL ? Double(GlucoseUnits.exchangeRate) : 1)
+
+                    return Text(glucoseFormatter.string(from: value as NSNumber)!)
+                        .position(CGPoint(x: geo.size.width - 12, y: range.minY + CGFloat(line) * yStep))
+                        .font(.caption2)
+                        .asAny()
+                }
             }
             }
         }
         }
     }
     }
 
 
     private func basalChart(fullSize: CGSize) -> some View {
     private func basalChart(fullSize: CGSize) -> some View {
         Group {
         Group {
-            Text("test")
+            EmptyView()
         }
         }
         .frame(width: fullGlucoseWidth(viewWidth: fullSize.width) + additionalWidth(viewWidth: fullSize.width))
         .frame(width: fullGlucoseWidth(viewWidth: fullSize.width) + additionalWidth(viewWidth: fullSize.width))
-        .frame(maxHeight: 80).background(Color.red)
+        .frame(maxHeight: Config.basalHeight).background(Color.secondary.opacity(0.1))
     }
     }
 
 
     private func mainChart(fullSize: CGSize) -> some View {
     private func mainChart(fullSize: CGSize) -> some View {
@@ -234,15 +269,16 @@ struct MainChartView: View {
         guard let first = glucose.first else {
         guard let first = glucose.first else {
             return .zero
             return .zero
         }
         }
-        let yPadding: CGFloat = 30
+        let topYPaddint = Config.topYPadding + Config.basalHeight
+        let bottomYPadding = Config.bottomYPadding
         let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
         let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
         let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
         let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
-        let stepYFraction = (fullSize.height - yPadding * 2) / CGFloat(maxValue - minValue)
+        let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
         let yOffset = CGFloat(minValue) * stepYFraction
         let yOffset = CGFloat(minValue) * stepYFraction
         let xOffset = -first.dateString.timeIntervalSince1970
         let xOffset = -first.dateString.timeIntervalSince1970
         let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
         let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
         let x = CGFloat(glucoseEntry.dateString.timeIntervalSince1970 + xOffset) * stepXFraction
         let x = CGFloat(glucoseEntry.dateString.timeIntervalSince1970 + xOffset) * stepXFraction
-        let y = fullSize.height - CGFloat(glucoseEntry.glucose ?? 0) * stepYFraction + yOffset - yPadding
+        let y = fullSize.height - CGFloat(glucoseEntry.glucose ?? 0) * stepYFraction + yOffset - bottomYPadding
 
 
         return CGPoint(x: x, y: y)
         return CGPoint(x: x, y: y)
     }
     }
@@ -251,20 +287,33 @@ struct MainChartView: View {
         guard let first = glucose.first, let deliveredAt = suggestion?.deliverAt else {
         guard let first = glucose.first, let deliveredAt = suggestion?.deliverAt else {
             return .zero
             return .zero
         }
         }
-        let yPadding: CGFloat = 30
+        let topYPaddint = Config.topYPadding + Config.basalHeight
+        let bottomYPadding = Config.bottomYPadding
         let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
         let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
         let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
         let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
-        let stepYFraction = (fullSize.height - yPadding * 2) / CGFloat(maxValue - minValue)
+        let stepYFraction = (fullSize.height - topYPaddint - bottomYPadding) / CGFloat(maxValue - minValue)
         let yOffset = CGFloat(minValue) * stepYFraction
         let yOffset = CGFloat(minValue) * stepYFraction
         let xOffset = -first.dateString.timeIntervalSince1970
         let xOffset = -first.dateString.timeIntervalSince1970
         let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
         let stepXFraction = fullGlucoseWidth(viewWidth: fullSize.width) / CGFloat(hours.hours.timeInterval)
         let predTime = deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes.timeInterval
         let predTime = deliveredAt.timeIntervalSince1970 + TimeInterval(index) * 5.minutes.timeInterval
         let x = CGFloat(predTime + xOffset) * stepXFraction
         let x = CGFloat(predTime + xOffset) * stepXFraction
-        let y = fullSize.height - CGFloat(pred) * stepYFraction + yOffset - yPadding
+        let y = fullSize.height - CGFloat(pred) * stepYFraction + yOffset - bottomYPadding
 
 
         return CGPoint(x: x, y: y)
         return CGPoint(x: x, y: y)
     }
     }
 
 
+    private func glucoseYRange(fullSize: CGSize) -> (minValue: Int, minY: CGFloat, maxValue: Int, maxY: CGFloat) {
+        let topYPaddint = Config.topYPadding + Config.basalHeight
+        let bottomYPadding = Config.bottomYPadding
+        let maxValue = max(glucose.compactMap(\.glucose).max() ?? 450, maxPredValue())
+        let minValue = min(glucose.compactMap(\.glucose).min() ?? 0, minPredValue())
+        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 firstHourDate() -> Date {
     private func firstHourDate() -> Date {
         let firstDate = glucose.first?.dateString ?? Date()
         let firstDate = glucose.first?.dateString ?? Date()
         return firstDate.dateTruncated(from: .minute)!
         return firstDate.dateTruncated(from: .minute)!