|
@@ -4,7 +4,6 @@ import SwiftUI
|
|
|
|
|
|
|
|
struct GlucoseTargetsView: ChartContent {
|
|
struct GlucoseTargetsView: ChartContent {
|
|
|
let startMarker: Date
|
|
let startMarker: Date
|
|
|
- let endMarker: Date
|
|
|
|
|
let units: GlucoseUnits
|
|
let units: GlucoseUnits
|
|
|
let bgTargets: BGTargets
|
|
let bgTargets: BGTargets
|
|
|
|
|
|
|
@@ -24,42 +23,7 @@ struct GlucoseTargetsView: ChartContent {
|
|
|
*/
|
|
*/
|
|
|
private func drawGlucoseTargets() -> some ChartContent {
|
|
private func drawGlucoseTargets() -> some ChartContent {
|
|
|
// Array to store target profiles for visualization
|
|
// Array to store target profiles for visualization
|
|
|
- var targetProfiles: [TargetProfile] = []
|
|
|
|
|
- let targets = bgTargets.targets
|
|
|
|
|
-
|
|
|
|
|
- // Generate profiles for today and tomorrow, because otherwise the targets would be cut off at midnight
|
|
|
|
|
- // TODO: maybe theres a better solution than introducing a second for loop?
|
|
|
|
|
- let days = [0, 1]
|
|
|
|
|
-
|
|
|
|
|
- for dayOffset in days {
|
|
|
|
|
- // Calculate base date for current day offset
|
|
|
|
|
- // it should be the start of the day of the startMarker
|
|
|
|
|
- let baseDate = Calendar.current.startOfDay(for: startMarker)
|
|
|
|
|
- .addingTimeInterval(TimeInterval(dayOffset * 24 * 60 * 60))
|
|
|
|
|
-
|
|
|
|
|
- for (index, target) in targets.enumerated() {
|
|
|
|
|
- // Calculate start time by adding target offset
|
|
|
|
|
- let startTime = baseDate.addingTimeInterval(TimeInterval(target.offset * 60))
|
|
|
|
|
-
|
|
|
|
|
- // Calculate end time - either next target or end of day
|
|
|
|
|
- let endTime: Date = {
|
|
|
|
|
- if index + 1 < targets.count {
|
|
|
|
|
- return baseDate.addingTimeInterval(TimeInterval(targets[index + 1].offset * 60))
|
|
|
|
|
- } else {
|
|
|
|
|
- return baseDate.addingTimeInterval(24 * 60 * 60)
|
|
|
|
|
- }
|
|
|
|
|
- }()
|
|
|
|
|
-
|
|
|
|
|
- // append target profile to array
|
|
|
|
|
- targetProfiles.append(
|
|
|
|
|
- TargetProfile(
|
|
|
|
|
- value: units == .mgdL ? target.low : target.low.asMmolL,
|
|
|
|
|
- startTime: startTime.timeIntervalSinceReferenceDate,
|
|
|
|
|
- endTime: endTime.timeIntervalSinceReferenceDate
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let targetProfiles: [TargetProfile] = processFetchedTargets(bgTargets)
|
|
|
|
|
|
|
|
// Draw target lines for each profile
|
|
// Draw target lines for each profile
|
|
|
return ForEach(targetProfiles, id: \.self) { profile in
|
|
return ForEach(targetProfiles, id: \.self) { profile in
|
|
@@ -67,16 +31,99 @@ struct GlucoseTargetsView: ChartContent {
|
|
|
x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.startTime)),
|
|
x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.startTime)),
|
|
|
y: .value("Target", profile.value)
|
|
y: .value("Target", profile.value)
|
|
|
)
|
|
)
|
|
|
- .lineStyle(.init(lineWidth: 0.5))
|
|
|
|
|
- .foregroundStyle(Color.green.opacity(0.8))
|
|
|
|
|
|
|
+ .lineStyle(.init(lineWidth: 1))
|
|
|
|
|
+ .foregroundStyle(Color.green.gradient)
|
|
|
|
|
|
|
|
LineMark(
|
|
LineMark(
|
|
|
x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.endTime)),
|
|
x: .value("Time", Date(timeIntervalSinceReferenceDate: profile.endTime)),
|
|
|
y: .value("Target", profile.value)
|
|
y: .value("Target", profile.value)
|
|
|
)
|
|
)
|
|
|
- .lineStyle(.init(lineWidth: 0.5))
|
|
|
|
|
- .foregroundStyle(Color.green.opacity(0.8))
|
|
|
|
|
|
|
+ .lineStyle(.init(lineWidth: 1))
|
|
|
|
|
+ .foregroundStyle(Color.green.gradient)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ Processes raw glucose target data into a list of target profiles for visualization.
|
|
|
|
|
+
|
|
|
|
|
+ - Parameter rawTargets: The raw glucose target data containing offset and glucose values.
|
|
|
|
|
+ - Returns: An array of `TargetProfile` objects, each representing a glucose target range for today and tomorrow.
|
|
|
|
|
+
|
|
|
|
|
+ The function:
|
|
|
|
|
+ - Converts glucose targets into profiles covering two consecutive days (today and tomorrow).
|
|
|
|
|
+ - Calculates start and end times for each target based on the offsets provided.
|
|
|
|
|
+ - Handles conversions between mg/dL and mmol/L as per user settings.
|
|
|
|
|
+ - Ensures targets span across midnight to avoid data cutoff.
|
|
|
|
|
+
|
|
|
|
|
+ Example:
|
|
|
|
|
+ For a target at offset 0 (midnight) with low glucose value 70 mg/dL, the function generates two profiles:
|
|
|
|
|
+ - One for today from midnight to the next target offset or end of the day.
|
|
|
|
|
+ - Another for tomorrow covering the same time range.
|
|
|
|
|
+ */
|
|
|
|
|
+ private func processFetchedTargets(_ rawTargets: BGTargets) -> [TargetProfile] {
|
|
|
|
|
+ var targetProfiles: [TargetProfile] = []
|
|
|
|
|
+
|
|
|
|
|
+ // Ensure there are targets to process
|
|
|
|
|
+ guard !rawTargets.targets.isEmpty else {
|
|
|
|
|
+ print("Warning: No targets to process in rawTargets.")
|
|
|
|
|
+ return []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let targets = rawTargets.targets
|
|
|
|
|
+
|
|
|
|
|
+ // Base date is the start of the day for the startMarker
|
|
|
|
|
+ let baseDate = Calendar.current.startOfDay(for: startMarker)
|
|
|
|
|
+
|
|
|
|
|
+ // Process each target twice: once for today and once for tomorrow
|
|
|
|
|
+ for index in 0 ..< (targets.count * 2) {
|
|
|
|
|
+ // Calculate the day offset (0 for today, 1 for tomorrow)
|
|
|
|
|
+ let dayOffset = index / targets.count
|
|
|
|
|
+ let targetIndex = index % targets.count
|
|
|
|
|
+
|
|
|
|
|
+ // Validate target index to ensure safety
|
|
|
|
|
+ guard targetIndex < targets.count else {
|
|
|
|
|
+ print("Error: Invalid target index \(targetIndex).")
|
|
|
|
|
+ continue
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Fetch the target for the current iteration
|
|
|
|
|
+ let target = targets[targetIndex]
|
|
|
|
|
+
|
|
|
|
|
+ // Calculate the time offset for the current day
|
|
|
|
|
+ let dayTimeOffset = TimeInterval(dayOffset * 24 * 60 * 60)
|
|
|
|
|
+
|
|
|
|
|
+ // Calculate the start time for the current target
|
|
|
|
|
+ let startTime = baseDate
|
|
|
|
|
+ .addingTimeInterval(dayTimeOffset)
|
|
|
|
|
+ .addingTimeInterval(TimeInterval(target.offset * 60))
|
|
|
|
|
+
|
|
|
|
|
+ // Calculate the end time for the current target
|
|
|
|
|
+ let endTime: Date = {
|
|
|
|
|
+ if targetIndex + 1 < targets.count {
|
|
|
|
|
+ // End time is the start time of the next target within the same day
|
|
|
|
|
+ return baseDate
|
|
|
|
|
+ .addingTimeInterval(dayTimeOffset)
|
|
|
|
|
+ .addingTimeInterval(TimeInterval(targets[targetIndex + 1].offset * 60))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // End time is the end of the day (midnight of the next day)
|
|
|
|
|
+ return baseDate.addingTimeInterval(dayTimeOffset + 24 * 60 * 60)
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
|
|
+ // Convert glucose value based on user unit preference (mg/dL or mmol/L)
|
|
|
|
|
+ let targetValue = units == .mgdL ? target.low : target.low.asMmolL
|
|
|
|
|
+
|
|
|
|
|
+ // Append the processed target profile to the list
|
|
|
|
|
+ targetProfiles.append(
|
|
|
|
|
+ TargetProfile(
|
|
|
|
|
+ value: targetValue,
|
|
|
|
|
+ startTime: startTime.timeIntervalSinceReferenceDate,
|
|
|
|
|
+ endTime: endTime.timeIntervalSinceReferenceDate
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return targetProfiles
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|