| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- import Charts
- import CoreData
- import SwiftDate
- import SwiftUI
- import Swinject
- extension Stat {
- struct RootView: BaseView {
- let resolver: Resolver
- @StateObject var state = StateModel()
- @FetchRequest(
- entity: Readings.entity(),
- sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
- format: "date >= %@", Calendar.current.startOfDay(for: Date()) as NSDate
- )
- ) var fetchedGlucoseDay: FetchedResults<Readings>
- @FetchRequest(
- entity: Readings.entity(),
- sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)],
- predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-24.hours.timeInterval) as NSDate)
- ) var fetchedGlucoseTwentyFourHours: FetchedResults<Readings>
- @FetchRequest(
- entity: Readings.entity(),
- sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)],
- predicate: NSPredicate(format: "date > %@", Date().addingTimeInterval(-7.days.timeInterval) as NSDate)
- ) var fetchedGlucoseWeek: FetchedResults<Readings>
- @FetchRequest(
- entity: Readings.entity(),
- sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
- format: "date > %@",
- Date().addingTimeInterval(-30.days.timeInterval) as NSDate
- )
- ) var fetchedGlucoseMonth: FetchedResults<Readings>
- @FetchRequest(
- entity: Readings.entity(),
- sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)], predicate: NSPredicate(
- format: "date > %@",
- Date().addingTimeInterval(-90.days.timeInterval) as NSDate
- )
- ) var fetchedGlucose: FetchedResults<Readings>
- @FetchRequest(
- entity: TDD.entity(),
- sortDescriptors: [NSSortDescriptor(key: "timestamp", ascending: false)]
- ) var fetchedTDD: FetchedResults<TDD>
- @FetchRequest(
- entity: LoopStatRecord.entity(),
- sortDescriptors: [NSSortDescriptor(key: "start", ascending: false)], predicate: NSPredicate(
- format: "start > %@",
- Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
- )
- ) var fetchedLoopStats: FetchedResults<LoopStatRecord>
- @FetchRequest(
- entity: InsulinDistribution.entity(),
- sortDescriptors: [NSSortDescriptor(key: "date", ascending: false)]
- ) var fetchedInsulin: FetchedResults<InsulinDistribution>
- enum Duration: String, CaseIterable, Identifiable {
- case Today
- case Day
- case Week
- case Month
- case Total
- var id: Self { self }
- }
- @State private var selectedDuration: Duration = .Today
- @State var paddingAmount: CGFloat? = 10
- @State var headline: Color = .secondary
- @State var days: Double = 0
- @State var pointSize: CGFloat = 3
- @State var conversionFactor = 0.0555
- @ViewBuilder func stats() -> some View {
- if state.layingChart ?? true {
- bloodGlucose
- Divider()
- } else {
- bloodGlucose
- standingTIRchart
- Divider()
- }
- loops
- Divider()
- hba1c
- }
- @ViewBuilder func chart() -> some View {
- switch selectedDuration {
- case .Today:
- glucoseChart
- case .Day:
- glucoseChartTwentyFourHours
- case .Week:
- glucoseChartWeek
- case .Month:
- glucoseChartMonth
- case .Total:
- glucoseChart90
- }
- if state.layingChart ?? true {
- tirChart
- }
- }
- var body: some View {
- ZStack {
- VStack(alignment: .center) {
- chart().padding(.top, 20)
- Divider()
- stats()
- Divider()
- Picker("Duration", selection: $selectedDuration) {
- ForEach(Duration.allCases) { duration in
- Text(duration.rawValue).tag(Optional(duration))
- }
- }
- .pickerStyle(.segmented)
- }
- }
- .onAppear(perform: configureView)
- .navigationBarTitle("Statistics")
- .navigationBarTitleDisplayMode(.automatic)
- .navigationBarItems(leading: Button("Close", action: state.hideModal))
- }
- var loops: some View {
- VStack {
- let loops_ = loopStats(fetchedLoopStats)
- HStack {
- ForEach(0 ..< loops_.count, id: \.self) { index in
- VStack {
- Text(loops_[index].string).font(.subheadline).foregroundColor(.secondary)
- Text(
- index == 0 ? loops_[index].double.formatted() : (
- index == 2 ? loops_[index].double
- .formatted(.number.grouping(.never).rounded().precision(.fractionLength(2))) :
- loops_[index]
- .double
- .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))
- )
- )
- }.padding(.horizontal, 6)
- }
- }
- }
- }
- var hba1c: some View {
- let useUnit: GlucoseUnits = (state.units == .mmolL && (state.overrideUnit ?? false)) ? .mgdL :
- (state.units == .mgdL && (state.overrideUnit ?? false) || state.units == .mmolL) ? .mmolL : .mgdL
- return HStack {
- let hba1cs = glucoseStats(fetchedGlucose)
- let hba1cString = (
- useUnit == .mmolL ? hba1cs.ifcc
- .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))) : hba1cs.ngsp
- .formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))
- + " %"
- )
- VStack {
- Text("HbA1C").font(.subheadline).foregroundColor(headline)
- HStack {
- VStack {
- Text(hba1cString)
- }
- }
- }.padding([.horizontal], 15)
- VStack {
- Text("SD").font(.subheadline).foregroundColor(.secondary)
- HStack {
- VStack {
- Text(
- hba1cs.sd
- .formatted(
- .number.grouping(.never).rounded()
- .precision(.fractionLength(state.units == .mmolL ? 1 : 0))
- )
- )
- }
- }
- }.padding([.horizontal], 15)
- VStack {
- Text("CV").font(.subheadline).foregroundColor(.secondary)
- HStack {
- VStack {
- Text(
- hba1cs.cv.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))
- )
- }
- }
- }.padding([.horizontal], 15)
- // if selectedDuration == .Total || selectedDuration == .Today {
- VStack {
- Text("Days").font(.subheadline).foregroundColor(.secondary)
- HStack {
- VStack {
- Text(numberOfDays.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1))))
- }
- }
- }.padding([.horizontal], 15)
- // }
- }
- }
- var bloodGlucose: some View {
- VStack {
- HStack {
- let bgs = glucoseStats(fetchedGlucose)
- VStack {
- HStack {
- Text(selectedDuration == .Today ? "Readings today" : "Readings / 24h").font(.subheadline)
- .foregroundColor(.secondary)
- }
- HStack {
- VStack {
- Text(
- bgs.readings.formatted(.number.grouping(.never).rounded().precision(.fractionLength(0)))
- )
- }
- }
- }
- VStack {
- HStack {
- Text("Average").font(.subheadline).foregroundColor(headline)
- }
- HStack {
- VStack {
- Text(
- bgs.average
- .formatted(
- .number.grouping(.never).rounded()
- .precision(.fractionLength(state.units == .mmolL ? 1 : 0))
- )
- )
- }
- }
- }
- VStack {
- HStack {
- Text("Median").font(.subheadline).foregroundColor(.secondary)
- }
- HStack {
- VStack {
- Text(
- bgs.median
- .formatted(
- .number.grouping(.never).rounded()
- .precision(.fractionLength(state.units == .mmolL ? 1 : 0))
- )
- )
- }
- }
- }
- }
- }
- }
- var numberOfDays: Double {
- let array = selectedDuration == .Today ? fetchedGlucoseDay : selectedDuration == .Day ?
- fetchedGlucoseTwentyFourHours :
- selectedDuration == .Week ? fetchedGlucoseWeek : selectedDuration == .Month ? fetchedGlucoseMonth :
- selectedDuration ==
- .Total ? fetchedGlucose : fetchedGlucoseDay
- let endIndex = array.count - 1
- var days = 0.0
- if endIndex > 0 {
- let firstElementTime = fetchedGlucose.first?.date ?? Date()
- let lastElementTime = fetchedGlucose[endIndex].date ?? Date()
- days = (firstElementTime - lastElementTime).timeInterval / 8.64E4
- }
- return days
- }
- var tirChart: some View {
- let array = selectedDuration == .Today ? fetchedGlucoseDay : selectedDuration == .Day ?
- fetchedGlucoseTwentyFourHours :
- selectedDuration == .Week ? fetchedGlucoseWeek : selectedDuration == .Month ? fetchedGlucoseMonth :
- selectedDuration ==
- .Total ? fetchedGlucose : fetchedGlucoseDay
- let fetched = tir(array)
- let data: [ShapeModel] = [
- .init(type: "Low", percent: fetched[0].decimal),
- .init(type: "In Range", percent: fetched[1].decimal),
- .init(type: "High", percent: fetched[2].decimal)
- ]
- return Chart(data) { shape in
- BarMark(
- x: .value("TIR", shape.percent)
- )
- .foregroundStyle(by: .value("Group", shape.type))
- .annotation(position: .overlay, alignment: .center) {
- Text(
- shape.percent == 0 ? "" : shape
- .percent < 12 ? "\(shape.percent, format: .number.precision(.fractionLength(0)))" :
- "\(shape.percent, format: .number.precision(.fractionLength(0))) %"
- )
- }
- }
- .chartXAxis(.hidden)
- .chartForegroundStyleScale(["Low": .red, "In Range": .green, "High": .orange]).frame(maxHeight: 55)
- }
- var standingTIRchart: some View {
- let array = selectedDuration == .Today ? fetchedGlucoseDay : selectedDuration == .Day ?
- fetchedGlucoseTwentyFourHours :
- selectedDuration == .Week ? fetchedGlucoseWeek : selectedDuration == .Month ? fetchedGlucoseMonth :
- selectedDuration == .Total ? fetchedGlucose : fetchedGlucoseDay
- let fetched = tir(array)
- let data: [ShapeModel] = [
- .init(type: "Low", percent: fetched[0].decimal),
- .init(type: "In Range", percent: fetched[1].decimal),
- .init(type: "High", percent: fetched[2].decimal)
- ]
- return VStack(alignment: .center) {
- Chart(data) { shape in
- BarMark(
- x: .value("Shape", shape.type),
- y: .value("Percentage", shape.percent)
- )
- .foregroundStyle(by: .value("Group", shape.type))
- .annotation(position: shape.percent < 5 ? .top : .overlay, alignment: .center) {
- Text(shape.percent == 0 ? "" : "\(shape.percent, format: .number.precision(.fractionLength(0))) %")
- }
- }
- .chartYAxis(.hidden)
- .chartLegend(.hidden)
- .chartForegroundStyleScale(["Low": .red, "In Range": .green, "High": .orange])
- }
- }
- var glucoseChart: some View {
- let count = fetchedGlucoseDay.count
- let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- return Chart {
- ForEach(fetchedGlucoseDay.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.orange)
- .symbolSize(count < 20 ? 30 : 12)
- }
- ForEach(
- fetchedGlucoseDay
- .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
- id: \.date
- ) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.green)
- .symbolSize(count < 20 ? 30 : 12)
- }
- ForEach(fetchedGlucoseDay.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.red)
- .symbolSize(count < 20 ? 30 : 12)
- }
- }
- .chartYAxis {
- AxisMarks(
- values: [
- 0,
- lowLimit,
- highLimit,
- state.units == .mmolL ? 15 : 270
- ]
- )
- }
- }
- var glucoseChartTwentyFourHours: some View {
- let count = fetchedGlucoseTwentyFourHours.count
- let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- return Chart {
- ForEach(fetchedGlucoseTwentyFourHours.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.orange)
- .symbolSize(count < 20 ? 20 : 10)
- }
- ForEach(
- fetchedGlucoseTwentyFourHours
- .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
- id: \.date
- ) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.green)
- .symbolSize(count < 20 ? 20 : 10)
- }
- ForEach(fetchedGlucoseTwentyFourHours.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.red)
- .symbolSize(count < 20 ? 20 : 10)
- }
- }
- .chartYAxis {
- AxisMarks(
- values: [
- 0,
- lowLimit,
- highLimit,
- state.units == .mmolL ? 15 : 270
- ]
- )
- } }
- var glucoseChartWeek: some View {
- let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- return Chart {
- ForEach(fetchedGlucoseWeek.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.orange)
- .symbolSize(5)
- }
- ForEach(
- fetchedGlucoseWeek
- .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
- id: \.date
- ) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.green)
- .symbolSize(5)
- }
- ForEach(fetchedGlucoseWeek.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.red)
- .symbolSize(5)
- }
- }
- .chartYAxis {
- AxisMarks(
- values: [
- 0,
- lowLimit,
- highLimit,
- state.units == .mmolL ? 15 : 270
- ]
- )
- }
- }
- var glucoseChartMonth: some View {
- let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- return Chart {
- ForEach(fetchedGlucoseMonth.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.orange)
- .symbolSize(2)
- }
- ForEach(
- fetchedGlucoseMonth
- .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
- id: \.date
- ) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.green)
- .symbolSize(2)
- }
- ForEach(fetchedGlucoseMonth.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.red)
- .symbolSize(2)
- }
- }
- .chartYAxis {
- AxisMarks(
- values: [
- 0,
- lowLimit,
- highLimit,
- state.units == .mmolL ? 15 : 270
- ]
- )
- }
- }
- var glucoseChart90: some View {
- let lowLimit = (state.lowLimit ?? (4 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- let highLimit = (state.highLimit ?? (10 * 0.0555)) * (state.units == .mmolL ? Decimal(conversionFactor) : 1)
- return Chart {
- ForEach(fetchedGlucose.filter({ $0.glucose > Int(state.highLimit ?? 145) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("Low", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.orange)
- .symbolSize(2)
- }
- ForEach(
- fetchedGlucose
- .filter({ $0.glucose >= Int(state.lowLimit ?? 70) && $0.glucose <= Int(state.highLimit ?? 145) }),
- id: \.date
- ) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("In Range", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.green)
- .symbolSize(2)
- }
- ForEach(fetchedGlucose.filter({ $0.glucose < Int(state.lowLimit ?? 70) }), id: \.date) { item in
- PointMark(
- x: .value("Date", item.date ?? Date()),
- y: .value("High", Double(item.glucose) * (state.units == .mmolL ? conversionFactor : 1))
- )
- .foregroundStyle(.red)
- .symbolSize(2)
- }
- }
- .chartYAxis {
- AxisMarks(
- values: [
- 0,
- lowLimit,
- highLimit,
- state.units == .mmolL ? 15 : 270
- ]
- )
- }
- }
- private func loopStats(_ loops: FetchedResults<LoopStatRecord>) -> [(double: Double, string: String)] {
- guard (loops.first?.start) != nil else { return [] }
- var i = 0.0
- var minimumInt = 999.0
- var maximumInt = 0.0
- var timeIntervalLoops = 0.0
- var previousTimeLoop = loops.first?.end ?? Date()
- var timeIntervalLoopArray: [Double] = []
- let durationArray = loops.compactMap({ each in each.duration })
- let durationArrayCount = durationArray.count
- // var durationAverage = durationArray.reduce(0, +) / Double(durationArrayCount)
- let medianDuration = medianCalculationDouble(array: durationArray)
- let successsNR = loops.compactMap({ each in each.loopStatus }).filter({ each in each!.contains("Success") }).count
- let errorNR = durationArrayCount - successsNR
- let successRate: Double? = (Double(successsNR) / Double(successsNR + errorNR)) * 100
- for each in loops {
- if let loopEnd = each.end {
- i += 1
- timeIntervalLoops = (previousTimeLoop - (each.start ?? previousTimeLoop)).timeInterval / 60
- if timeIntervalLoops > 0.0, i != 1 {
- timeIntervalLoopArray.append(timeIntervalLoops)
- }
- if timeIntervalLoops > maximumInt {
- maximumInt = timeIntervalLoops
- }
- if timeIntervalLoops < minimumInt, i != 1 {
- minimumInt = timeIntervalLoops
- }
- previousTimeLoop = loopEnd
- }
- }
- // Average Loop Interval in minutes
- let timeOfFirstIndex = loops.first?.start ?? Date()
- let lastIndexWithTimestamp = loops.count - 1
- let timeOfLastIndex = loops[lastIndexWithTimestamp].end ?? Date()
- let averageInterval = (timeOfFirstIndex - timeOfLastIndex).timeInterval / 60 / Double(errorNR + successsNR)
- if minimumInt == 999.0 {
- minimumInt = 0.0
- }
- var array: [(double: Double, string: String)] = []
- array.append((double: Double(successsNR + errorNR), string: "Loops"))
- array.append((double: averageInterval, string: "Interval"))
- array.append((double: medianDuration, string: "Duration"))
- array.append((double: successRate ?? 100, string: "%"))
- return array
- }
- private func medianCalculation(array: [Int]) -> Double {
- guard !array.isEmpty else {
- return 0
- }
- let sorted = array.sorted()
- let length = array.count
- if length % 2 == 0 {
- return Double((sorted[length / 2 - 1] + sorted[length / 2]) / 2)
- }
- return Double(sorted[length / 2])
- }
- private func medianCalculationDouble(array: [Double]) -> Double {
- guard !array.isEmpty else {
- return 0
- }
- let sorted = array.sorted()
- let length = array.count
- if length % 2 == 0 {
- return (sorted[length / 2 - 1] + sorted[length / 2]) / 2
- }
- return sorted[length / 2]
- }
- private func glucoseStats(_ glucose_90: FetchedResults<Readings>)
- -> (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
- {
- var numberOfDays: Double = 0
- let endIndex = glucose_90.count - 1
- if endIndex > 0 {
- let firstElementTime = glucose_90[0].date ?? Date()
- let lastElementTime = glucose_90[endIndex].date ?? Date()
- numberOfDays = (firstElementTime - lastElementTime).timeInterval / 8.64E4
- }
- var duration = 1
- var denominator: Double = 1
- switch selectedDuration {
- case .Today:
- let minutesSinceMidnight = Calendar.current.component(.hour, from: Date()) * 60 + Calendar.current
- .component(.minute, from: Date())
- duration = minutesSinceMidnight
- denominator = 1
- case .Day:
- duration = 1 * 1440
- denominator = 1
- case .Week:
- duration = 7 * 1440
- if numberOfDays > 7 { denominator = 7 } else { denominator = numberOfDays }
- case .Month:
- duration = 30 * 1440
- if numberOfDays > 30 { denominator = 30 } else { denominator = numberOfDays }
- case .Total:
- duration = 90 * 1440
- if numberOfDays >= 90 { denominator = 90 } else { denominator = numberOfDays }
- }
- let timeAgo = Date().addingTimeInterval(-duration.minutes.timeInterval)
- let glucose = glucose_90.filter({ ($0.date ?? Date()) >= timeAgo })
- let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
- let sumReadings = justGlucoseArray.reduce(0, +)
- let countReadings = justGlucoseArray.count
- let glucoseAverage = Double(sumReadings) / Double(countReadings)
- let medianGlucose = medianCalculation(array: justGlucoseArray)
- var NGSPa1CStatisticValue = 0.0
- var IFCCa1CStatisticValue = 0.0
- if numberOfDays > 0 {
- NGSPa1CStatisticValue = (glucoseAverage + 46.7) / 28.7 // NGSP (%)
- IFCCa1CStatisticValue = 10.929 *
- (NGSPa1CStatisticValue - 2.152) // IFCC (mmol/mol) A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15)
- }
- var sumOfSquares = 0.0
- for array in justGlucoseArray {
- sumOfSquares += pow(Double(array) - Double(glucoseAverage), 2)
- }
- var sd = 0.0
- var cv = 0.0
- // Avoid division by zero
- if glucoseAverage > 0 {
- sd = sqrt(sumOfSquares / Double(countReadings))
- cv = sd / Double(glucoseAverage) * 100
- }
- var output: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double)
- output = (
- ifcc: IFCCa1CStatisticValue,
- ngsp: NGSPa1CStatisticValue,
- average: glucoseAverage * (state.units == .mmolL ? conversionFactor : 1),
- median: medianGlucose * (state.units == .mmolL ? conversionFactor : 1),
- sd: sd * (state.units == .mmolL ? conversionFactor : 1), cv: cv,
- readings: Double(countReadings) / denominator
- )
- return output
- }
- private func tir(_ glucose_90: FetchedResults<Readings>) -> [(decimal: Decimal, string: String)] {
- var duration = 1
- switch selectedDuration {
- case .Today:
- let minutesSinceMidnight = Calendar.current.component(.hour, from: Date()) * 60 + Calendar.current
- .component(.minute, from: Date())
- duration = minutesSinceMidnight
- case .Day:
- duration = 1 * 1440
- case .Week:
- duration = 7 * 1440
- case .Month:
- duration = 30 * 1440
- case .Total:
- duration = 90 * 1440
- }
- let hypoLimit = Int(state.lowLimit ?? 70)
- let hyperLimit = Int(state.highLimit ?? 145)
- let timeAgo = Date().addingTimeInterval(-duration.minutes.timeInterval)
- let glucose = glucose_90.filter({ ($0.date ?? Date()) >= timeAgo })
- let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) })
- let totalReadings = justGlucoseArray.count
- let hyperArray = glucose.filter({ $0.glucose >= hyperLimit })
- let hyperReadings = hyperArray.compactMap({ each in each.glucose as Int16 }).count
- let hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100
- let hypoArray = glucose.filter({ $0.glucose <= hypoLimit })
- let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count
- let hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100
- let tir = 100 - (hypoPercentage + hyperPercentage)
- var array: [(decimal: Decimal, string: String)] = []
- array.append((decimal: Decimal(hypoPercentage), string: "Low"))
- array.append((decimal: Decimal(tir), string: "NormaL"))
- array.append((decimal: Decimal(hyperPercentage), string: "High"))
- return array
- }
- private func colorOfGlucose(_ index: Int) -> Color {
- let whichIndex = index
- switch whichIndex {
- case 0:
- return .red
- case 1:
- return .green
- case 2:
- return .orange
- default:
- return .primary
- }
- }
- }
- }
|