| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import Charts
- import CoreData
- import Foundation
- import SwiftUICore
- enum MainChartHelper {
- // Calculates the glucose value thats the nearest to parameter 'time'
- /// -Returns: A NSManagedObject of GlucoseStored
- /// it is thread safe as everything is executed on the main thread
- static func timeToNearestGlucose(glucoseValues: [GlucoseStored], time: TimeInterval) -> GlucoseStored? {
- guard !glucoseValues.isEmpty else {
- return nil
- }
- var low = 0
- var high = glucoseValues.count - 1
- var closestGlucose: GlucoseStored?
- // binary search to find next glucose
- while low <= high {
- let mid = low + (high - low) / 2
- let midTime = glucoseValues[mid].date?.timeIntervalSince1970 ?? 0
- if midTime == time {
- return glucoseValues[mid]
- } else if midTime < time {
- low = mid + 1
- } else {
- high = mid - 1
- }
- // update if necessary
- if closestGlucose == nil || abs(midTime - time) < abs(closestGlucose!.date?.timeIntervalSince1970 ?? 0 - time) {
- closestGlucose = glucoseValues[mid]
- }
- }
- return closestGlucose
- }
- enum Config {
- static let bolusSize: CGFloat = 5
- static let bolusScale: CGFloat = 1.8
- static let carbsSize: CGFloat = 5
- static let maxCarbSize: CGFloat = 30
- static let carbsScale: CGFloat = 0.3
- static let fpuSize: CGFloat = 10
- static let maxGlucose = 270
- static let minGlucose = 45
- }
- static func bolusOffset(units: GlucoseUnits) -> Decimal {
- units == .mgdL ? 30 : 1.66
- }
- static func calculateDuration(
- objectID: NSManagedObjectID,
- attribute: String,
- context: NSManagedObjectContext
- ) -> TimeInterval? {
- do {
- let object = try context.existingObject(with: objectID)
- if let attributeValue = object.value(forKey: attribute) as? NSDecimalNumber {
- let doubleValue = attributeValue.doubleValue
- if doubleValue != 0 {
- return TimeInterval(doubleValue * 60) // return seconds
- }
- } else {
- debugPrint("Attribute \(attribute) not found or not of type NSDecimalNumber")
- }
- } catch {
- debugPrint(
- "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to calculate duration for object with error: \(error.localizedDescription)"
- )
- }
- return nil
- }
- static func calculateTarget(objectID: NSManagedObjectID, attribute: String, context: NSManagedObjectContext) -> Decimal? {
- do {
- let object = try context.existingObject(with: objectID)
- if let attributeValue = object.value(forKey: attribute) as? NSDecimalNumber, attributeValue != 0 {
- return attributeValue.decimalValue
- }
- } catch {
- debugPrint(
- "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to calculate target for object with error: \(error.localizedDescription)"
- )
- }
- return nil
- }
- }
- // MARK: - Rule Marks and Charts configurations
- extension MainChartView {
- func drawCurrentTimeMarker() -> some ChartContent {
- RuleMark(
- x: .value(
- "",
- Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970)),
- unit: .second
- )
- ).lineStyle(.init(lineWidth: 2, dash: [3])).foregroundStyle(Color(.systemGray2))
- }
- func drawStartRuleMark() -> some ChartContent {
- RuleMark(
- x: .value(
- "",
- startMarker,
- unit: .second
- )
- ).foregroundStyle(Color.clear)
- }
- func drawEndRuleMark() -> some ChartContent {
- RuleMark(
- x: .value(
- "",
- endMarker,
- unit: .second
- )
- ).foregroundStyle(Color.clear)
- }
- func basalChartPlotStyle(_ plotContent: ChartPlotContent) -> some View {
- plotContent
- .rotationEffect(.degrees(180))
- .scaleEffect(x: -1, y: 1)
- }
- var mainChartXAxis: some AxisContent {
- AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ in
- if displayXgridLines {
- AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
- } else {
- AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
- }
- }
- }
- var basalChartXAxis: some AxisContent {
- AxisMarks(values: .stride(by: .hour, count: screenHours > 6 ? (screenHours > 12 ? 4 : 2) : 1)) { _ 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)
- .font(.footnote).foregroundStyle(Color.primary)
- }
- }
- var mainChartYAxis: some AxisContent {
- AxisMarks(position: .trailing) { 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 {
- /// fix offset between the two charts...
- if units == .mmolL {
- AxisTick(length: 7, stroke: .init(lineWidth: 7)).foregroundStyle(Color.clear)
- }
- AxisValueLabel().font(.footnote).foregroundStyle(Color.primary)
- }
- }
- }
- var cobIobChartYAxis: some AxisContent {
- AxisMarks(position: .trailing) { _ in
- if displayYgridLines {
- AxisGridLine(stroke: .init(lineWidth: 0.5, dash: [2, 3]))
- } else {
- AxisGridLine(stroke: .init(lineWidth: 0, dash: [2, 3]))
- }
- }
- }
- }
- // MARK: - Calculations and formatting
- extension MainChartView {
- func fullWidth(viewWidth: CGFloat) -> CGFloat {
- viewWidth * CGFloat(hours) / CGFloat(min(max(screenHours, 2), 24))
- }
- // Update start and end marker to fix scroll update problem with x axis
- func updateStartEndMarkers() {
- startMarker = Date(timeIntervalSince1970: TimeInterval(NSDate().timeIntervalSince1970 - 86400))
- let threeHourSinceNow = Date(timeIntervalSinceNow: TimeInterval(hours: 3))
- // min is 1.5h -> (1.5*1h = 1.5*(5*12*60))
- let dynamicFutureDateForCone = Date(timeIntervalSinceNow: TimeInterval(
- Int(1.5) * 5 * state
- .minCount * 60
- ))
- endMarker = state
- .forecastDisplayType == .lines ? threeHourSinceNow : dynamicFutureDateForCone <= threeHourSinceNow ?
- dynamicFutureDateForCone.addingTimeInterval(TimeInterval(minutes: 30)) : threeHourSinceNow
- }
- }
|