| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207 |
- // LoopFollow
- // Graphs.swift
- import Charts
- import Foundation
- import UIKit
- import Charts
- /// Fill colors for the override and temp-target bars on the BG graph.
- ///
- /// Loop draws overrides green and temp targets purple, while Trio (and other
- /// OpenAPS-based algorithms) use the inverse — overrides purple, temp targets
- /// green. We follow the active backend's convention so the colors match the
- /// looping app the user is running.
- enum TreatmentGraphColors {
- static var override: NSUIColor {
- Storage.shared.device.value == "Loop" ? .systemGreen : .systemPurple
- }
- static var tempTarget: NSUIColor {
- Storage.shared.device.value == "Loop" ? .systemPurple : .systemGreen
- }
- }
- enum GraphDataIndex: Int {
- case bg = 0
- case prediction = 1
- case basal = 2
- case bolus = 3
- case carbs = 4
- case basalScheduled = 5
- case override = 6
- case bgCheck = 7
- case suspend = 8
- case resumePump = 9
- case sensorStart = 10
- case note = 11
- case ztPrediction = 12
- case iobPrediction = 13
- case cobPrediction = 14
- case uamPrediction = 15
- case smb = 16
- case tempTarget = 17
- case predictionCone = 18
- case yesterday = 19
- }
- extension GraphDataIndex {
- var description: String {
- switch self {
- case .bg: return "BG"
- case .prediction: return "Prediction"
- case .basal: return "Basal"
- case .bolus: return "Bolus"
- case .carbs: return "Carbs"
- case .basalScheduled: return "Basal Scheduled"
- case .override: return "Override"
- case .bgCheck: return "BG Check"
- case .suspend: return "Suspend"
- case .resumePump: return "Resume Pump"
- case .sensorStart: return "Sensor Start"
- case .note: return "Note"
- case .ztPrediction: return "ZT Prediction"
- case .iobPrediction: return "IOB Prediction"
- case .cobPrediction: return "COB Prediction"
- case .uamPrediction: return "UAM Prediction"
- case .smb: return "SMB"
- case .tempTarget: return "Temp Target"
- case .predictionCone: return "Prediction Cone"
- case .yesterday: return "Yesterday"
- }
- }
- }
- class CompositeRenderer: LineChartRenderer {
- let tempTargetRenderer: TempTargetRenderer
- let triangleRenderer: TriangleRenderer
- let coneRenderer: ConeOfUncertaintyRenderer
- init(dataProvider: LineChartDataProvider?, animator: Animator?, viewPortHandler: ViewPortHandler?, tempTargetDataSetIndex: Int, smbDataSetIndex: Int, coneDataSetIndex: Int) {
- tempTargetRenderer = TempTargetRenderer(
- dataProvider: dataProvider,
- animator: animator,
- viewPortHandler: viewPortHandler,
- tempTargetDataSetIndex: tempTargetDataSetIndex
- )
- triangleRenderer = TriangleRenderer(
- dataProvider: dataProvider,
- animator: animator,
- viewPortHandler: viewPortHandler,
- smbDataSetIndex: smbDataSetIndex
- )
- coneRenderer = ConeOfUncertaintyRenderer(
- dataProvider: dataProvider,
- animator: animator,
- viewPortHandler: viewPortHandler,
- coneDataSetIndex: coneDataSetIndex
- )
- super.init(dataProvider: dataProvider!, animator: animator!, viewPortHandler: viewPortHandler!)
- }
- override func drawExtras(context: CGContext) {
- super.drawExtras(context: context)
- coneRenderer.drawExtras(context: context)
- tempTargetRenderer.drawExtras(context: context)
- triangleRenderer.drawExtras(context: context)
- }
- }
- class TriangleRenderer: LineChartRenderer {
- let smbDataSetIndex: Int
- init(dataProvider: LineChartDataProvider?, animator: Animator?, viewPortHandler: ViewPortHandler?, smbDataSetIndex: Int) {
- self.smbDataSetIndex = smbDataSetIndex
- super.init(dataProvider: dataProvider!, animator: animator!, viewPortHandler: viewPortHandler!)
- }
- override func drawExtras(context: CGContext) {
- super.drawExtras(context: context)
- guard let dataProvider = dataProvider else { return }
- if dataProvider.lineData?.dataSets.count ?? 0 > smbDataSetIndex, let lineDataSet = dataProvider.lineData?.dataSets[smbDataSetIndex] as? LineChartDataSet {
- let trans = dataProvider.getTransformer(forAxis: lineDataSet.axisDependency)
- let phaseY = animator.phaseY
- for j in 0 ..< lineDataSet.entryCount {
- guard let e = lineDataSet.entryForIndex(j) else { continue }
- let pt = trans.pixelForValues(x: e.x, y: e.y * phaseY)
- context.saveGState()
- context.beginPath()
- context.move(to: CGPoint(x: pt.x, y: pt.y + 9))
- context.addLine(to: CGPoint(x: pt.x - 5, y: pt.y - 1))
- context.addLine(to: CGPoint(x: pt.x + 5, y: pt.y - 1))
- context.closePath()
- context.setFillColor(lineDataSet.circleColors.first!.cgColor)
- context.fillPath()
- context.restoreGState()
- }
- }
- }
- }
- class TempTargetChartDataEntry: ChartDataEntry {
- var xStart: Double = 0.0
- var xEnd: Double = 0.0
- var yTop: Double = 0.0
- var yBottom: Double = 0.0
- required init() {
- super.init()
- }
- init(xStart: Double, xEnd: Double, yTop: Double, yBottom: Double, data: Any?) {
- self.xStart = xStart
- self.xEnd = xEnd
- self.yTop = yTop
- self.yBottom = yBottom
- super.init(x: xStart, y: yTop)
- self.data = data
- }
- override func copy(with _: NSZone? = nil) -> Any {
- let copy = TempTargetChartDataEntry(
- xStart: xStart,
- xEnd: xEnd,
- yTop: yTop,
- yBottom: yBottom,
- data: data
- )
- return copy
- }
- }
- class TempTargetRenderer: LineChartRenderer {
- let tempTargetDataSetIndex: Int
- init(dataProvider: LineChartDataProvider?, animator: Animator?, viewPortHandler: ViewPortHandler?, tempTargetDataSetIndex: Int) {
- self.tempTargetDataSetIndex = tempTargetDataSetIndex
- super.init(dataProvider: dataProvider!, animator: animator!, viewPortHandler: viewPortHandler!)
- }
- override func drawExtras(context: CGContext) {
- super.drawExtras(context: context)
- guard let dataProvider = dataProvider else { return }
- if dataProvider.lineData?.dataSets.count ?? 0 > tempTargetDataSetIndex,
- let lineDataSet = dataProvider.lineData?.dataSets[tempTargetDataSetIndex] as? LineChartDataSet
- {
- let trans = dataProvider.getTransformer(forAxis: lineDataSet.axisDependency)
- let phaseY = animator.phaseY
- for i in 0 ..< lineDataSet.entryCount {
- guard let entry = lineDataSet.entryForIndex(i) as? TempTargetChartDataEntry else { continue }
- let xStart = entry.xStart
- let xEnd = entry.xEnd
- let yTop = entry.yTop * phaseY
- let yBottom = entry.yBottom * phaseY
- let leftTop = trans.pixelForValues(x: xStart, y: yTop)
- let rightBottom = trans.pixelForValues(x: xEnd, y: yBottom)
- var rect = CGRect(x: leftTop.x, y: leftTop.y, width: rightBottom.x - leftTop.x, height: rightBottom.y - leftTop.y)
- if rect.width < 0 {
- rect.origin.x += rect.width
- rect.size.width = abs(rect.width)
- }
- if rect.height < 0 {
- rect.origin.y += rect.height
- rect.size.height = abs(rect.height)
- }
- context.saveGState()
- context.setFillColor(TreatmentGraphColors.tempTarget.withAlphaComponent(0.5).cgColor)
- context.fill(rect)
- context.restoreGState()
- }
- }
- }
- }
- let ScaleXMax: Double = 150.0
- extension MainViewController {
- private func graphRangeThresholds() -> (low: Double, high: Double) {
- UnitSettingsStore.shared.effectiveThresholds()
- }
- func updateChartRenderers() {
- let tempTargetDataIndex = GraphDataIndex.tempTarget.rawValue
- let smbDataIndex = GraphDataIndex.smb.rawValue
- let coneDataIndex = GraphDataIndex.predictionCone.rawValue
- let compositeRenderer = CompositeRenderer(
- dataProvider: BGChart,
- animator: BGChart.chartAnimator,
- viewPortHandler: BGChart.viewPortHandler,
- tempTargetDataSetIndex: tempTargetDataIndex,
- smbDataSetIndex: smbDataIndex,
- coneDataSetIndex: coneDataIndex
- )
- BGChart.renderer = compositeRenderer
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- }
- func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight _: Highlight) {
- if chartView == BGChartFull {
- BGChart.moveViewToX(entry.x)
- }
- if entry.data as? String == "hide" {
- BGChart.highlightValue(nil, callDelegate: false)
- }
- }
- func chartScaled(_: ChartViewBase, scaleX _: CGFloat, scaleY _: CGFloat) {
- // dont store huge values
- var scale = Double(BGChart.scaleX)
- if scale > ScaleXMax {
- scale = ScaleXMax
- }
- Storage.shared.chartScaleX.value = scale
- }
- func createGraph() {
- // Create the BG Graph Data
- let bgChartEntry = [ChartDataEntry]()
- let maxBG = Storage.shared.minBGScale.value
- // Setup BG line details
- let lineBG = LineChartDataSet(entries: bgChartEntry, label: "")
- lineBG.circleRadius = CGFloat(globalVariables.dotBG)
- lineBG.circleColors = [NSUIColor.systemGreen]
- lineBG.drawCircleHoleEnabled = false
- lineBG.axisDependency = YAxis.AxisDependency.right
- lineBG.highlightEnabled = true
- lineBG.drawValuesEnabled = false
- if Storage.shared.showLines.value {
- lineBG.lineWidth = 2
- } else {
- lineBG.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- lineBG.drawCirclesEnabled = true
- } else {
- lineBG.drawCirclesEnabled = false
- }
- lineBG.setDrawHighlightIndicators(false)
- lineBG.valueFont.withSize(50)
- // Setup Prediction line details
- let predictionChartEntry = [ChartDataEntry]()
- let linePrediction = LineChartDataSet(entries: predictionChartEntry, label: "")
- linePrediction.circleRadius = CGFloat(globalVariables.dotBG)
- linePrediction.circleColors = [NSUIColor.systemPurple]
- linePrediction.colors = [NSUIColor.systemPurple]
- linePrediction.drawCircleHoleEnabled = false
- linePrediction.axisDependency = YAxis.AxisDependency.right
- linePrediction.highlightEnabled = true
- linePrediction.drawValuesEnabled = false
- if Storage.shared.showLines.value {
- linePrediction.lineWidth = 2
- } else {
- linePrediction.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- linePrediction.drawCirclesEnabled = true
- } else {
- linePrediction.drawCirclesEnabled = false
- }
- linePrediction.setDrawHighlightIndicators(false)
- linePrediction.valueFont.withSize(50)
- // create Basal graph data
- let chartEntry = [ChartDataEntry]()
- let maxBasal = Storage.shared.minBasalScale.value
- let lineBasal = LineChartDataSet(entries: chartEntry, label: "")
- lineBasal.setDrawHighlightIndicators(false)
- lineBasal.setColor(NSUIColor.systemBlue, alpha: 0.5)
- lineBasal.lineWidth = 0
- lineBasal.drawFilledEnabled = true
- lineBasal.fillColor = NSUIColor.systemBlue
- lineBasal.fillAlpha = 0.5
- lineBasal.drawCirclesEnabled = false
- lineBasal.axisDependency = YAxis.AxisDependency.left
- lineBasal.highlightEnabled = true
- lineBasal.drawValuesEnabled = false
- lineBasal.fillFormatter = basalFillFormatter()
- // Boluses
- let chartEntryBolus = [ChartDataEntry]()
- let lineBolus = LineChartDataSet(entries: chartEntryBolus, label: "")
- lineBolus.circleRadius = CGFloat(globalVariables.dotBolus)
- lineBolus.circleColors = [NSUIColor.systemBlue.withAlphaComponent(0.75)]
- lineBolus.drawCircleHoleEnabled = false
- lineBolus.setDrawHighlightIndicators(false)
- lineBolus.setColor(NSUIColor.systemBlue, alpha: 1.0)
- lineBolus.lineWidth = 0
- lineBolus.axisDependency = YAxis.AxisDependency.right
- lineBolus.valueFormatter = ChartYDataValueFormatter()
- lineBolus.valueTextColor = NSUIColor.label
- lineBolus.fillColor = NSUIColor.systemBlue
- lineBolus.fillAlpha = 0.6
- lineBolus.drawCirclesEnabled = true
- lineBolus.drawFilledEnabled = false
- lineBolus.drawValuesEnabled = Storage.shared.showValues.value
- lineBolus.highlightEnabled = true
- // Carbs
- let chartEntryCarbs = [ChartDataEntry]()
- let lineCarbs = LineChartDataSet(entries: chartEntryCarbs, label: "")
- lineCarbs.circleRadius = CGFloat(globalVariables.dotCarb)
- lineCarbs.circleColors = [NSUIColor.systemOrange.withAlphaComponent(0.75)]
- lineCarbs.drawCircleHoleEnabled = false
- lineCarbs.setDrawHighlightIndicators(false)
- lineCarbs.setColor(NSUIColor.systemBlue, alpha: 1.0)
- lineCarbs.lineWidth = 0
- lineCarbs.axisDependency = YAxis.AxisDependency.right
- lineCarbs.valueFormatter = ChartYDataValueFormatter()
- lineCarbs.valueTextColor = NSUIColor.label
- lineCarbs.fillColor = NSUIColor.systemOrange
- lineCarbs.fillAlpha = 0.6
- lineCarbs.drawCirclesEnabled = true
- lineCarbs.drawFilledEnabled = false
- lineCarbs.drawValuesEnabled = Storage.shared.showValues.value
- lineCarbs.highlightEnabled = true
- // create Scheduled Basal graph data
- let chartBasalScheduledEntry = [ChartDataEntry]()
- let lineBasalScheduled = LineChartDataSet(entries: chartBasalScheduledEntry, label: "")
- lineBasalScheduled.setDrawHighlightIndicators(false)
- lineBasalScheduled.setColor(NSUIColor.systemBlue, alpha: 0.8)
- lineBasalScheduled.lineWidth = 2
- lineBasalScheduled.drawFilledEnabled = false
- lineBasalScheduled.drawCirclesEnabled = false
- lineBasalScheduled.axisDependency = YAxis.AxisDependency.left
- lineBasalScheduled.highlightEnabled = false
- lineBasalScheduled.drawValuesEnabled = false
- lineBasalScheduled.lineDashLengths = [10.0, 5.0]
- // create Override graph data
- let chartOverrideEntry = [ChartDataEntry]()
- let lineOverride = LineChartDataSet(entries: chartOverrideEntry, label: "")
- lineOverride.setDrawHighlightIndicators(false)
- lineOverride.lineWidth = 0
- lineOverride.drawFilledEnabled = true
- lineOverride.fillFormatter = OverrideFillFormatter()
- lineOverride.fillColor = TreatmentGraphColors.override
- lineOverride.fillAlpha = 0.6
- lineOverride.drawCirclesEnabled = false
- lineOverride.axisDependency = YAxis.AxisDependency.right
- lineOverride.highlightEnabled = true
- lineOverride.drawValuesEnabled = false
- // BG Check
- let chartEntryBGCheck = [ChartDataEntry]()
- let lineBGCheck = LineChartDataSet(entries: chartEntryBGCheck, label: "")
- lineBGCheck.circleRadius = CGFloat(globalVariables.dotOther)
- lineBGCheck.circleColors = [NSUIColor.systemRed.withAlphaComponent(0.75)]
- lineBGCheck.drawCircleHoleEnabled = false
- lineBGCheck.setDrawHighlightIndicators(false)
- lineBGCheck.setColor(NSUIColor.systemRed, alpha: 1.0)
- lineBGCheck.drawCirclesEnabled = true
- lineBGCheck.lineWidth = 0
- lineBGCheck.highlightEnabled = true
- lineBGCheck.axisDependency = YAxis.AxisDependency.right
- lineBGCheck.valueFormatter = ChartYDataValueFormatter()
- lineBGCheck.drawValuesEnabled = false
- // Suspend Pump
- let chartEntrySuspend = [ChartDataEntry]()
- let lineSuspend = LineChartDataSet(entries: chartEntrySuspend, label: "")
- lineSuspend.circleRadius = CGFloat(globalVariables.dotOther)
- lineSuspend.circleColors = [NSUIColor.systemTeal.withAlphaComponent(0.75)]
- lineSuspend.drawCircleHoleEnabled = false
- lineSuspend.setDrawHighlightIndicators(false)
- lineSuspend.setColor(NSUIColor.systemGray2, alpha: 1.0)
- lineSuspend.drawCirclesEnabled = true
- lineSuspend.lineWidth = 0
- lineSuspend.highlightEnabled = true
- lineSuspend.axisDependency = YAxis.AxisDependency.right
- lineSuspend.valueFormatter = ChartYDataValueFormatter()
- lineSuspend.drawValuesEnabled = false
- // Resume Pump
- let chartEntryResume = [ChartDataEntry]()
- let lineResume = LineChartDataSet(entries: chartEntryResume, label: "")
- lineResume.circleRadius = CGFloat(globalVariables.dotOther)
- lineResume.circleColors = [NSUIColor.systemTeal.withAlphaComponent(0.75)]
- lineResume.drawCircleHoleEnabled = false
- lineResume.setDrawHighlightIndicators(false)
- lineResume.setColor(NSUIColor.systemGray4, alpha: 1.0)
- lineResume.drawCirclesEnabled = true
- lineResume.lineWidth = 0
- lineResume.highlightEnabled = true
- lineResume.axisDependency = YAxis.AxisDependency.right
- lineResume.valueFormatter = ChartYDataValueFormatter()
- lineResume.drawValuesEnabled = false
- // Sensor Start
- let chartEntrySensor = [ChartDataEntry]()
- let lineSensor = LineChartDataSet(entries: chartEntrySensor, label: "")
- lineSensor.circleRadius = CGFloat(globalVariables.dotOther)
- lineSensor.circleColors = [NSUIColor.systemIndigo.withAlphaComponent(0.75)]
- lineSensor.drawCircleHoleEnabled = false
- lineSensor.setDrawHighlightIndicators(false)
- lineSensor.setColor(NSUIColor.systemGray3, alpha: 1.0)
- lineSensor.drawCirclesEnabled = true
- lineSensor.lineWidth = 0
- lineSensor.highlightEnabled = true
- lineSensor.axisDependency = YAxis.AxisDependency.right
- lineSensor.valueFormatter = ChartYDataValueFormatter()
- lineSensor.drawValuesEnabled = false
- // Notes
- let chartEntryNote = [ChartDataEntry]()
- let lineNote = LineChartDataSet(entries: chartEntryNote, label: "")
- lineNote.circleRadius = CGFloat(globalVariables.dotOther)
- lineNote.circleColors = [NSUIColor.systemGray.withAlphaComponent(0.75)]
- lineNote.drawCircleHoleEnabled = false
- lineNote.setDrawHighlightIndicators(false)
- lineNote.setColor(NSUIColor.systemGray3, alpha: 1.0)
- lineNote.drawCirclesEnabled = true
- lineNote.lineWidth = 0
- lineNote.highlightEnabled = true
- lineNote.axisDependency = YAxis.AxisDependency.right
- lineNote.valueFormatter = ChartYDataValueFormatter()
- lineNote.drawValuesEnabled = false
- // Setup COB Prediction line details
- let COBpredictionChartEntry = [ChartDataEntry]()
- let COBlinePrediction = LineChartDataSet(entries: COBpredictionChartEntry, label: "")
- COBlinePrediction.circleRadius = CGFloat(globalVariables.dotBG)
- COBlinePrediction.circleColors = [NSUIColor.systemPurple]
- COBlinePrediction.colors = [NSUIColor.systemPurple]
- COBlinePrediction.drawCircleHoleEnabled = false
- COBlinePrediction.axisDependency = YAxis.AxisDependency.right
- COBlinePrediction.highlightEnabled = true
- COBlinePrediction.drawValuesEnabled = false
- if Storage.shared.showLines.value {
- COBlinePrediction.lineWidth = 2
- } else {
- COBlinePrediction.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- COBlinePrediction.drawCirclesEnabled = true
- } else {
- COBlinePrediction.drawCirclesEnabled = false
- }
- COBlinePrediction.setDrawHighlightIndicators(false)
- COBlinePrediction.valueFont.withSize(50)
- // Setup IOB Prediction line details
- let IOBpredictionChartEntry = [ChartDataEntry]()
- let IOBlinePrediction = LineChartDataSet(entries: IOBpredictionChartEntry, label: "")
- IOBlinePrediction.circleRadius = CGFloat(globalVariables.dotBG)
- IOBlinePrediction.circleColors = [NSUIColor.systemPurple]
- IOBlinePrediction.colors = [NSUIColor.systemPurple]
- IOBlinePrediction.drawCircleHoleEnabled = false
- IOBlinePrediction.axisDependency = YAxis.AxisDependency.right
- IOBlinePrediction.highlightEnabled = true
- IOBlinePrediction.drawValuesEnabled = false
- if Storage.shared.showLines.value {
- IOBlinePrediction.lineWidth = 2
- } else {
- IOBlinePrediction.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- IOBlinePrediction.drawCirclesEnabled = true
- } else {
- IOBlinePrediction.drawCirclesEnabled = false
- }
- IOBlinePrediction.setDrawHighlightIndicators(false)
- IOBlinePrediction.valueFont.withSize(50)
- // Setup UAM Prediction line details
- let UAMpredictionChartEntry = [ChartDataEntry]()
- let UAMlinePrediction = LineChartDataSet(entries: UAMpredictionChartEntry, label: "")
- UAMlinePrediction.circleRadius = CGFloat(globalVariables.dotBG)
- UAMlinePrediction.circleColors = [NSUIColor.systemPurple]
- UAMlinePrediction.colors = [NSUIColor.systemPurple]
- UAMlinePrediction.drawCircleHoleEnabled = false
- UAMlinePrediction.axisDependency = YAxis.AxisDependency.right
- UAMlinePrediction.highlightEnabled = true
- UAMlinePrediction.drawValuesEnabled = false
- if Storage.shared.showLines.value {
- UAMlinePrediction.lineWidth = 2
- } else {
- UAMlinePrediction.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- UAMlinePrediction.drawCirclesEnabled = true
- } else {
- UAMlinePrediction.drawCirclesEnabled = false
- }
- linePrediction.setDrawHighlightIndicators(false)
- linePrediction.valueFont.withSize(50)
- // Setup ZT Prediction line details
- let ZTpredictionChartEntry = [ChartDataEntry]()
- let ZTlinePrediction = LineChartDataSet(entries: ZTpredictionChartEntry, label: "")
- ZTlinePrediction.circleRadius = CGFloat(globalVariables.dotBG)
- ZTlinePrediction.circleColors = [NSUIColor.systemPurple]
- ZTlinePrediction.colors = [NSUIColor.systemPurple]
- ZTlinePrediction.drawCircleHoleEnabled = false
- ZTlinePrediction.axisDependency = YAxis.AxisDependency.right
- ZTlinePrediction.highlightEnabled = true
- ZTlinePrediction.drawValuesEnabled = false
- if Storage.shared.showLines.value {
- ZTlinePrediction.lineWidth = 2
- } else {
- ZTlinePrediction.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- ZTlinePrediction.drawCirclesEnabled = true
- } else {
- ZTlinePrediction.drawCirclesEnabled = false
- }
- ZTlinePrediction.setDrawHighlightIndicators(false)
- ZTlinePrediction.valueFont.withSize(50)
- // SMB
- let chartEntrySmb = [ChartDataEntry]()
- let lineSmb = LineChartDataSet(entries: chartEntrySmb, label: "")
- lineSmb.circleRadius = CGFloat(globalVariables.dotBolus)
- lineSmb.circleColors = [NSUIColor.systemBlue.withAlphaComponent(1.0)]
- lineSmb.drawCircleHoleEnabled = false
- lineSmb.setDrawHighlightIndicators(false)
- lineSmb.setColor(NSUIColor.red, alpha: 1.0)
- lineSmb.lineWidth = 0
- lineSmb.axisDependency = YAxis.AxisDependency.right
- lineSmb.valueFormatter = ChartYDataValueFormatter()
- lineSmb.valueTextColor = NSUIColor.label
- lineSmb.drawCirclesEnabled = false
- lineSmb.drawFilledEnabled = false
- lineSmb.drawValuesEnabled = Storage.shared.showValues.value
- lineSmb.highlightEnabled = true
- // TempTarget graph data
- let chartTempTargetEntry = [ChartDataEntry]()
- let lineTempTarget = LineChartDataSet(entries: chartTempTargetEntry, label: "")
- lineTempTarget.setDrawHighlightIndicators(false)
- lineTempTarget.lineWidth = 0
- lineTempTarget.drawFilledEnabled = false
- lineTempTarget.fillColor = NSUIColor.systemPurple
- lineTempTarget.fillAlpha = 0.6
- lineTempTarget.drawCirclesEnabled = false
- lineTempTarget.axisDependency = YAxis.AxisDependency.right
- lineTempTarget.highlightEnabled = true
- lineTempTarget.drawValuesEnabled = false
- // Setup the chart data of all lines
- let data = LineChartData()
- data.append(lineBG) // Dataset 0
- data.append(linePrediction) // Dataset 1
- data.append(lineBasal) // Dataset 2
- data.append(lineBolus) // Dataset 3
- data.append(lineCarbs) // Dataset 4
- data.append(lineBasalScheduled) // Dataset 5
- data.append(lineOverride) // Dataset 6
- data.append(lineBGCheck) // Dataset 7
- data.append(lineSuspend) // Dataset 8
- data.append(lineResume) // Dataset 9
- data.append(lineSensor) // Dataset 10
- data.append(lineNote) // Dataset 11
- data.append(ZTlinePrediction) // Dataset 12
- data.append(IOBlinePrediction) // Dataset 13
- data.append(COBlinePrediction) // Dataset 14
- data.append(UAMlinePrediction) // Dataset 15
- data.append(lineSmb) // Dataset 16
- data.append(lineTempTarget) // Dataset 17
- // Dataset 18: Prediction Cone (rendered via ConeOfUncertaintyRenderer)
- let lineCone = LineChartDataSet(entries: [ChartDataEntry](), label: "")
- lineCone.lineWidth = 0
- lineCone.drawCirclesEnabled = false
- lineCone.drawValuesEnabled = false
- lineCone.highlightEnabled = false
- 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
- let marker = PillMarker(color: .secondarySystemBackground, font: UIFont.boldSystemFont(ofSize: 14), textColor: .label)
- BGChart.marker = marker
- // Clear limit lines so they don't add multiples when changing the settings
- BGChart.rightAxis.removeAllLimitLines()
- let thresholds = graphRangeThresholds()
- // Add lower red line
- let ll = ChartLimitLine()
- ll.limit = thresholds.low
- ll.lineColor = NSUIColor.systemRed.withAlphaComponent(0.5)
- BGChart.rightAxis.addLimitLine(ll)
- // Add upper yellow line
- let ul = ChartLimitLine()
- ul.limit = thresholds.high
- ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
- BGChart.rightAxis.addLimitLine(ul)
- // Add vertical lines as configured
- createVerticalLines()
- startGraphNowTimer()
- // Setup the main graph overall details
- BGChart.xAxis.valueFormatter = ChartXValueFormatter()
- BGChart.xAxis.granularity = 1800
- BGChart.xAxis.labelTextColor = NSUIColor.label
- BGChart.xAxis.labelPosition = XAxis.LabelPosition.bottom
- BGChart.xAxis.drawGridLinesEnabled = false
- BGChart.leftAxis.enabled = true
- BGChart.leftAxis.labelPosition = YAxis.LabelPosition.insideChart
- BGChart.leftAxis.axisMaximum = maxBasal
- BGChart.leftAxis.axisMinimum = 0
- BGChart.leftAxis.drawGridLinesEnabled = false
- BGChart.leftAxis.granularityEnabled = true
- BGChart.leftAxis.granularity = 0.5
- BGChart.rightAxis.labelTextColor = NSUIColor.label
- BGChart.rightAxis.labelPosition = YAxis.LabelPosition.insideChart
- BGChart.rightAxis.axisMinimum = 0.0
- BGChart.rightAxis.axisMaximum = Double(maxBG)
- BGChart.rightAxis.gridLineDashLengths = [5.0, 5.0]
- BGChart.rightAxis.drawGridLinesEnabled = false
- BGChart.rightAxis.valueFormatter = ChartYMMOLValueFormatter()
- BGChart.rightAxis.granularityEnabled = true
- BGChart.rightAxis.granularity = 50
- BGChart.maxHighlightDistance = 15.0
- BGChart.legend.enabled = false
- BGChart.scaleYEnabled = false
- BGChart.drawGridBackgroundEnabled = true
- BGChart.gridBackgroundColor = NSUIColor.secondarySystemBackground
- BGChart.highlightValue(nil, callDelegate: false)
- BGChart.data = data
- BGChart.setExtraOffsets(left: 5, top: 10, right: 5, bottom: 10)
- }
- func createVerticalLines() {
- BGChart.xAxis.removeAllLimitLines()
- BGChartFull.xAxis.removeAllLimitLines()
- createNowAndDIALines()
- createMidnightLines()
- }
- func createNowAndDIALines() {
- let ul = ChartLimitLine()
- ul.limit = Double(dateTimeUtils.getNowTimeIntervalUTC())
- ul.lineColor = NSUIColor.systemGray.withAlphaComponent(0.5)
- ul.lineWidth = 1
- BGChart.xAxis.addLimitLine(ul)
- if Storage.shared.show30MinLine.value {
- let ul2 = ChartLimitLine()
- ul2.limit = Double(dateTimeUtils.getNowTimeIntervalUTC().advanced(by: -30 * 60))
- ul2.lineColor = NSUIColor.systemBlue.withAlphaComponent(0.5)
- ul2.lineWidth = 1
- BGChart.xAxis.addLimitLine(ul2)
- }
- if Storage.shared.showDIALines.value {
- for i in 1 ..< 7 {
- let ul = ChartLimitLine()
- ul.limit = Double(dateTimeUtils.getNowTimeIntervalUTC() - Double(i * 60 * 60))
- ul.lineColor = NSUIColor.systemGray.withAlphaComponent(0.3)
- let dash = 10.0 - Double(i)
- let space = 5.0 + Double(i)
- ul.lineDashLengths = [CGFloat(dash), CGFloat(space)]
- ul.lineWidth = 1
- BGChart.xAxis.addLimitLine(ul)
- }
- }
- if Storage.shared.show90MinLine.value {
- let ul3 = ChartLimitLine()
- ul3.limit = Double(dateTimeUtils.getNowTimeIntervalUTC().advanced(by: -90 * 60))
- ul3.lineColor = NSUIColor.systemOrange.withAlphaComponent(0.5)
- ul3.lineWidth = 1
- BGChart.xAxis.addLimitLine(ul3)
- }
- }
- func createMidnightLines() {
- // Draw a line at midnight: useful when showing multiple days of data
- if Storage.shared.showMidnightLines.value {
- var midnightTimeInterval: TimeInterval
- if Storage.shared.graphTimeZoneEnabled.value,
- let tz = TimeZone(identifier: Storage.shared.graphTimeZoneIdentifier.value)
- {
- var cal = Calendar.current
- cal.timeZone = tz
- midnightTimeInterval = cal.startOfDay(for: Date()).timeIntervalSince1970
- } else {
- midnightTimeInterval = dateTimeUtils.getTimeIntervalMidnightToday()
- }
- let graphHours = 24 * Storage.shared.downloadDays.value
- let graphStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
- while midnightTimeInterval > graphStart {
- // Large chart
- let ul = ChartLimitLine()
- ul.limit = Double(midnightTimeInterval)
- ul.lineColor = NSUIColor.systemTeal.withAlphaComponent(0.5)
- ul.lineDashLengths = [CGFloat(2), CGFloat(5)]
- ul.lineWidth = 1
- BGChart.xAxis.addLimitLine(ul)
- // Small chart
- let sl = ChartLimitLine()
- sl.limit = Double(midnightTimeInterval)
- sl.lineColor = NSUIColor.systemTeal
- sl.lineDashLengths = [CGFloat(2), CGFloat(2)]
- sl.lineWidth = 1
- BGChartFull.xAxis.addLimitLine(sl)
- midnightTimeInterval = midnightTimeInterval.advanced(by: -24 * 60 * 60)
- }
- }
- }
- func updateBGGraphSettings() {
- let dataIndex = 0
- let dataIndexPrediction = 1
- let lineBG = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let linePrediction = BGChart.lineData!.dataSets[dataIndexPrediction] as! LineChartDataSet
- if Storage.shared.showLines.value {
- lineBG.lineWidth = 2
- linePrediction.lineWidth = 2
- } else {
- lineBG.lineWidth = 0
- linePrediction.lineWidth = 0
- }
- if Storage.shared.showDots.value {
- lineBG.drawCirclesEnabled = true
- linePrediction.drawCirclesEnabled = true
- } else {
- lineBG.drawCirclesEnabled = false
- linePrediction.drawCirclesEnabled = false
- }
- BGChart.rightAxis.axisMinimum = 0
- // Clear limit lines so they don't add multiples when changing the settings
- BGChart.rightAxis.removeAllLimitLines()
- let thresholds = graphRangeThresholds()
- // Add lower red line
- let ll = ChartLimitLine()
- ll.limit = thresholds.low
- ll.lineColor = NSUIColor.systemRed.withAlphaComponent(0.5)
- BGChart.rightAxis.addLimitLine(ll)
- // Add upper yellow line
- let ul = ChartLimitLine()
- ul.limit = thresholds.high
- ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
- BGChart.rightAxis.addLimitLine(ul)
- // Re-create vertical markers in case their settings changed
- createVerticalLines()
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- 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()
- }
- func updateBGGraph() {
- let dataIndex = 0
- let entries = bgData
- guard !entries.isEmpty else {
- return
- }
- let mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.removeAll(keepingCapacity: false)
- smallChart.removeAll(keepingCapacity: false)
- let maxBGOffset: Double = 50
- var colors = [NSUIColor]()
- topBG = Storage.shared.minBGScale.value
- let thresholds = graphRangeThresholds()
- for i in 0 ..< entries.count {
- // Clamp the plotted y-value to the same bounds the header text uses
- // (HIGH/LOW), so the graph stays consistent with the main display.
- // The pill tooltip still shows the raw reading.
- let plottedSgv = Double(min(max(entries[i].sgv, globalVariables.minDisplayGlucose), globalVariables.maxDisplayGlucose))
- if plottedSgv > topBG - maxBGOffset {
- topBG = plottedSgv + maxBGOffset
- }
- let value = ChartDataEntry(x: Double(entries[i].date), y: plottedSgv, data: formatPillText(line1: Localizer.toDisplayUnits(String(entries[i].sgv)), time: entries[i].date))
- mainChart.append(value)
- smallChart.append(value)
- if Double(entries[i].sgv) >= thresholds.high {
- colors.append(NSUIColor.systemYellow)
- } else if Double(entries[i].sgv) <= thresholds.low {
- colors.append(NSUIColor.systemRed)
- } else {
- colors.append(NSUIColor.systemGreen)
- }
- }
- // Set Colors
- let lineBG = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let lineBGSmall = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- lineBG.colors.removeAll()
- lineBG.circleColors.removeAll()
- lineBGSmall.colors.removeAll()
- lineBGSmall.circleColors.removeAll()
- if colors.count > 0 {
- for i in 0 ..< colors.count {
- mainChart.addColor(colors[i])
- mainChart.circleColors.append(colors[i])
- smallChart.addColor(colors[i])
- smallChart.circleColors.append(colors[i])
- }
- }
- BGChart.rightAxis.axisMaximum = Double(calculateMaxBgGraphValue())
- BGChart.setVisibleXRangeMinimum(600)
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- BGChartFull.rightAxis.axisMaximum = Double(calculateMaxBgGraphValue())
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- updateYesterdayBGGraph()
- // The initial zoom is a one-shot, relative to the chart's current
- // viewport. Skip it until the chart actually has a width — otherwise a
- // refresh that lands while the view is loaded but off-screen (e.g. Home
- // lives in the Menu, so MainViewController is force-loaded headless by
- // bootstrap()) would consume firstGraphLoad against a zero-size viewport
- // and leave the main graph blank. viewDidAppear re-runs updateBGGraph
- // once a real frame exists, applying the zoom correctly.
- if firstGraphLoad, BGChart.bounds.width > 0 {
- var scaleX = CGFloat(Storage.shared.chartScaleX.value)
- if scaleX > CGFloat(ScaleXMax) {
- scaleX = CGFloat(ScaleXMax)
- Storage.shared.chartScaleX.value = ScaleXMax
- }
- BGChart.zoom(scaleX: scaleX, scaleY: 1, x: 1, y: 1)
- firstGraphLoad = false
- }
- // Move to current reading everytime new readings load
- // Check if auto-scrolling should be performed
- if autoScrollPauseUntil == nil || Date() > autoScrollPauseUntil! {
- BGChart.moveViewToAnimated(xValue: dateTimeUtils.getNowTimeIntervalUTC() - (BGChart.visibleXRange * 0.7), yValue: 0.0, axis: .right, duration: 1, easingOption: .easeInBack)
- }
- }
- // 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()
- }
- // Removes the Loop forecast line. Used when the active system switches away from Loop.
- func clearLoopPredictionGraph() {
- guard !predictionData.isEmpty else { return }
- predictionData.removeAll()
- updatePredictionGraph()
- }
- // Removes the Trio/OpenAPS forecast lines (ZT/IOB/COB/UAM). Used when the active system switches away from Trio/OpenAPS.
- func clearOpenAPSPredictionGraph() {
- let openAPSDataIndices = [12, 13, 14, 15]
- for dataIndex in openAPSDataIndices {
- let mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- if !mainChart.entries.isEmpty || !smallChart.entries.isEmpty {
- updatePredictionGraphGeneric(
- dataIndex: dataIndex,
- predictionData: [],
- chartLabel: "",
- color: UIColor.systemGray
- )
- }
- }
- }
- func updatePredictionGraph(color: UIColor? = nil) {
- let dataIndex = 1
- var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.clear()
- smallChart.clear()
- var colors = [NSUIColor]()
- let maxBGOffset: Double = 20
- topPredictionBG = Storage.shared.minBGScale.value
- for i in 0 ..< predictionData.count {
- var predictionVal = Double(predictionData[i].sgv)
- if Double(predictionVal) > topPredictionBG - maxBGOffset {
- topPredictionBG = predictionVal + maxBGOffset
- }
- if i == 0 {
- if Storage.shared.showDots.value {
- colors.append((color ?? NSUIColor.systemPurple).withAlphaComponent(0.0))
- } else {
- colors.append((color ?? NSUIColor.systemPurple).withAlphaComponent(1.0))
- }
- } else if predictionVal > 400 {
- colors.append(color ?? NSUIColor.systemYellow)
- } else if predictionVal < 0 {
- colors.append(color ?? NSUIColor.systemRed)
- } else {
- colors.append(color ?? NSUIColor.systemPurple)
- }
- let value = ChartDataEntry(x: predictionData[i].date, y: predictionVal, data: formatPillText(line1: Localizer.toDisplayUnits(String(predictionData[i].sgv)), time: predictionData[i].date))
- mainChart.addEntry(value)
- smallChart.addEntry(value)
- }
- smallChart.circleColors.removeAll()
- smallChart.colors.removeAll()
- mainChart.colors.removeAll()
- mainChart.circleColors.removeAll()
- if colors.count > 0 {
- for i in 0 ..< colors.count {
- mainChart.addColor(colors[i])
- mainChart.circleColors.append(colors[i])
- smallChart.addColor(colors[i])
- smallChart.circleColors.append(colors[i])
- }
- }
- BGChart.rightAxis.axisMaximum = Double(calculateMaxBgGraphValue())
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- func updateBasalGraph() {
- var dataIndex = 2
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- var maxBasal = Storage.shared.minBasalScale.value
- var maxBasalSmall = 0.0
- for i in 0 ..< basalData.count {
- let value = ChartDataEntry(x: Double(basalData[i].date), y: Double(basalData[i].basalRate), data: formatPillText(line1: String(basalData[i].basalRate), time: basalData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- if basalData[i].basalRate > maxBasal {
- maxBasal = basalData[i].basalRate
- }
- if basalData[i].basalRate > maxBasalSmall {
- maxBasalSmall = basalData[i].basalRate
- }
- }
- BGChart.leftAxis.axisMaximum = maxBasal
- BGChartFull.leftAxis.axisMaximum = maxBasalSmall
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateBasalScheduledGraph() {
- var dataIndex = 5
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- for i in 0 ..< basalScheduleData.count {
- let value = ChartDataEntry(x: Double(basalScheduleData[i].date), y: Double(basalScheduleData[i].basalRate))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateBolusGraph() {
- var dataIndex = 3
- var yTop: Double = 370
- var yBottom: Double = 345
- var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.clear()
- smallChart.clear()
- var colors = [NSUIColor]()
- for i in 0 ..< bolusData.count {
- let formatter = NumberFormatter()
- formatter.minimumFractionDigits = 0
- formatter.maximumFractionDigits = 2
- formatter.minimumIntegerDigits = 0
- // Check overlapping carbs to shift left if needed
- let bolusShift = findNextBolusTime(timeWithin: 240, needle: bolusData[i].date, haystack: bolusData, startingIndex: i)
- var dateTimeStamp = bolusData[i].date
- colors.append(NSUIColor.systemBlue.withAlphaComponent(1.0))
- if bolusShift {
- // Move it half the distance between BG readings
- dateTimeStamp = dateTimeStamp - 150
- }
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let valueString = formatter.string(from: NSNumber(value: bolusData[i].value)) ?? ""
- let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(bolusData[i].sgv), data: valueString + "\r\r" + formatPillText(line1: valueString + " U", time: bolusData[i].date))
- mainChart.addEntry(dot)
- if Storage.shared.smallGraphTreatments.value {
- smallChart.addEntry(dot)
- }
- }
- // Set Colors
- let lineBolus = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let lineBolusSmall = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- lineBolus.colors.removeAll()
- lineBolus.circleColors.removeAll()
- lineBolusSmall.colors.removeAll()
- lineBolusSmall.circleColors.removeAll()
- if colors.count > 0 {
- for i in 0 ..< colors.count {
- mainChart.addColor(colors[i])
- mainChart.circleColors.append(colors[i])
- smallChart.addColor(colors[i])
- smallChart.circleColors.append(colors[i])
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateSmbGraph() {
- var dataIndex = 16
- var yTop: Double = 370
- var yBottom: Double = 345
- var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.clear()
- smallChart.clear()
- let lightBlue = NSUIColor(red: 135 / 255, green: 206 / 255, blue: 235 / 255, alpha: 1.0) // Light Sky Blue
- var colors = [NSUIColor]()
- for i in 0 ..< smbData.count {
- let formatter = NumberFormatter()
- formatter.minimumFractionDigits = 0
- formatter.maximumFractionDigits = 2
- formatter.minimumIntegerDigits = 0
- let bolusShift = findNextBolusTime(timeWithin: 240, needle: smbData[i].date, haystack: smbData, startingIndex: i)
- var dateTimeStamp = smbData[i].date
- let nowTime = dateTimeUtils.getNowTimeIntervalUTC()
- let diffTimeHours = (nowTime - dateTimeStamp) / 60 / 60
- if diffTimeHours <= 1 {
- colors.append(lightBlue.withAlphaComponent(1.0))
- } else if diffTimeHours > 6 {
- colors.append(lightBlue.withAlphaComponent(0.25))
- } else {
- let thisAlpha = 1.0 - (0.15 * diffTimeHours)
- colors.append(lightBlue.withAlphaComponent(CGFloat(thisAlpha)))
- }
- if bolusShift {
- dateTimeStamp = dateTimeStamp - 150
- }
- let graphHours = 24 * Storage.shared.downloadDays.value
- if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let valueString = formatter.string(from: NSNumber(value: smbData[i].value)) ?? ""
- let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(smbData[i].sgv), data: valueString + "\r\r" + formatPillText(line1: valueString + " U", time: smbData[i].date))
- mainChart.addEntry(dot)
- if Storage.shared.smallGraphTreatments.value {
- smallChart.addEntry(dot)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateCarbGraph() {
- var dataIndex = 4
- var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.removeAll(keepingCapacity: true)
- smallChart.removeAll(keepingCapacity: true)
- var colors = [NSUIColor]()
- for i in 0 ..< carbData.count {
- let formatter = NumberFormatter()
- formatter.minimumFractionDigits = 0
- formatter.maximumFractionDigits = 2
- formatter.minimumIntegerDigits = 1
- let carbAmountString = formatter.string(from: NSNumber(value: carbData[i].value))!
- var valueString = carbAmountString
- var markerLine1 = carbAmountString + "g"
- var hours = 3
- if carbData[i].absorptionTime > 0, Storage.shared.showAbsorption.value {
- hours = carbData[i].absorptionTime / 60
- valueString += " " + String(hours) + "h"
- markerLine1 += " " + String(hours) + "h"
- }
- // Check overlapping carbs to shift left if needed
- let carbShift = findNextCarbTime(timeWithin: 250, needle: carbData[i].date, haystack: carbData, startingIndex: i)
- var dateTimeStamp = carbData[i].date
- colors.append(NSUIColor.systemOrange.withAlphaComponent(1.0))
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- if carbShift {
- dateTimeStamp = dateTimeStamp - 250
- }
- let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(carbData[i].sgv), data: valueString + "\r\r" + formatPillText(line1: markerLine1, time: carbData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(dot)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(dot)
- }
- }
- // Set Colors
- let lineCarbs = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let lineCarbsSmall = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- lineCarbs.colors.removeAll()
- lineCarbs.circleColors.removeAll()
- lineCarbsSmall.colors.removeAll()
- lineCarbsSmall.circleColors.removeAll()
- if colors.count > 0 {
- for i in 0 ..< colors.count {
- mainChart.addColor(colors[i])
- mainChart.circleColors.append(colors[i])
- smallChart.addColor(colors[i])
- smallChart.circleColors.append(colors[i])
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateBGCheckGraph() {
- var dataIndex = 7
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- for i in 0 ..< bgCheckData.count {
- let formatter = NumberFormatter()
- formatter.minimumFractionDigits = 0
- formatter.maximumFractionDigits = 2
- formatter.minimumIntegerDigits = 1
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if bgCheckData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let value = ChartDataEntry(x: Double(bgCheckData[i].date), y: Double(bgCheckData[i].sgv), data: formatPillText(line1: Localizer.toDisplayUnits(String(bgCheckData[i].sgv)), time: bgCheckData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateSuspendGraph() {
- var dataIndex = 8
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- let thisData = suspendGraphData
- for i in 0 ..< thisData.count {
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Suspend Pump", time: thisData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateResumeGraph() {
- var dataIndex = 9
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- let thisData = resumeGraphData
- for i in 0 ..< thisData.count {
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Resume Pump", time: thisData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateSensorStart() {
- var dataIndex = 10
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- let thisData = sensorStartGraphData
- for i in 0 ..< thisData.count {
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Start Sensor", time: thisData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func updateNotes() {
- var dataIndex = 11
- BGChart.lineData?.dataSets[dataIndex].clear()
- BGChartFull.lineData?.dataSets[dataIndex].clear()
- let thisData = noteGraphData
- for i in 0 ..< thisData.count {
- // skip if outside of visible area
- let graphHours = 24 * Storage.shared.downloadDays.value
- if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
- let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: thisData[i].note, time: thisData[i].date))
- BGChart.data?.dataSets[dataIndex].addEntry(value)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(value)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func createSmallBGGraph() {
- let entries = bgData
- var bgChartEntry = [ChartDataEntry]()
- var colors = [NSUIColor]()
- var maxBG = Storage.shared.minBGScale.value
- let lineBG = LineChartDataSet(entries: bgChartEntry, label: "")
- lineBG.drawCirclesEnabled = false
- // line2.setDrawHighlightIndicators(false)
- lineBG.highlightEnabled = true
- lineBG.drawHorizontalHighlightIndicatorEnabled = false
- lineBG.drawVerticalHighlightIndicatorEnabled = false
- lineBG.highlightColor = NSUIColor.label
- lineBG.drawValuesEnabled = false
- lineBG.lineWidth = 1.5
- lineBG.axisDependency = YAxis.AxisDependency.right
- // Setup Prediction line details
- var predictionChartEntry = [ChartDataEntry]()
- let linePrediction = LineChartDataSet(entries: predictionChartEntry, label: "")
- linePrediction.drawCirclesEnabled = false
- // line2.setDrawHighlightIndicators(false)
- linePrediction.setColor(NSUIColor.systemPurple)
- linePrediction.highlightEnabled = true
- linePrediction.drawHorizontalHighlightIndicatorEnabled = false
- linePrediction.drawVerticalHighlightIndicatorEnabled = false
- linePrediction.highlightColor = NSUIColor.label
- linePrediction.drawValuesEnabled = false
- linePrediction.lineWidth = 1.5
- linePrediction.axisDependency = YAxis.AxisDependency.right
- // create Basal graph data
- var chartEntry = [ChartDataEntry]()
- var maxBasal = Storage.shared.minBasalScale.value
- let lineBasal = LineChartDataSet(entries: chartEntry, label: "")
- lineBasal.setDrawHighlightIndicators(false)
- lineBasal.setColor(NSUIColor.systemBlue, alpha: 0.5)
- lineBasal.lineWidth = 0
- lineBasal.drawFilledEnabled = true
- lineBasal.fillColor = NSUIColor.systemBlue
- lineBasal.fillAlpha = 0.35
- lineBasal.drawCirclesEnabled = false
- lineBasal.axisDependency = YAxis.AxisDependency.left
- lineBasal.highlightEnabled = false
- lineBasal.drawValuesEnabled = false
- lineBasal.fillFormatter = basalFillFormatter()
- // Boluses
- var chartEntryBolus = [ChartDataEntry]()
- let lineBolus = LineChartDataSet(entries: chartEntryBolus, label: "")
- lineBolus.circleRadius = 2
- lineBolus.circleColors = [NSUIColor.systemBlue.withAlphaComponent(0.75)]
- lineBolus.drawCircleHoleEnabled = false
- lineBolus.setDrawHighlightIndicators(false)
- lineBolus.setColor(NSUIColor.systemBlue, alpha: 1.0)
- lineBolus.lineWidth = 0
- lineBolus.axisDependency = YAxis.AxisDependency.right
- lineBolus.valueFormatter = ChartYDataValueFormatter()
- lineBolus.valueTextColor = NSUIColor.label
- lineBolus.fillColor = NSUIColor.systemBlue
- lineBolus.fillAlpha = 0.6
- lineBolus.drawCirclesEnabled = true
- lineBolus.drawFilledEnabled = false
- lineBolus.drawValuesEnabled = false
- lineBolus.highlightEnabled = false
- // Carbs
- var chartEntryCarbs = [ChartDataEntry]()
- let lineCarbs = LineChartDataSet(entries: chartEntryCarbs, label: "")
- lineCarbs.circleRadius = 2
- lineCarbs.circleColors = [NSUIColor.systemOrange.withAlphaComponent(0.75)]
- lineCarbs.drawCircleHoleEnabled = false
- lineCarbs.setDrawHighlightIndicators(false)
- lineCarbs.setColor(NSUIColor.systemBlue, alpha: 1.0)
- lineCarbs.lineWidth = 0
- lineCarbs.axisDependency = YAxis.AxisDependency.right
- lineCarbs.valueFormatter = ChartYDataValueFormatter()
- lineCarbs.valueTextColor = NSUIColor.label
- lineCarbs.fillColor = NSUIColor.systemOrange
- lineCarbs.fillAlpha = 0.6
- lineCarbs.drawCirclesEnabled = true
- lineCarbs.drawFilledEnabled = false
- lineCarbs.drawValuesEnabled = false
- lineCarbs.highlightEnabled = false
- // create Scheduled Basal graph data
- var chartBasalScheduledEntry = [ChartDataEntry]()
- let lineBasalScheduled = LineChartDataSet(entries: chartBasalScheduledEntry, label: "")
- lineBasalScheduled.setDrawHighlightIndicators(false)
- lineBasalScheduled.setColor(NSUIColor.systemBlue, alpha: 0.8)
- lineBasalScheduled.lineWidth = 0.5
- lineBasalScheduled.drawFilledEnabled = false
- lineBasalScheduled.drawCirclesEnabled = false
- lineBasalScheduled.axisDependency = YAxis.AxisDependency.left
- lineBasalScheduled.highlightEnabled = false
- lineBasalScheduled.drawValuesEnabled = false
- lineBasalScheduled.lineDashLengths = [2, 1]
- // create Override graph data
- var chartOverrideEntry = [ChartDataEntry]()
- let lineOverride = LineChartDataSet(entries: chartOverrideEntry, label: "")
- lineOverride.setDrawHighlightIndicators(false)
- lineOverride.lineWidth = 0
- lineOverride.drawFilledEnabled = true
- lineOverride.fillFormatter = OverrideFillFormatter()
- lineOverride.fillColor = TreatmentGraphColors.override
- lineOverride.fillAlpha = 0.6
- lineOverride.drawCirclesEnabled = false
- lineOverride.axisDependency = YAxis.AxisDependency.right
- lineOverride.highlightEnabled = true
- lineOverride.drawValuesEnabled = false
- // BG Check
- var chartEntryBGCheck = [ChartDataEntry]()
- let lineBGCheck = LineChartDataSet(entries: chartEntryBGCheck, label: "")
- lineBGCheck.circleRadius = 2
- lineBGCheck.circleColors = [NSUIColor.systemRed.withAlphaComponent(0.75)]
- lineBGCheck.drawCircleHoleEnabled = false
- lineBGCheck.setDrawHighlightIndicators(false)
- lineBGCheck.setColor(NSUIColor.systemRed, alpha: 1.0)
- lineBGCheck.drawCirclesEnabled = true
- lineBGCheck.lineWidth = 0
- lineBGCheck.highlightEnabled = false
- lineBGCheck.axisDependency = YAxis.AxisDependency.right
- lineBGCheck.valueFormatter = ChartYDataValueFormatter()
- lineBGCheck.drawValuesEnabled = false
- // Suspend Pump
- var chartEntrySuspend = [ChartDataEntry]()
- let lineSuspend = LineChartDataSet(entries: chartEntrySuspend, label: "")
- lineSuspend.circleRadius = 2
- lineSuspend.circleColors = [NSUIColor.systemTeal.withAlphaComponent(0.75)]
- lineSuspend.drawCircleHoleEnabled = false
- lineSuspend.setDrawHighlightIndicators(false)
- lineSuspend.setColor(NSUIColor.systemGray2, alpha: 1.0)
- lineSuspend.drawCirclesEnabled = true
- lineSuspend.lineWidth = 0
- lineSuspend.highlightEnabled = false
- lineSuspend.axisDependency = YAxis.AxisDependency.right
- lineSuspend.valueFormatter = ChartYDataValueFormatter()
- lineSuspend.drawValuesEnabled = false
- // Resume Pump
- var chartEntryResume = [ChartDataEntry]()
- let lineResume = LineChartDataSet(entries: chartEntryResume, label: "")
- lineResume.circleRadius = 2
- lineResume.circleColors = [NSUIColor.systemTeal.withAlphaComponent(0.75)]
- lineResume.drawCircleHoleEnabled = false
- lineResume.setDrawHighlightIndicators(false)
- lineResume.setColor(NSUIColor.systemGray4, alpha: 1.0)
- lineResume.drawCirclesEnabled = true
- lineResume.lineWidth = 0
- lineResume.highlightEnabled = false
- lineResume.axisDependency = YAxis.AxisDependency.right
- lineResume.valueFormatter = ChartYDataValueFormatter()
- lineResume.drawValuesEnabled = false
- // Sensor Start
- var chartEntrySensor = [ChartDataEntry]()
- let lineSensor = LineChartDataSet(entries: chartEntrySensor, label: "")
- lineSensor.circleRadius = 2
- lineSensor.circleColors = [NSUIColor.systemIndigo.withAlphaComponent(0.75)]
- lineSensor.drawCircleHoleEnabled = false
- lineSensor.setDrawHighlightIndicators(false)
- lineSensor.setColor(NSUIColor.systemGray3, alpha: 1.0)
- lineSensor.drawCirclesEnabled = true
- lineSensor.lineWidth = 0
- lineSensor.highlightEnabled = false
- lineSensor.axisDependency = YAxis.AxisDependency.right
- lineSensor.valueFormatter = ChartYDataValueFormatter()
- lineSensor.drawValuesEnabled = false
- // Notes
- var chartEntryNote = [ChartDataEntry]()
- let lineNote = LineChartDataSet(entries: chartEntryNote, label: "")
- lineNote.circleRadius = 2
- lineNote.circleColors = [NSUIColor.systemGray.withAlphaComponent(0.75)]
- lineNote.drawCircleHoleEnabled = false
- lineNote.setDrawHighlightIndicators(false)
- lineNote.setColor(NSUIColor.systemGray3, alpha: 1.0)
- lineNote.drawCirclesEnabled = true
- lineNote.lineWidth = 0
- lineNote.highlightEnabled = false
- lineNote.axisDependency = YAxis.AxisDependency.right
- lineNote.valueFormatter = ChartYDataValueFormatter()
- lineNote.drawValuesEnabled = false
- // Setup COB Prediction line details
- var COBpredictionChartEntry = [ChartDataEntry]()
- let COBlinePrediction = LineChartDataSet(entries: COBpredictionChartEntry, label: "")
- COBlinePrediction.drawCirclesEnabled = false
- COBlinePrediction.setColor(NSUIColor.systemPurple)
- COBlinePrediction.highlightEnabled = true
- COBlinePrediction.drawHorizontalHighlightIndicatorEnabled = false
- COBlinePrediction.drawVerticalHighlightIndicatorEnabled = false
- COBlinePrediction.highlightColor = NSUIColor.label
- COBlinePrediction.drawValuesEnabled = false
- COBlinePrediction.lineWidth = 1.5
- COBlinePrediction.axisDependency = YAxis.AxisDependency.right
- // Setup IOB Prediction line details
- var IOBpredictionChartEntry = [ChartDataEntry]()
- let IOBlinePrediction = LineChartDataSet(entries: IOBpredictionChartEntry, label: "")
- IOBlinePrediction.drawCirclesEnabled = false
- IOBlinePrediction.setColor(NSUIColor.systemPurple)
- IOBlinePrediction.highlightEnabled = true
- IOBlinePrediction.drawHorizontalHighlightIndicatorEnabled = false
- IOBlinePrediction.drawVerticalHighlightIndicatorEnabled = false
- IOBlinePrediction.highlightColor = NSUIColor.label
- IOBlinePrediction.drawValuesEnabled = false
- IOBlinePrediction.lineWidth = 1.5
- IOBlinePrediction.axisDependency = YAxis.AxisDependency.right
- // Setup UAM Prediction line details
- var UAMpredictionChartEntry = [ChartDataEntry]()
- let UAMlinePrediction = LineChartDataSet(entries: UAMpredictionChartEntry, label: "")
- UAMlinePrediction.drawCirclesEnabled = false
- UAMlinePrediction.setColor(NSUIColor.systemPurple)
- UAMlinePrediction.highlightEnabled = true
- UAMlinePrediction.drawHorizontalHighlightIndicatorEnabled = false
- UAMlinePrediction.drawVerticalHighlightIndicatorEnabled = false
- UAMlinePrediction.highlightColor = NSUIColor.label
- UAMlinePrediction.drawValuesEnabled = false
- UAMlinePrediction.lineWidth = 1.5
- UAMlinePrediction.axisDependency = YAxis.AxisDependency.right
- // Setup ZT Prediction line details
- var ZTpredictionChartEntry = [ChartDataEntry]()
- let ZTlinePrediction = LineChartDataSet(entries: ZTpredictionChartEntry, label: "")
- ZTlinePrediction.drawCirclesEnabled = false
- ZTlinePrediction.setColor(NSUIColor.systemPurple)
- ZTlinePrediction.highlightEnabled = true
- ZTlinePrediction.drawHorizontalHighlightIndicatorEnabled = false
- ZTlinePrediction.drawVerticalHighlightIndicatorEnabled = false
- ZTlinePrediction.highlightColor = NSUIColor.label
- ZTlinePrediction.drawValuesEnabled = false
- ZTlinePrediction.lineWidth = 1.5
- ZTlinePrediction.axisDependency = YAxis.AxisDependency.right
- // SMB
- var chartEntrySmb = [ChartDataEntry]()
- let lineSmb = LineChartDataSet(entries: chartEntrySmb, label: "")
- lineSmb.circleRadius = 2
- lineSmb.circleColors = [NSUIColor.systemBlue.withAlphaComponent(0.75)]
- lineSmb.drawCircleHoleEnabled = false
- lineSmb.setDrawHighlightIndicators(false)
- lineSmb.setColor(NSUIColor.systemBlue, alpha: 1.0)
- lineSmb.lineWidth = 0
- lineSmb.axisDependency = YAxis.AxisDependency.right
- lineSmb.valueFormatter = ChartYDataValueFormatter()
- lineSmb.valueTextColor = NSUIColor.label
- lineSmb.fillColor = NSUIColor.systemBlue
- lineSmb.fillAlpha = 0.6
- lineSmb.drawCirclesEnabled = true
- lineSmb.drawFilledEnabled = false
- lineSmb.drawValuesEnabled = false
- lineSmb.highlightEnabled = false
- // Temp Target graph data
- let chartTempTargetEntry = [ChartDataEntry]()
- let lineTempTarget = LineChartDataSet(entries: chartTempTargetEntry, label: "")
- lineTempTarget.setDrawHighlightIndicators(false)
- lineTempTarget.lineWidth = 0
- lineTempTarget.drawFilledEnabled = false
- lineTempTarget.fillColor = NSUIColor.systemPurple
- lineTempTarget.fillAlpha = 0.6
- lineTempTarget.drawCirclesEnabled = false
- lineTempTarget.axisDependency = YAxis.AxisDependency.right
- lineTempTarget.highlightEnabled = true
- lineTempTarget.drawValuesEnabled = false
- // Setup the chart data of all lines
- let data = LineChartData()
- data.append(lineBG) // Dataset 0
- data.append(linePrediction) // Dataset 1
- data.append(lineBasal) // Dataset 2
- data.append(lineBolus) // Dataset 3
- data.append(lineCarbs) // Dataset 4
- data.append(lineBasalScheduled) // Dataset 5
- data.append(lineOverride) // Dataset 6
- data.append(lineBGCheck) // Dataset 7
- data.append(lineSuspend) // Dataset 8
- data.append(lineResume) // Dataset 9
- data.append(lineSensor) // Dataset 10
- data.append(lineNote) // Dataset 11
- data.append(ZTlinePrediction) // Dataset 12
- data.append(IOBlinePrediction) // Dataset 13
- data.append(COBlinePrediction) // Dataset 14
- data.append(UAMlinePrediction) // Dataset 15
- data.append(lineSmb) // Dataset 16
- data.append(lineTempTarget) // Dataset 17
- // Dataset 18: Prediction Cone placeholder (not rendered on small chart)
- let lineConeSmall = LineChartDataSet(entries: [ChartDataEntry](), label: "")
- lineConeSmall.lineWidth = 0
- lineConeSmall.drawCirclesEnabled = false
- lineConeSmall.drawValuesEnabled = false
- lineConeSmall.highlightEnabled = false
- lineConeSmall.axisDependency = YAxis.AxisDependency.right
- data.append(lineConeSmall)
- BGChartFull.highlightPerDragEnabled = true
- BGChartFull.leftAxis.enabled = false
- BGChartFull.leftAxis.axisMaximum = maxBasal
- BGChartFull.leftAxis.axisMinimum = 0
- BGChartFull.rightAxis.enabled = false
- BGChartFull.rightAxis.axisMinimum = 0.0
- BGChartFull.rightAxis.axisMaximum = Double(maxBG)
- BGChartFull.xAxis.drawLabelsEnabled = false
- BGChartFull.xAxis.drawGridLinesEnabled = false
- BGChartFull.xAxis.drawAxisLineEnabled = false
- BGChartFull.legend.enabled = false
- BGChartFull.scaleYEnabled = false
- BGChartFull.scaleXEnabled = false
- BGChartFull.drawGridBackgroundEnabled = false
- BGChartFull.data = data
- }
- func updateOverrideGraph() {
- var dataIndex = 6
- var yTop = Double(calculateMaxBgGraphValue() - 5)
- var yBottom = Double(calculateMaxBgGraphValue() - 25)
- var chart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- chart.clear()
- smallChart.clear()
- // Refresh the fill color in case the backend (Loop vs Trio) changed.
- chart.fillColor = TreatmentGraphColors.override
- smallChart.fillColor = TreatmentGraphColors.override
- let thisData = overrideGraphData
- var colors = [NSUIColor]()
- for i in 0 ..< thisData.count {
- let thisItem = thisData[i]
- let multiplier = thisItem.insulNeedsScaleFactor as! Double * 100.0
- var labelText = thisItem.reason + "\r\n"
- labelText += String(Int(thisItem.insulNeedsScaleFactor * 100)) + "% "
- if thisItem.correctionRange.count == 2 {
- labelText += String(thisItem.correctionRange[0]) + "-" + String(thisItem.correctionRange[1])
- }
- if thisItem.enteredBy.count > 0 {
- labelText += "\r\nEntered By: " + thisItem.enteredBy
- }
- // Start Dot
- // Shift dots 30 seconds to create an empty 0 space between consecutive temps
- let preStartDot = ChartDataEntry(x: Double(thisItem.date), y: yBottom, data: labelText)
- BGChart.data?.dataSets[dataIndex].addEntry(preStartDot)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(preStartDot)
- }
- let startDot = ChartDataEntry(x: Double(thisItem.date + 1), y: yTop, data: labelText)
- BGChart.data?.dataSets[dataIndex].addEntry(startDot)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(startDot)
- }
- // End Dot
- let endDot = ChartDataEntry(x: Double(thisItem.endDate - 2), y: yTop, data: labelText)
- BGChart.data?.dataSets[dataIndex].addEntry(endDot)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(endDot)
- }
- // Post end dot
- let postEndDot = ChartDataEntry(x: Double(thisItem.endDate - 1), y: yBottom, data: labelText)
- BGChart.data?.dataSets[dataIndex].addEntry(postEndDot)
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].addEntry(postEndDot)
- }
- }
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if Storage.shared.smallGraphTreatments.value {
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func getChartDataSets(for index: GraphDataIndex) -> (chart: LineChartDataSet?, smallChart: LineChartDataSet?) {
- guard let chart = BGChart.lineData,
- index.rawValue < chart.dataSets.count,
- let smallChartData = BGChartFull.lineData,
- index.rawValue < smallChartData.dataSets.count
- else {
- // print("Warning: Invalid GraphDataIndex \(index.description) or lineData is nil.")
- return (nil, nil)
- }
- let chartDataSet = chart.dataSets[index.rawValue] as? LineChartDataSet
- let smallChartDataSet = smallChartData.dataSets[index.rawValue] as? LineChartDataSet
- return (chartDataSet, smallChartDataSet)
- }
- func addEntryToCharts(entry: ChartDataEntry, chart: LineChartDataSet, smallChart: LineChartDataSet?) {
- chart.addEntry(entry)
- if Storage.shared.smallGraphTreatments.value, let smallChart = smallChart {
- smallChart.addEntry(entry)
- }
- }
- func updateTempTargetGraph() {
- let dataIndex = GraphDataIndex.tempTarget.rawValue
- guard let chartData = BGChart.lineData,
- chartData.dataSets.count > dataIndex,
- let mainChartDataSet = chartData.dataSets[dataIndex] as? LineChartDataSet
- else {
- print("Error: Could not retrieve temp target datasets.")
- return
- }
- mainChartDataSet.clear()
- var smallChartDataSet: LineChartDataSet?
- if Storage.shared.smallGraphTreatments.value,
- let smallChartData = BGChartFull.lineData,
- smallChartData.dataSets.count > dataIndex,
- let smallDataSet = smallChartData.dataSets[dataIndex] as? LineChartDataSet
- {
- smallChartDataSet = smallDataSet
- smallChartDataSet?.clear()
- }
- let thisData = tempTargetGraphData
- for tempTarget in thisData {
- let xStart = tempTarget.date
- let xEnd = tempTarget.endDate
- let yCenter = Double(tempTarget.correctionRange[0])
- let yTop = yCenter + 5.0
- let yBottom = yCenter - 5.0
- let entry = TempTargetChartDataEntry(
- xStart: xStart,
- xEnd: xEnd,
- yTop: yTop,
- yBottom: yBottom,
- data: nil
- )
- mainChartDataSet.addEntry(entry)
- if let smallDataSet = smallChartDataSet {
- smallDataSet.addEntry(entry)
- }
- }
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- if let smallDataSet = smallChartDataSet {
- let tempTargetRendererSmall = TempTargetRenderer(
- dataProvider: BGChartFull,
- animator: BGChartFull.chartAnimator,
- viewPortHandler: BGChartFull.viewPortHandler,
- tempTargetDataSetIndex: dataIndex
- )
- BGChartFull.renderer = tempTargetRendererSmall
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
- func extractMessage(from logEntry: String) -> String? {
- // Check if this is a JSON-containing log entry
- guard let jsonStartIndex = logEntry.range(of: "{\"")?.lowerBound else {
- return nil
- }
- // Extract the error message part (before JSON)
- let errorMessage = String(logEntry[..<jsonStartIndex])
- .trimmingCharacters(in: .whitespacesAndNewlines)
- // Extract and parse JSON to get context
- let jsonString = String(logEntry[jsonStartIndex...])
- var actionContext = ""
- if let jsonData = jsonString.data(using: .utf8),
- let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any]
- {
- // Extract relevant action information
- var actionParts: [String] = []
- // Check for bolus entry
- if let bolusAmount = json["bolus-entry"] as? Double {
- actionParts.append("Bolus: \(bolusAmount) U")
- }
- // Check for carbs entry
- if let carbsAmount = json["carbs-entry"] as? Double {
- actionParts.append("Carbs: \(carbsAmount) g")
- }
- // Check for absorption time (relevant for carbs)
- if let absorptionTime = json["absorption-time"] as? Double {
- actionParts.append("Absorption: \(absorptionTime) hrs")
- }
- // Check for OTP (password)
- if let otp = json["otp"] as? String {
- actionParts.append("OTP: \(otp)")
- }
- // Check for sender
- if let enteredBy = json["entered-by"] as? String {
- actionParts.append("From: \(enteredBy)")
- }
- // Combine action parts
- if !actionParts.isEmpty {
- actionContext = " [" + actionParts.joined(separator: ", ") + "]"
- }
- }
- // Combine error message with action context
- let finalMessage = errorMessage + actionContext
- return finalMessage.isEmpty ? nil : finalMessage
- }
- func wrapText(_ text: String, maxLineLength: Int) -> String {
- let messageToWrap = extractMessage(from: text) ?? text
- guard maxLineLength > 0 else {
- return messageToWrap
- }
- var result: [String] = []
- let lines = messageToWrap.components(separatedBy: .newlines)
- for line in lines {
- var currentLine = ""
- let words = line.components(separatedBy: .whitespaces)
- for word in words {
- if word.count > maxLineLength {
- if !currentLine.isEmpty {
- result.append(currentLine)
- currentLine = ""
- }
- var wordToSplit = word
- while !wordToSplit.isEmpty {
- let splitIndex = wordToSplit.index(wordToSplit.startIndex, offsetBy: min(maxLineLength, wordToSplit.count))
- result.append(String(wordToSplit[..<splitIndex]))
- wordToSplit = String(wordToSplit[splitIndex...])
- }
- } else {
- if currentLine.isEmpty {
- currentLine = word
- } else if currentLine.count + word.count + 1 <= maxLineLength {
- currentLine += " " + word
- } else {
- result.append(currentLine)
- currentLine = word
- }
- }
- }
- if !currentLine.isEmpty {
- result.append(currentLine)
- }
- }
- return result.joined(separator: "\r\n")
- }
- func formatPillText(line1: String, time: TimeInterval, line2: String? = nil) -> String {
- let dateFormatter = DateFormatter()
- if dateTimeUtils.is24Hour() {
- dateFormatter.setLocalizedDateFormatFromTemplate("HH:mm")
- } else {
- dateFormatter.setLocalizedDateFormatFromTemplate("hh:mm")
- }
- if Storage.shared.graphTimeZoneEnabled.value,
- let tz = TimeZone(identifier: Storage.shared.graphTimeZoneIdentifier.value)
- {
- dateFormatter.timeZone = tz
- }
- let wrappedLine1 = wrapText(line1, maxLineLength: 40)
- let date = Date(timeIntervalSince1970: time)
- let formattedDate = dateFormatter.string(from: date)
- if let line2 = line2 {
- return wrappedLine1 + "\r\n" + line2 + "\r\n" + formattedDate
- } else {
- return wrappedLine1 + "\r\n" + formattedDate
- }
- }
- func updateConeGraph(coneData: [ConeChartDataEntry]) {
- let dataIndex = GraphDataIndex.predictionCone.rawValue
- let mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.clear()
- for entry in coneData {
- mainChart.addEntry(entry)
- }
- BGChart.rightAxis.axisMaximum = Double(calculateMaxBgGraphValue())
- updateChartRenderers()
- }
- func clearConeGraph() {
- let dataIndex = GraphDataIndex.predictionCone.rawValue
- guard let lineData = BGChart.lineData, lineData.dataSets.count > dataIndex else { return }
- lineData.dataSets[dataIndex].clear()
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- }
- func updateOpenAPSPredictionDisplay() {
- guard let predBGs = openAPSPredBGs else { return }
- // Cone is only for OpenAPS-based systems; Loop always uses lines
- let displayType: PredictionDisplayType = Storage.shared.device.value == "Loop" ? .lines : Storage.shared.predictionDisplayType.value
- let toLoad = Int(Storage.shared.predictionToLoad.value * 12)
- let predictionStart = openAPSPredUpdatedTime ?? Date().timeIntervalSince1970
- let predictionTypes: [(type: String, colorName: String, dataIndex: Int)] = [
- ("ZT", "ZT", GraphDataIndex.ztPrediction.rawValue),
- ("IOB", "Insulin", GraphDataIndex.iobPrediction.rawValue),
- ("COB", "LoopYellow", GraphDataIndex.cobPrediction.rawValue),
- ("UAM", "UAM", GraphDataIndex.uamPrediction.rawValue),
- ]
- topPredictionBG = Storage.shared.minBGScale.value
- if displayType == .cone {
- var allArrays = [[Double]]()
- for (type, _, _) in predictionTypes {
- if let arr = predBGs[type], !arr.isEmpty {
- allArrays.append(arr)
- }
- }
- var coneData = [ConeChartDataEntry]()
- if !allArrays.isEmpty {
- // Cap at the shortest predBG array length so every cone point uses
- // the same set of contributing arrays. Matches Trio's ForecastSetup.
- let coneLength = min(allArrays.map { $0.count }.min()!, toLoad + 1)
- var t = predictionStart
- for i in 0 ..< coneLength {
- var valuesAtIndex = [Double]()
- for arr in allArrays where i < arr.count {
- valuesAtIndex.append(arr[i])
- }
- if !valuesAtIndex.isEmpty {
- var yMin = max(valuesAtIndex.min()!, Double(globalVariables.minDisplayGlucose))
- var yMax = min(valuesAtIndex.max()!, Double(globalVariables.maxDisplayGlucose))
- // Ensure minimum ±1 mg/dL range so the cone is visible when predictions agree
- if yMin == yMax {
- yMin -= 1
- yMax += 1
- }
- coneData.append(ConeChartDataEntry(x: t, yMin: yMin, yMax: yMax))
- if yMax > topPredictionBG - 20 { topPredictionBG = yMax + 20 }
- }
- t += 300
- }
- }
- updateConeGraph(coneData: coneData)
- // Clear individual prediction lines
- for (_, _, dataIndex) in predictionTypes {
- updatePredictionGraphGeneric(dataIndex: dataIndex, predictionData: [], chartLabel: "", color: .clear)
- }
- } else {
- clearConeGraph()
- for (type, colorName, dataIndex) in predictionTypes {
- var predictionData = [ShareGlucoseData]()
- if let graphdata = predBGs[type] {
- var t = predictionStart
- for i in 0 ... toLoad {
- if i < graphdata.count {
- let v = graphdata[i]
- let clamped = min(max(Int(round(v)), globalVariables.minDisplayGlucose), globalVariables.maxDisplayGlucose)
- predictionData.append(ShareGlucoseData(sgv: clamped, date: t, direction: "flat"))
- t += 300
- }
- }
- }
- let color = UIColor(named: colorName) ?? UIColor.systemPurple
- updatePredictionGraphGeneric(dataIndex: dataIndex, predictionData: predictionData, chartLabel: type, color: color)
- }
- }
- }
- func updatePredictionGraphGeneric(
- dataIndex: Int,
- predictionData: [ShareGlucoseData],
- chartLabel: String,
- color: UIColor
- ) {
- let mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
- let smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
- mainChart.clear()
- smallChart.clear()
- var colors = [NSUIColor]()
- let maxBGOffset: Double = 20
- for i in 0 ..< predictionData.count {
- let predictionVal = Double(predictionData[i].sgv)
- if predictionVal > topPredictionBG - maxBGOffset {
- topPredictionBG = predictionVal + maxBGOffset
- }
- if i == 0 {
- if Storage.shared.showDots.value {
- colors.append(color.withAlphaComponent(0.0))
- } else {
- colors.append(color.withAlphaComponent(1.0))
- }
- } else {
- colors.append(color)
- }
- let value = ChartDataEntry(
- x: predictionData[i].date,
- y: predictionVal,
- data: formatPillText(
- line1: chartLabel,
- time: predictionData[i].date,
- line2: Localizer.toDisplayUnits(String(predictionVal))
- )
- )
- mainChart.addEntry(value)
- smallChart.addEntry(value)
- }
- smallChart.circleColors.removeAll()
- smallChart.colors.removeAll()
- mainChart.colors.removeAll()
- mainChart.circleColors.removeAll()
- if colors.count > 0 {
- for color in colors {
- mainChart.addColor(color)
- mainChart.circleColors.append(color)
- smallChart.addColor(color)
- smallChart.circleColors.append(color)
- }
- }
- BGChart.rightAxis.axisMaximum = Double(calculateMaxBgGraphValue())
- BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChart.data?.notifyDataChanged()
- BGChart.notifyDataSetChanged()
- BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
- BGChartFull.data?.notifyDataChanged()
- BGChartFull.notifyDataSetChanged()
- }
- }
|