Graphs.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. //
  2. // Graphs.swift
  3. // LoopFollow
  4. //
  5. // Created by Jon Fawcett on 6/16/20.
  6. // Copyright © 2020 Jon Fawcett. All rights reserved.
  7. //
  8. import Foundation
  9. import Charts
  10. import UIKit
  11. extension MainViewController {
  12. func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
  13. if chartView == BGChartFull {
  14. BGChart.moveViewToX(entry.x)
  15. }
  16. }
  17. func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat) {
  18. if chartView == BGChart {
  19. let currentMatrix = chartView.viewPortHandler.touchMatrix
  20. //BGChartFull.viewPortHandler.refresh(newMatrix: currentMatrix, chart: BGChartFull, invalidate: true)
  21. //BGChartFull.highlightValue(x: Double(currentMatrix.tx), y: Double(currentMatrix.ty), dataSetIndex: 0)
  22. }
  23. }
  24. func createGraph(){
  25. self.BGChart.clear()
  26. // Create the BG Graph Data
  27. let entries = bgData
  28. var bgChartEntry = [ChartDataEntry]()
  29. var colors = [NSUIColor]()
  30. var maxBG: Float = UserDefaultsRepository.minBGScale.value
  31. // Setup BG line details
  32. let lineBG = LineChartDataSet(entries:bgChartEntry, label: "")
  33. lineBG.circleRadius = 3
  34. lineBG.circleColors = [NSUIColor.systemGreen]
  35. lineBG.drawCircleHoleEnabled = false
  36. lineBG.axisDependency = YAxis.AxisDependency.right
  37. lineBG.highlightEnabled = true
  38. lineBG.drawValuesEnabled = false
  39. if UserDefaultsRepository.showLines.value {
  40. lineBG.lineWidth = 2
  41. } else {
  42. lineBG.lineWidth = 0
  43. }
  44. if UserDefaultsRepository.showDots.value {
  45. lineBG.drawCirclesEnabled = true
  46. } else {
  47. lineBG.drawCirclesEnabled = false
  48. }
  49. lineBG.setDrawHighlightIndicators(false)
  50. lineBG.valueFont.withSize(50)
  51. // Setup Prediction line details
  52. var predictionChartEntry = [ChartDataEntry]()
  53. let linePrediction = LineChartDataSet(entries:predictionChartEntry, label: "")
  54. linePrediction.circleRadius = 3
  55. linePrediction.circleColors = [NSUIColor.systemPurple]
  56. linePrediction.drawCircleHoleEnabled = false
  57. linePrediction.axisDependency = YAxis.AxisDependency.right
  58. linePrediction.highlightEnabled = true
  59. linePrediction.drawValuesEnabled = false
  60. if UserDefaultsRepository.showLines.value {
  61. linePrediction.lineWidth = 2
  62. } else {
  63. linePrediction.lineWidth = 0
  64. }
  65. if UserDefaultsRepository.showDots.value {
  66. linePrediction.drawCirclesEnabled = true
  67. } else {
  68. linePrediction.drawCirclesEnabled = false
  69. }
  70. linePrediction.setDrawHighlightIndicators(false)
  71. linePrediction.valueFont.withSize(50)
  72. // create Basal graph data
  73. var chartEntry = [ChartDataEntry]()
  74. var maxBasal = UserDefaultsRepository.minBasalScale.value
  75. let lineBasal = LineChartDataSet(entries:chartEntry, label: "")
  76. lineBasal.setDrawHighlightIndicators(false)
  77. lineBasal.setColor(NSUIColor.systemBlue, alpha: 0.5)
  78. lineBasal.lineWidth = 0
  79. lineBasal.drawFilledEnabled = true
  80. lineBasal.fillColor = NSUIColor.systemBlue.withAlphaComponent(0.8)
  81. lineBasal.drawCirclesEnabled = false
  82. lineBasal.axisDependency = YAxis.AxisDependency.left
  83. lineBasal.highlightEnabled = true
  84. lineBasal.drawValuesEnabled = false
  85. // Boluses
  86. var chartEntryBolus = [ChartDataEntry]()
  87. let lineBolus = LineChartDataSet(entries:chartEntryBolus, label: "")
  88. lineBolus.circleRadius = 7
  89. lineBolus.circleColors = [NSUIColor.systemBlue.withAlphaComponent(0.75)]
  90. lineBolus.drawCircleHoleEnabled = false
  91. lineBolus.setDrawHighlightIndicators(false)
  92. lineBolus.setColor(NSUIColor.systemBlue, alpha: 1.0)
  93. lineBolus.drawCirclesEnabled = true
  94. lineBolus.lineWidth = 0
  95. lineBolus.highlightEnabled = false
  96. lineBolus.axisDependency = YAxis.AxisDependency.right
  97. lineBolus.valueFormatter = ChartYDataValueFormatter()
  98. lineBolus.drawValuesEnabled = true
  99. lineBolus.valueTextColor = NSUIColor.label
  100. // Carbs
  101. var chartEntryCarbs = [ChartDataEntry]()
  102. let lineCarbs = LineChartDataSet(entries:chartEntryCarbs, label: "")
  103. lineCarbs.circleRadius = 7
  104. lineCarbs.circleColors = [NSUIColor.systemOrange.withAlphaComponent(0.75)]
  105. lineCarbs.drawCircleHoleEnabled = false
  106. lineCarbs.setDrawHighlightIndicators(false)
  107. lineCarbs.setColor(NSUIColor.systemBlue, alpha: 1.0)
  108. lineCarbs.drawCirclesEnabled = true
  109. lineCarbs.lineWidth = 0
  110. lineCarbs.highlightEnabled = false
  111. lineCarbs.axisDependency = YAxis.AxisDependency.right
  112. lineCarbs.valueFormatter = ChartYDataValueFormatter()
  113. lineCarbs.drawValuesEnabled = true
  114. lineCarbs.valueTextColor = NSUIColor.label
  115. // create Scheduled Basal graph data
  116. var chartBasalScheduledEntry = [ChartDataEntry]()
  117. let lineBasalScheduled = LineChartDataSet(entries:chartBasalScheduledEntry, label: "")
  118. lineBasalScheduled.setDrawHighlightIndicators(false)
  119. lineBasalScheduled.setColor(NSUIColor.systemBlue, alpha: 0.8)
  120. lineBasalScheduled.lineWidth = 2
  121. lineBasalScheduled.drawFilledEnabled = false
  122. lineBasalScheduled.drawCirclesEnabled = false
  123. lineBasalScheduled.axisDependency = YAxis.AxisDependency.left
  124. lineBasalScheduled.highlightEnabled = false
  125. lineBasalScheduled.drawValuesEnabled = false
  126. lineBasalScheduled.lineDashLengths = [10.0, 5.0]
  127. // create Override graph data
  128. var chartOverrideEntry = [ChartDataEntry]()
  129. let lineOverride = LineChartDataSet(entries:chartOverrideEntry, label: "")
  130. lineOverride.setDrawHighlightIndicators(false)
  131. lineOverride.setColor(NSUIColor.systemTeal, alpha: 0.8 )
  132. lineOverride.lineWidth = 10
  133. lineOverride.drawFilledEnabled = false
  134. lineOverride.drawCirclesEnabled = false
  135. lineOverride.axisDependency = YAxis.AxisDependency.right
  136. lineOverride.highlightEnabled = true
  137. lineOverride.drawValuesEnabled = false
  138. // Setup the chart data of all lines
  139. let data = LineChartData()
  140. data.addDataSet(lineBG) // Dataset 0
  141. data.addDataSet(linePrediction) // Dataset 1
  142. data.addDataSet(lineBasal) // Dataset 2
  143. data.addDataSet(lineBolus) // Dataset 3
  144. data.addDataSet(lineCarbs) // Dataset 4
  145. data.addDataSet(lineBasalScheduled) // Dataset 5
  146. data.addDataSet(lineOverride) // Dataset 6
  147. data.setValueFont(UIFont.systemFont(ofSize: 12))
  148. // Add marker popups for bolus and carbs
  149. let marker = PillMarker(color: .secondarySystemBackground, font: UIFont.boldSystemFont(ofSize: 14), textColor: .label)
  150. BGChart.marker = marker
  151. // Clear limit lines so they don't add multiples when changing the settings
  152. BGChart.rightAxis.removeAllLimitLines()
  153. //Add lower red line based on low alert value
  154. let ll = ChartLimitLine()
  155. ll.limit = Double(UserDefaultsRepository.lowLine.value)
  156. ll.lineColor = NSUIColor.systemRed.withAlphaComponent(0.5)
  157. BGChart.rightAxis.addLimitLine(ll)
  158. //Add upper yellow line based on low alert value
  159. let ul = ChartLimitLine()
  160. ul.limit = Double(UserDefaultsRepository.highLine.value)
  161. ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
  162. BGChart.rightAxis.addLimitLine(ul)
  163. // Setup the main graph overall details
  164. BGChart.xAxis.valueFormatter = ChartXValueFormatter()
  165. BGChart.xAxis.granularity = 1800
  166. BGChart.xAxis.labelTextColor = NSUIColor.label
  167. BGChart.xAxis.labelPosition = XAxis.LabelPosition.bottom
  168. BGChart.xAxis.drawGridLinesEnabled = false
  169. BGChart.leftAxis.enabled = true
  170. BGChart.leftAxis.labelPosition = YAxis.LabelPosition.insideChart
  171. BGChart.leftAxis.axisMaximum = maxBasal
  172. BGChart.leftAxis.axisMinimum = 0.0
  173. BGChart.leftAxis.drawGridLinesEnabled = false
  174. BGChart.rightAxis.labelTextColor = NSUIColor.label
  175. BGChart.rightAxis.labelPosition = YAxis.LabelPosition.insideChart
  176. BGChart.rightAxis.axisMinimum = Double(UserDefaultsRepository.minBGValue.value)
  177. BGChart.rightAxis.axisMaximum = Double(maxBG)
  178. BGChart.rightAxis.gridLineDashLengths = [5.0, 5.0]
  179. BGChart.rightAxis.valueFormatter = ChartYMMOLValueFormatter()
  180. BGChart.legend.enabled = false
  181. BGChart.scaleYEnabled = false
  182. BGChart.drawGridBackgroundEnabled = false
  183. //BGChart.gridBackgroundColor = NSUIColor.secondarySystemBackground
  184. BGChart.data = data
  185. BGChart.setExtraOffsets(left: 10, top: 10, right: 10, bottom: 10)
  186. BGChart.setVisibleXRangeMinimum(10)
  187. }
  188. func updateBGGraphSettings() {
  189. let dataIndex = 0
  190. let lineBG = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
  191. if UserDefaultsRepository.showLines.value {
  192. lineBG.lineWidth = 2
  193. } else {
  194. lineBG.lineWidth = 0
  195. }
  196. if UserDefaultsRepository.showDots.value {
  197. lineBG.drawCirclesEnabled = true
  198. } else {
  199. lineBG.drawCirclesEnabled = false
  200. }
  201. BGChart.rightAxis.axisMinimum = Double(UserDefaultsRepository.minBGValue.value)
  202. // Clear limit lines so they don't add multiples when changing the settings
  203. BGChart.rightAxis.removeAllLimitLines()
  204. //Add lower red line based on low alert value
  205. let ll = ChartLimitLine()
  206. ll.limit = Double(UserDefaultsRepository.lowLine.value)
  207. ll.lineColor = NSUIColor.systemRed.withAlphaComponent(0.5)
  208. BGChart.rightAxis.addLimitLine(ll)
  209. //Add upper yellow line based on low alert value
  210. let ul = ChartLimitLine()
  211. ul.limit = Double(UserDefaultsRepository.highLine.value)
  212. ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
  213. BGChart.rightAxis.addLimitLine(ul)
  214. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  215. BGChart.data?.notifyDataChanged()
  216. BGChart.notifyDataSetChanged()
  217. }
  218. func updateBGGraph() {
  219. let dataIndex = 0
  220. let entries = bgData
  221. var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
  222. var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
  223. mainChart.clear()
  224. smallChart.clear()
  225. var maxBG = UserDefaultsRepository.minBGScale.value
  226. var maxBGOffset: Float = 0.0
  227. if UserDefaultsRepository.offsetCarbsBolus.value {
  228. maxBGOffset = 40.0
  229. }
  230. var colors = [NSUIColor]()
  231. for i in 0..<entries.count{
  232. if Float(entries[i].sgv) > maxBG - maxBGOffset {
  233. maxBG = Float(entries[i].sgv) + maxBGOffset
  234. }
  235. let value = ChartDataEntry(x: Double(entries[i].date), y: Double(entries[i].sgv))
  236. mainChart.addEntry(value)
  237. smallChart.addEntry(value)
  238. if Double(entries[i].sgv) >= Double(UserDefaultsRepository.highLine.value) {
  239. colors.append(NSUIColor.systemYellow)
  240. } else if Double(entries[i].sgv) <= Double(UserDefaultsRepository.lowLine.value) {
  241. colors.append(NSUIColor.systemRed)
  242. } else {
  243. colors.append(NSUIColor.systemGreen)
  244. }
  245. }
  246. // Set Colors
  247. let lineBG = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
  248. let lineBGSmall = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
  249. lineBG.colors.removeAll()
  250. lineBG.circleColors.removeAll()
  251. lineBGSmall.colors.removeAll()
  252. lineBGSmall.circleColors.removeAll()
  253. if colors.count > 0 {
  254. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Graph: colors") }
  255. for i in 0..<colors.count{
  256. mainChart.addColor(colors[i])
  257. mainChart.circleColors.append(colors[i])
  258. smallChart.addColor(colors[i])
  259. smallChart.circleColors.append(colors[i])
  260. }
  261. }
  262. BGChart.rightAxis.axisMaximum = Double(maxBG)
  263. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  264. BGChart.data?.notifyDataChanged()
  265. BGChart.notifyDataSetChanged()
  266. BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
  267. BGChartFull.data?.notifyDataChanged()
  268. BGChartFull.notifyDataSetChanged()
  269. if firstGraphLoad {
  270. BGChart.zoom(scaleX: 18, scaleY: 1, x: 1, y: 1)
  271. firstGraphLoad = false
  272. }
  273. if BGChart.chartXMax > dateTimeUtils.getNowTimeIntervalUTC() {
  274. BGChart.moveViewToAnimated(xValue: dateTimeUtils.getNowTimeIntervalUTC() - (BGChart.visibleXRange * 0.7), yValue: 0.0, axis: .right, duration: 1, easingOption: .easeInBack)
  275. }
  276. }
  277. func updatePredictionGraph() {
  278. let dataIndex = 1
  279. var mainChart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
  280. var smallChart = BGChartFull.lineData!.dataSets[dataIndex] as! LineChartDataSet
  281. mainChart.clear()
  282. smallChart.clear()
  283. if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Graph: print prediction") }
  284. for i in 0..<predictionData.count {
  285. let predictionVal = Double(predictionData[i].sgv)
  286. // Below can be turned on to prevent out of range on the graph if desired.
  287. // It currently just drops them out of view
  288. if predictionVal > 400 {
  289. // predictionVal = 400
  290. } else if predictionVal < 0 {
  291. // predictionVal = 0
  292. }
  293. let value = ChartDataEntry(x: predictionData[i].date, y: predictionVal)
  294. mainChart.addEntry(value)
  295. smallChart.addEntry(value)
  296. }
  297. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  298. BGChart.data?.notifyDataChanged()
  299. BGChart.notifyDataSetChanged()
  300. BGChartFull.data?.dataSets[dataIndex].notifyDataSetChanged()
  301. BGChartFull.data?.notifyDataChanged()
  302. BGChartFull.notifyDataSetChanged()
  303. }
  304. func updateBasalGraph() {
  305. var dataIndex = 2
  306. BGChart.lineData?.dataSets[dataIndex].clear()
  307. var maxBasal = UserDefaultsRepository.minBasalScale.value
  308. for i in 0..<basalData.count{
  309. let value = ChartDataEntry(x: Double(basalData[i].date), y: Double(basalData[i].basalRate))
  310. BGChart.data?.dataSets[dataIndex].addEntry(value)
  311. if basalData[i].basalRate > maxBasal {
  312. maxBasal = basalData[i].basalRate
  313. }
  314. }
  315. BGChart.leftAxis.axisMaximum = maxBasal
  316. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  317. BGChart.data?.notifyDataChanged()
  318. BGChart.notifyDataSetChanged()
  319. }
  320. func updateBasalScheduledGraph() {
  321. var dataIndex = 5
  322. BGChart.lineData?.dataSets[dataIndex].clear()
  323. for i in 0..<basalScheduleData.count{
  324. let value = ChartDataEntry(x: Double(basalScheduleData[i].date), y: Double(basalScheduleData[i].basalRate))
  325. BGChart.data?.dataSets[dataIndex].addEntry(value)
  326. }
  327. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  328. BGChart.data?.notifyDataChanged()
  329. BGChart.notifyDataSetChanged()
  330. }
  331. func updateBolusGraph() {
  332. var dataIndex = 3
  333. BGChart.lineData?.dataSets[dataIndex].clear()
  334. for i in 0..<bolusData.count{
  335. let formatter = NumberFormatter()
  336. formatter.minimumFractionDigits = 0
  337. formatter.maximumFractionDigits = 2
  338. formatter.minimumIntegerDigits = 1
  339. var offset = 0
  340. if UserDefaultsRepository.offsetCarbsBolus.value {
  341. offset = 10
  342. }
  343. let value = ChartDataEntry(x: Double(bolusData[i].date), y: Double(bolusData[i].sgv + offset), data: formatter.string(from: NSNumber(value: bolusData[i].value)))
  344. BGChart.data?.dataSets[dataIndex].addEntry(value)
  345. }
  346. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  347. BGChart.data?.notifyDataChanged()
  348. BGChart.notifyDataSetChanged()
  349. }
  350. func updateCarbGraph() {
  351. var dataIndex = 4
  352. BGChart.lineData?.dataSets[dataIndex].clear()
  353. for i in 0..<carbData.count{
  354. let formatter = NumberFormatter()
  355. formatter.minimumFractionDigits = 0
  356. formatter.maximumFractionDigits = 2
  357. formatter.minimumIntegerDigits = 1
  358. var offset = 0
  359. if UserDefaultsRepository.offsetCarbsBolus.value {
  360. offset = 30
  361. }
  362. let value = ChartDataEntry(x: Double(carbData[i].date), y: Double(carbData[i].sgv + offset), data: formatter.string(from: NSNumber(value: carbData[i].value)))
  363. BGChart.data?.dataSets[dataIndex].addEntry(value)
  364. }
  365. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  366. BGChart.data?.notifyDataChanged()
  367. BGChart.notifyDataSetChanged()
  368. }
  369. func createSmallBGGraph(){
  370. let entries = bgData
  371. var bgChartEntry = [ChartDataEntry]()
  372. var colors = [NSUIColor]()
  373. let lineBG = LineChartDataSet(entries:bgChartEntry, label: "")
  374. lineBG.drawCirclesEnabled = false
  375. //line2.setDrawHighlightIndicators(false)
  376. lineBG.highlightEnabled = true
  377. lineBG.drawHorizontalHighlightIndicatorEnabled = false
  378. lineBG.drawVerticalHighlightIndicatorEnabled = false
  379. lineBG.highlightColor = NSUIColor.label
  380. lineBG.drawValuesEnabled = false
  381. lineBG.lineWidth = 2
  382. // Setup Prediction line details
  383. var predictionChartEntry = [ChartDataEntry]()
  384. let linePrediction = LineChartDataSet(entries:predictionChartEntry, label: "")
  385. linePrediction.drawCirclesEnabled = false
  386. //line2.setDrawHighlightIndicators(false)
  387. linePrediction.setColor(NSUIColor.systemPurple)
  388. linePrediction.highlightEnabled = true
  389. linePrediction.drawHorizontalHighlightIndicatorEnabled = false
  390. linePrediction.drawVerticalHighlightIndicatorEnabled = false
  391. linePrediction.highlightColor = NSUIColor.label
  392. linePrediction.drawValuesEnabled = false
  393. linePrediction.lineWidth = 2
  394. let data = LineChartData()
  395. data.addDataSet(lineBG)
  396. data.addDataSet(linePrediction)
  397. BGChartFull.highlightPerDragEnabled = true
  398. BGChartFull.leftAxis.enabled = false
  399. BGChartFull.rightAxis.enabled = false
  400. BGChartFull.xAxis.enabled = false
  401. BGChartFull.legend.enabled = false
  402. BGChartFull.scaleYEnabled = false
  403. BGChartFull.scaleXEnabled = false
  404. BGChartFull.drawGridBackgroundEnabled = false
  405. BGChartFull.data = data
  406. }
  407. func updateOverrideGraph() {
  408. var dataIndex = 6
  409. var chart = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
  410. chart.clear()
  411. var colors = [NSUIColor]()
  412. for i in 0..<overrideData.count{
  413. let value = ChartDataEntry(x: Double(overrideData[i].date), y: Double(overrideData[i].sgv), data: overrideData[i].value)
  414. BGChart.data?.dataSets[dataIndex].addEntry(value)
  415. if Double(overrideData[i].value) == 1.0 {
  416. colors.append(NSUIColor.systemGray.withAlphaComponent(0.0))
  417. } else if i >= overrideData.count - 2 {
  418. colors.append(NSUIColor.systemGreen)
  419. } else {
  420. colors.append(NSUIColor.systemGray.withAlphaComponent(CGFloat(overrideData[i].value / 2)))
  421. }
  422. }
  423. // Set Colors
  424. let line = BGChart.lineData!.dataSets[dataIndex] as! LineChartDataSet
  425. line.colors.removeAll()
  426. line.circleColors.removeAll()
  427. if colors.count > 0 {
  428. for i in 0..<colors.count{
  429. chart.addColor(colors[i])
  430. chart.circleColors.append(colors[i])
  431. }
  432. }
  433. BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
  434. BGChart.data?.notifyDataChanged()
  435. BGChart.notifyDataSetChanged()
  436. }
  437. }