@@ -26,6 +26,7 @@ enum GraphDataIndex: Int {
2626 case uamPrediction = 15
2727 case smb = 16
2828 case tempTarget = 17
29+ case predictionCone = 18
2930}
3031
3132extension GraphDataIndex {
@@ -49,15 +50,17 @@ extension GraphDataIndex {
4950 case . uamPrediction: return " UAM Prediction "
5051 case . smb: return " SMB "
5152 case . tempTarget: return " Temp Target "
53+ case . predictionCone: return " Prediction Cone "
5254 }
5355 }
5456}
5557
5658class CompositeRenderer : LineChartRenderer {
5759 let tempTargetRenderer : TempTargetRenderer
5860 let triangleRenderer : TriangleRenderer
61+ let coneRenderer : ConeOfUncertaintyRenderer
5962
60- init ( dataProvider: LineChartDataProvider ? , animator: Animator ? , viewPortHandler: ViewPortHandler ? , tempTargetDataSetIndex: Int , smbDataSetIndex: Int ) {
63+ init ( dataProvider: LineChartDataProvider ? , animator: Animator ? , viewPortHandler: ViewPortHandler ? , tempTargetDataSetIndex: Int , smbDataSetIndex: Int , coneDataSetIndex : Int ) {
6164 tempTargetRenderer = TempTargetRenderer (
6265 dataProvider: dataProvider,
6366 animator: animator,
@@ -70,11 +73,18 @@ class CompositeRenderer: LineChartRenderer {
7073 viewPortHandler: viewPortHandler,
7174 smbDataSetIndex: smbDataSetIndex
7275 )
76+ coneRenderer = ConeOfUncertaintyRenderer (
77+ dataProvider: dataProvider,
78+ animator: animator,
79+ viewPortHandler: viewPortHandler,
80+ coneDataSetIndex: coneDataSetIndex
81+ )
7382 super. init ( dataProvider: dataProvider!, animator: animator!, viewPortHandler: viewPortHandler!)
7483 }
7584
7685 override func drawExtras( context: CGContext ) {
7786 super. drawExtras ( context: context)
87+ coneRenderer. drawExtras ( context: context)
7888 tempTargetRenderer. drawExtras ( context: context)
7989 triangleRenderer. drawExtras ( context: context)
8090 }
@@ -204,13 +214,15 @@ extension MainViewController {
204214 func updateChartRenderers( ) {
205215 let tempTargetDataIndex = GraphDataIndex . tempTarget. rawValue
206216 let smbDataIndex = GraphDataIndex . smb. rawValue
217+ let coneDataIndex = GraphDataIndex . predictionCone. rawValue
207218
208219 let compositeRenderer = CompositeRenderer (
209220 dataProvider: BGChart,
210221 animator: BGChart . chartAnimator,
211222 viewPortHandler: BGChart . viewPortHandler,
212223 tempTargetDataSetIndex: tempTargetDataIndex,
213- smbDataSetIndex: smbDataIndex
224+ smbDataSetIndex: smbDataIndex,
225+ coneDataSetIndex: coneDataIndex
214226 )
215227 BGChart . renderer = compositeRenderer
216228
@@ -595,7 +607,16 @@ extension MainViewController {
595607 data. append ( COBlinePrediction) // Dataset 14
596608 data. append ( UAMlinePrediction) // Dataset 15
597609 data. append ( lineSmb) // Dataset 16
598- data. append ( lineTempTarget)
610+ data. append ( lineTempTarget) // Dataset 17
611+
612+ // Dataset 18: Prediction Cone (rendered via ConeOfUncertaintyRenderer)
613+ let lineCone = LineChartDataSet ( entries: [ ChartDataEntry] ( ) , label: " " )
614+ lineCone. lineWidth = 0
615+ lineCone. drawCirclesEnabled = false
616+ lineCone. drawValuesEnabled = false
617+ lineCone. highlightEnabled = false
618+ lineCone. axisDependency = YAxis . AxisDependency. right
619+ data. append ( lineCone)
599620
600621 data. setValueFont ( UIFont . systemFont ( ofSize: 12 ) )
601622
@@ -783,6 +804,9 @@ extension MainViewController {
783804 BGChart . data? . dataSets [ dataIndex] . notifyDataSetChanged ( )
784805 BGChart . data? . notifyDataChanged ( )
785806 BGChart . notifyDataSetChanged ( )
807+
808+ // Re-render prediction display in case display type changed
809+ updateOpenAPSPredictionDisplay ( )
786810 }
787811
788812 func updateBGGraph( ) {
@@ -1605,7 +1629,16 @@ extension MainViewController {
16051629 data. append ( COBlinePrediction) // Dataset 14
16061630 data. append ( UAMlinePrediction) // Dataset 15
16071631 data. append ( lineSmb) // Dataset 16
1608- data. append ( lineTempTarget)
1632+ data. append ( lineTempTarget) // Dataset 17
1633+
1634+ // Dataset 18: Prediction Cone placeholder (not rendered on small chart)
1635+ let lineConeSmall = LineChartDataSet ( entries: [ ChartDataEntry] ( ) , label: " " )
1636+ lineConeSmall. lineWidth = 0
1637+ lineConeSmall. drawCirclesEnabled = false
1638+ lineConeSmall. drawValuesEnabled = false
1639+ lineConeSmall. highlightEnabled = false
1640+ lineConeSmall. axisDependency = YAxis . AxisDependency. right
1641+ data. append ( lineConeSmall)
16091642
16101643 BGChartFull . highlightPerDragEnabled = true
16111644 BGChartFull . leftAxis. enabled = false
@@ -1902,6 +1935,104 @@ extension MainViewController {
19021935 }
19031936 }
19041937
1938+ func updateConeGraph( coneData: [ ConeChartDataEntry ] ) {
1939+ let dataIndex = GraphDataIndex . predictionCone. rawValue
1940+ let mainChart = BGChart . lineData!. dataSets [ dataIndex] as! LineChartDataSet
1941+ mainChart. clear ( )
1942+ for entry in coneData {
1943+ mainChart. addEntry ( entry)
1944+ }
1945+ BGChart . rightAxis. axisMaximum = Double ( calculateMaxBgGraphValue ( ) )
1946+ updateChartRenderers ( )
1947+ }
1948+
1949+ func clearConeGraph( ) {
1950+ let dataIndex = GraphDataIndex . predictionCone. rawValue
1951+ guard let lineData = BGChart . lineData, lineData. dataSets. count > dataIndex else { return }
1952+ lineData. dataSets [ dataIndex] . clear ( )
1953+ BGChart . data? . dataSets [ dataIndex] . notifyDataSetChanged ( )
1954+ BGChart . data? . notifyDataChanged ( )
1955+ BGChart . notifyDataSetChanged ( )
1956+ }
1957+
1958+ func updateOpenAPSPredictionDisplay( ) {
1959+ guard let predBGs = openAPSPredBGs else { return }
1960+
1961+ // Cone is only for OpenAPS-based systems; Loop always uses lines
1962+ let displayType : PredictionDisplayType = Storage . shared. device. value == " Loop " ? . lines : Storage . shared. predictionDisplayType. value
1963+ let toLoad = Int ( Storage . shared. predictionToLoad. value * 12 )
1964+ let predictionStart = openAPSPredUpdatedTime ?? Date ( ) . timeIntervalSince1970
1965+
1966+ let predictionTypes : [ ( type: String , colorName: String , dataIndex: Int ) ] = [
1967+ ( " ZT " , " ZT " , GraphDataIndex . ztPrediction. rawValue) ,
1968+ ( " IOB " , " Insulin " , GraphDataIndex . iobPrediction. rawValue) ,
1969+ ( " COB " , " LoopYellow " , GraphDataIndex . cobPrediction. rawValue) ,
1970+ ( " UAM " , " UAM " , GraphDataIndex . uamPrediction. rawValue) ,
1971+ ]
1972+
1973+ topPredictionBG = Storage . shared. minBGScale. value
1974+
1975+ if displayType == . cone {
1976+ var allArrays = [ [ Double] ] ( )
1977+ for (type, _, _) in predictionTypes {
1978+ if let arr = predBGs [ type] , !arr. isEmpty {
1979+ allArrays. append ( arr)
1980+ }
1981+ }
1982+
1983+ var coneData = [ ConeChartDataEntry] ( )
1984+ if !allArrays. isEmpty {
1985+ let maxLength = min ( allArrays. map { $0. count } . max ( ) !, toLoad + 1 )
1986+ var t = predictionStart
1987+ for i in 0 ..< maxLength {
1988+ var valuesAtIndex = [ Double] ( )
1989+ for arr in allArrays where i < arr. count {
1990+ valuesAtIndex. append ( arr [ i] )
1991+ }
1992+ if !valuesAtIndex. isEmpty {
1993+ var yMin = max ( valuesAtIndex. min ( ) !, Double ( globalVariables. minDisplayGlucose) )
1994+ var yMax = min ( valuesAtIndex. max ( ) !, Double ( globalVariables. maxDisplayGlucose) )
1995+ // Ensure minimum ±1 mg/dL range so the cone is visible when predictions agree
1996+ if yMin == yMax {
1997+ yMin -= 1
1998+ yMax += 1
1999+ }
2000+ coneData. append ( ConeChartDataEntry ( x: t, yMin: yMin, yMax: yMax) )
2001+ if yMax > topPredictionBG - 20 { topPredictionBG = yMax + 20 }
2002+ }
2003+ t += 300
2004+ }
2005+ }
2006+
2007+ updateConeGraph ( coneData: coneData)
2008+
2009+ // Clear individual prediction lines
2010+ for (_, _, dataIndex) in predictionTypes {
2011+ updatePredictionGraphGeneric ( dataIndex: dataIndex, predictionData: [ ] , chartLabel: " " , color: . clear)
2012+ }
2013+
2014+ } else {
2015+ clearConeGraph ( )
2016+
2017+ for (type, colorName, dataIndex) in predictionTypes {
2018+ var predictionData = [ ShareGlucoseData] ( )
2019+ if let graphdata = predBGs [ type] {
2020+ var t = predictionStart
2021+ for i in 0 ... toLoad {
2022+ if i < graphdata. count {
2023+ let v = graphdata [ i]
2024+ let clamped = min ( max ( Int ( round ( v) ) , globalVariables. minDisplayGlucose) , globalVariables. maxDisplayGlucose)
2025+ predictionData. append ( ShareGlucoseData ( sgv: clamped, date: t, direction: " flat " ) )
2026+ t += 300
2027+ }
2028+ }
2029+ }
2030+ let color = UIColor ( named: colorName) ?? UIColor . systemPurple
2031+ updatePredictionGraphGeneric ( dataIndex: dataIndex, predictionData: predictionData, chartLabel: type, color: color)
2032+ }
2033+ }
2034+ }
2035+
19052036 func updatePredictionGraphGeneric(
19062037 dataIndex: Int ,
19072038 predictionData: [ ShareGlucoseData ] ,
0 commit comments