From dd8a6b41f10bf0e1f84bd78b417550e6f48662d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 30 May 2026 19:59:21 +0200 Subject: [PATCH] Add optional yesterday BG comparison line to main graph Adds a Graph Settings toggle (Nightscout only) that overlays yesterday's BG curve on the main graph, time-shifted by 24h so it aligns with the same clock time today. Drawn as a thin dimmed gray line with no dots, purely for comparison. When enabled, one extra day of history is fetched and the overlay is capped to now plus the configured hours of prediction so it never extends further into the future than the prediction line. --- LoopFollow/Controllers/Graphs.swift | 45 +++++++++++++++++++ .../Controllers/Nightscout/BGData.swift | 35 +++++++++++++-- LoopFollow/Settings/GraphSettingsView.swift | 4 ++ LoopFollow/Storage/Storage.swift | 1 + .../ViewControllers/MainViewController.swift | 1 + 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/LoopFollow/Controllers/Graphs.swift b/LoopFollow/Controllers/Graphs.swift index 9789e9c46..6b2d261cd 100644 --- a/LoopFollow/Controllers/Graphs.swift +++ b/LoopFollow/Controllers/Graphs.swift @@ -27,6 +27,7 @@ enum GraphDataIndex: Int { case smb = 16 case tempTarget = 17 case predictionCone = 18 + case yesterday = 19 } extension GraphDataIndex { @@ -51,6 +52,7 @@ extension GraphDataIndex { case .smb: return "SMB" case .tempTarget: return "Temp Target" case .predictionCone: return "Prediction Cone" + case .yesterday: return "Yesterday" } } } @@ -622,6 +624,16 @@ extension MainViewController { lineCone.axisDependency = YAxis.AxisDependency.right data.append(lineCone) + // Dataset 19: Yesterday's BG comparison overlay (thin dimmed gray line, no dots) + let lineYesterday = LineChartDataSet(entries: [ChartDataEntry](), label: "") + lineYesterday.lineWidth = 1.5 + lineYesterday.setColor(NSUIColor.systemGray, alpha: 0.4) + lineYesterday.drawCirclesEnabled = false + lineYesterday.drawValuesEnabled = false + lineYesterday.highlightEnabled = false + lineYesterday.axisDependency = YAxis.AxisDependency.right + data.append(lineYesterday) + data.setValueFont(UIFont.systemFont(ofSize: 12)) // Add marker popups for bolus and carbs @@ -813,6 +825,11 @@ extension MainViewController { BGChart.data?.notifyDataChanged() BGChart.notifyDataSetChanged() + // Reflect the yesterday overlay toggle immediately, and reload the BG window + // so the extra day of history is fetched (or dropped) when the toggle changed. + updateYesterdayBGGraph() + TaskScheduler.shared.rescheduleTask(id: .fetchBG, to: Date()) + // Re-render prediction display in case display type changed updateOpenAPSPredictionDisplay() } @@ -882,6 +899,8 @@ extension MainViewController { BGChartFull.data?.notifyDataChanged() BGChartFull.notifyDataSetChanged() + updateYesterdayBGGraph() + if firstGraphLoad { var scaleX = CGFloat(Storage.shared.chartScaleX.value) if scaleX > CGFloat(ScaleXMax) { @@ -899,6 +918,32 @@ extension MainViewController { } } + // Populates (or clears) the dimmed "yesterday" comparison overlay on the main graph. + // Points in yesterdayBGData are already shifted +24h so they align with today's clock time. + func updateYesterdayBGGraph() { + let dataIndex = GraphDataIndex.yesterday.rawValue + guard let lineData = BGChart.lineData, + dataIndex < lineData.dataSets.count, + let dataSet = lineData.dataSets[dataIndex] as? LineChartDataSet + else { + return + } + + dataSet.removeAll(keepingCapacity: false) + + if Storage.shared.showYesterdayLine.value { + for entry in yesterdayBGData { + // Clamp the plotted y-value to the same bounds the main BG line uses. + let plottedSgv = Double(min(max(entry.sgv, globalVariables.minDisplayGlucose), globalVariables.maxDisplayGlucose)) + dataSet.append(ChartDataEntry(x: entry.date, y: plottedSgv)) + } + } + + BGChart.data?.dataSets[dataIndex].notifyDataSetChanged() + BGChart.data?.notifyDataChanged() + BGChart.notifyDataSetChanged() + } + func updatePredictionGraph(color: UIColor? = nil) { let dataIndex = 1 var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet diff --git a/LoopFollow/Controllers/Nightscout/BGData.swift b/LoopFollow/Controllers/Nightscout/BGData.swift index d97aba24c..966ddacb1 100644 --- a/LoopFollow/Controllers/Nightscout/BGData.swift +++ b/LoopFollow/Controllers/Nightscout/BGData.swift @@ -5,11 +5,19 @@ import Foundation import UIKit extension MainViewController { + /// Number of days of BG history to request from the source. One extra day is + /// added when the "Show Yesterday's BG" overlay is enabled (Nightscout only), + /// so the overlay can display the same clock time from the day before. + var bgFetchDays: Int { + let extraDay = (Storage.shared.showYesterdayLine.value && IsNightscoutEnabled()) ? 1 : 0 + return Storage.shared.downloadDays.value + extraDay + } + // Dex Share Web Call func webLoadDexShare() { // Dexcom Share only returns 24 hrs of data as of now // Requesting more just for consistency with NS - let graphHours = 24 * Storage.shared.downloadDays.value + let graphHours = 24 * bgFetchDays let count = graphHours * 12 dexShare?.fetchData(count) { err, result in if let error = err { @@ -51,8 +59,8 @@ extension MainViewController { } var parameters: [String: String] = [:] - let date = Calendar.current.date(byAdding: .day, value: -1 * Storage.shared.downloadDays.value, to: Date())! - parameters["count"] = "\(Storage.shared.downloadDays.value * 2 * 24 * 60 / 5)" + let date = Calendar.current.date(byAdding: .day, value: -1 * bgFetchDays, to: Date())! + parameters["count"] = "\(bgFetchDays * 2 * 24 * 60 / 5)" parameters["find[date][$gte]"] = "\(Int(date.timeIntervalSince1970 * 1000))" // Exclude 'cal' entries @@ -207,6 +215,27 @@ extension MainViewController { LogManager.shared.log(category: .nightscout, message: "Graph data updated with \(bgData.count) entries.", isDebug: true) + + // Build the optional "yesterday" comparison overlay. Every fetched reading is + // shifted +24h so it lines up with the same clock time today; the extra day of + // history pulled by bgFetchDays provides the portion that falls inside the + // visible window. The overlay is capped to "now + hours of prediction" so it + // never extends further into the future than the prediction line. + yesterdayBGData.removeAll() + if Storage.shared.showYesterdayLine.value, IsNightscoutEnabled() { + let cutoff = dateTimeUtils.getTimeIntervalNHoursAgo(N: 24 * bgFetchDays) + let futureLimit = dateTimeUtils.getNowTimeIntervalUTC() + Storage.shared.predictionToLoad.value * 3600 + for i in 0 ..< data.count { + let reading = data[data.count - 1 - i] + guard reading.date >= cutoff, reading.sgv <= 600 else { continue } + let shiftedDate = reading.date + 24 * 60 * 60 + guard shiftedDate <= futureLimit else { continue } + yesterdayBGData.append(ShareGlucoseData(sgv: reading.sgv, + date: shiftedDate, + direction: reading.direction)) + } + } + viewUpdateNSBG(sourceName: sourceName) } diff --git a/LoopFollow/Settings/GraphSettingsView.swift b/LoopFollow/Settings/GraphSettingsView.swift index 534e00698..a9b07d666 100644 --- a/LoopFollow/Settings/GraphSettingsView.swift +++ b/LoopFollow/Settings/GraphSettingsView.swift @@ -12,6 +12,7 @@ struct GraphSettingsView: View { @ObservedObject private var show30MinLine = Storage.shared.show30MinLine @ObservedObject private var show90MinLine = Storage.shared.show90MinLine @ObservedObject private var showMidnightLines = Storage.shared.showMidnightLines + @ObservedObject private var showYesterdayLine = Storage.shared.showYesterdayLine @ObservedObject private var smallGraphTreatments = Storage.shared.smallGraphTreatments @ObservedObject private var smallGraphHeight = Storage.shared.smallGraphHeight @@ -43,6 +44,9 @@ struct GraphSettingsView: View { Toggle("Show −90 min Line", isOn: $show90MinLine.value) .onChange(of: show90MinLine.value) { _ in markDirty() } + + Toggle("Show Yesterday's BG", isOn: $showYesterdayLine.value) + .onChange(of: showYesterdayLine.value) { _ in markDirty() } } Toggle("Show Midnight Lines", isOn: $showMidnightLines.value) diff --git a/LoopFollow/Storage/Storage.swift b/LoopFollow/Storage/Storage.swift index 6ee01522b..e5110b1f0 100644 --- a/LoopFollow/Storage/Storage.swift +++ b/LoopFollow/Storage/Storage.swift @@ -129,6 +129,7 @@ class Storage { var show30MinLine = StorageValue(key: "show30MinLine", defaultValue: false) var show90MinLine = StorageValue(key: "show90MinLine", defaultValue: false) var showMidnightLines = StorageValue(key: "showMidnightMarkers", defaultValue: false) + var showYesterdayLine = StorageValue(key: "showYesterdayLine", defaultValue: false) var smallGraphTreatments = StorageValue(key: "smallGraphTreatments", defaultValue: true) var smallGraphHeight = StorageValue(key: "smallGraphHeight", defaultValue: 40) diff --git a/LoopFollow/ViewControllers/MainViewController.swift b/LoopFollow/ViewControllers/MainViewController.swift index 6abea5ab4..272823fe7 100644 --- a/LoopFollow/ViewControllers/MainViewController.swift +++ b/LoopFollow/ViewControllers/MainViewController.swift @@ -77,6 +77,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele var profileManager = ProfileManager.shared var bgData: [ShareGlucoseData] = [] + var yesterdayBGData: [ShareGlucoseData] = [] // readings already shifted +24h for the comparison overlay var basalProfile: [basalProfileStruct] = [] var basalData: [basalGraphStruct] = [] var basalScheduleData: [basalGraphStruct] = []