Support heart rate in overview graph

This commit is contained in:
Robert Buessow 2023-05-10 17:02:14 +02:00 committed by robertbuessow
parent 7faa34aa50
commit ecea212c83
15 changed files with 77 additions and 10 deletions

View file

@ -327,6 +327,7 @@ class HistoryBrowseActivity : TranslatedDaggerAppCompatActivity() {
var useRatioForScale = false var useRatioForScale = false
var useDSForScale = false var useDSForScale = false
var useBGIForScale = false var useBGIForScale = false
var useHRForScale = false
when { when {
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true
@ -335,6 +336,7 @@ class HistoryBrowseActivity : TranslatedDaggerAppCompatActivity() {
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] -> useHRForScale = true
} }
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
@ -345,6 +347,7 @@ class HistoryBrowseActivity : TranslatedDaggerAppCompatActivity() {
if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8) if (menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]) secondGraphData.addMinusBGI(useBGIForScale, if (alignDevBgiScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8) if (menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal]) secondGraphData.addRatio(useRatioForScale, if (useRatioForScale) 1.0 else 0.8)
if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && config.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0) if (menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] && config.isDev()) secondGraphData.addDeviationSlope(useDSForScale, 1.0)
if (menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] && config.isDev()) secondGraphData.addHeartRate(useHRForScale, 1.0)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
secondGraphData.formatAxis(historyBrowserData.overviewData.fromTime, historyBrowserData.overviewData.endTime) secondGraphData.formatAxis(historyBrowserData.overviewData.fromTime, historyBrowserData.overviewData.endTime)

View file

@ -150,4 +150,7 @@ interface OverviewData {
val dsMinScale: Scale val dsMinScale: Scale
var dsMaxSeries: LineGraphSeries<ScaledDataPoint> var dsMaxSeries: LineGraphSeries<ScaledDataPoint>
var dsMinSeries: LineGraphSeries<ScaledDataPoint> var dsMinSeries: LineGraphSeries<ScaledDataPoint>
} var heartRateScale: Scale
var heartRateGraphSeries: LineGraphSeries<DataPointWithLabelInterface>
}

View file

@ -0,0 +1,24 @@
package info.nightscout.core.graph.data
import android.content.Context
import android.graphics.Paint
import info.nightscout.database.entities.HeartRate
import info.nightscout.shared.interfaces.ResourceHelper
class HeartRateDataPoint(
private val data: HeartRate,
private val rh: ResourceHelper,
) : DataPointWithLabelInterface {
override fun getX(): Double = (data.timestamp - data.duration).toDouble()
override fun getY(): Double = data.beatsPerMinute
override fun setY(y: Double) {}
override val label: String = ""
override val duration = data.duration
override val shape = PointsWithLabelGraphSeries.Shape.HEARTRATE
override val size = 1f
override val paintStyle: Paint.Style = Paint.Style.FILL
override fun color(context: Context?): Int = rh.gac(context, info.nightscout.core.ui.R.attr.heartRateColor)
}

View file

@ -54,7 +54,8 @@ public class PointsWithLabelGraphSeries<E extends DataPointWithLabelInterface> e
GENERAL_WITH_DURATION, GENERAL_WITH_DURATION,
COB_FAIL_OVER, COB_FAIL_OVER,
IOB_PREDICTION, IOB_PREDICTION,
BUCKETED_BG BUCKETED_BG,
HEARTRATE,
} }
/** /**
@ -324,6 +325,10 @@ public class PointsWithLabelGraphSeries<E extends DataPointWithLabelInterface> e
mPaint.setStrokeWidth(5); mPaint.setStrokeWidth(5);
canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint); canvas.drawRect(endX - 3, bounds.top + py - 3, xPlusLength + 3, bounds.bottom + py + 3, mPaint);
} }
} else if (value.getShape() == Shape.HEARTRATE) {
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(0);
canvas.drawCircle(endX, endY, 1F, mPaint);
} }
// set values above point // set values above point
} }

View file

@ -15,7 +15,8 @@ interface OverviewMenus {
BGI, BGI,
SEN, SEN,
ACT, ACT,
DEVSLOPE DEVSLOPE,
HR,
} }
val setting: List<Array<Boolean>> val setting: List<Array<Boolean>>
@ -23,4 +24,4 @@ interface OverviewMenus {
fun setupChartMenu(context: Context, chartButton: ImageButton) fun setupChartMenu(context: Context, chartButton: ImageButton)
fun enabledTypes(graph: Int): String fun enabledTypes(graph: Int): String
fun isEnabledIn(type: CharType): Int fun isEnabledIn(type: CharType): Int
} }

View file

@ -216,6 +216,7 @@
<item name="inRangeBackground">@color/inRangeBackground</item> <item name="inRangeBackground">@color/inRangeBackground</item>
<item name="devSlopePosColor">@color/devSlopePos</item> <item name="devSlopePosColor">@color/devSlopePos</item>
<item name="devSlopeNegColor">@color/devSlopeNeg</item> <item name="devSlopeNegColor">@color/devSlopeNeg</item>
<item name="heartRateColor">@color/heartRate</item>
<item name="deviationGreyColor">@color/deviationGrey</item> <item name="deviationGreyColor">@color/deviationGrey</item>
<item name="deviationBlackColor">@color/deviationBlack</item> <item name="deviationBlackColor">@color/deviationBlack</item>
<item name="deviationGreenColor">@color/deviationGreen</item> <item name="deviationGreenColor">@color/deviationGreen</item>

View file

@ -181,6 +181,7 @@
<attr name="bolusDataPointColor" format="reference|color" /> <attr name="bolusDataPointColor" format="reference|color" />
<attr name="profileSwitchColor" format="reference|color" /> <attr name="profileSwitchColor" format="reference|color" />
<attr name="originalBgValueColor" format="reference|color" /> <attr name="originalBgValueColor" format="reference|color" />
<attr name="heartRateColor" format="reference|color" />
<attr name="therapyEvent_NS_MBG" format="reference|color" /> <attr name="therapyEvent_NS_MBG" format="reference|color" />
<attr name="therapyEvent_FINGER_STICK_BG_VALUE" format="reference|color" /> <attr name="therapyEvent_FINGER_STICK_BG_VALUE" format="reference|color" />
<attr name="therapyEvent_EXERCISE" format="reference|color" /> <attr name="therapyEvent_EXERCISE" format="reference|color" />
@ -221,4 +222,4 @@
<attr name="crossTargetColor" format="reference|color" /> <attr name="crossTargetColor" format="reference|color" />
<!---Custom button --> <!---Custom button -->
<attr name="customBtnStyle" format="reference"/> <attr name="customBtnStyle" format="reference"/>
</resources> </resources>

View file

@ -153,6 +153,7 @@
<color name="bgi">#00EEEE</color> <color name="bgi">#00EEEE</color>
<color name="devSlopePos">#FFFFFF00</color> <color name="devSlopePos">#FFFFFF00</color>
<color name="devSlopeNeg">#FFFF00FF</color> <color name="devSlopeNeg">#FFFF00FF</color>
<color name="heartRate">#FFFFFF66</color>
<color name="actionsConfirm">#F6CE22</color> <color name="actionsConfirm">#F6CE22</color>
<color name="deviations">#FF0000</color> <color name="deviations">#FF0000</color>
<color name="cobAlert">#7484E2</color> <color name="cobAlert">#7484E2</color>

View file

@ -219,6 +219,7 @@
<item name="inRangeBackground">@color/inRangeBackground</item> <item name="inRangeBackground">@color/inRangeBackground</item>
<item name="devSlopePosColor">@color/devSlopePos</item> <item name="devSlopePosColor">@color/devSlopePos</item>
<item name="devSlopeNegColor">@color/devSlopeNeg</item> <item name="devSlopeNegColor">@color/devSlopeNeg</item>
<item name="heartRateColor">@color/heartRate</item>
<item name="deviationGreyColor">@color/deviationGrey</item> <item name="deviationGreyColor">@color/deviationGrey</item>
<item name="deviationBlackColor">@color/deviationBlack</item> <item name="deviationBlackColor">@color/deviationBlack</item>
<item name="deviationGreenColor">@color/deviationGreen</item> <item name="deviationGreenColor">@color/deviationGreen</item>

View file

@ -87,6 +87,7 @@ class OverviewDataImpl @Inject constructor(
dsMinSeries = LineGraphSeries() dsMinSeries = LineGraphSeries()
treatmentsSeries = PointsWithLabelGraphSeries() treatmentsSeries = PointsWithLabelGraphSeries()
epsSeries = PointsWithLabelGraphSeries() epsSeries = PointsWithLabelGraphSeries()
heartRateGraphSeries = LineGraphSeries()
} }
override fun initRange() { override fun initRange() {
@ -322,4 +323,6 @@ class OverviewDataImpl @Inject constructor(
override val dsMinScale = Scale() override val dsMinScale = Scale()
override var dsMaxSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries() override var dsMaxSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
override var dsMinSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries() override var dsMinSeries: LineGraphSeries<ScaledDataPoint> = LineGraphSeries()
override var heartRateScale = Scale()
override var heartRateGraphSeries: LineGraphSeries<DataPointWithLabelInterface> = LineGraphSeries()
} }

View file

@ -1030,6 +1030,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
var useRatioForScale = false var useRatioForScale = false
var useDSForScale = false var useDSForScale = false
var useBGIForScale = false var useBGIForScale = false
var useHRForScale = false
when { when {
menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.ABS.ordinal] -> useABSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.IOB.ordinal] -> useIobForScale = true
@ -1038,6 +1039,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] -> useBGIForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] -> useRatioForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] -> useDSForScale = true
menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal] -> useHRForScale = true
} }
val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] val alignDevBgiScale = menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] && menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal]
@ -1052,6 +1054,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
if (useDSForScale) 1.0 else 0.8, if (useDSForScale) 1.0 else 0.8,
useRatioForScale useRatioForScale
) )
if (menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal]) secondGraphData.addHeartRate(useHRForScale, if (useHRForScale) 1.0 else 0.8)
// set manual x bounds to have nice steps // set manual x bounds to have nice steps
secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime) secondGraphData.formatAxis(overviewData.fromTime, overviewData.endTime)
@ -1067,7 +1070,8 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] || menuChartSettings[g + 1][OverviewMenus.CharType.DEV.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] || menuChartSettings[g + 1][OverviewMenus.CharType.BGI.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] || menuChartSettings[g + 1][OverviewMenus.CharType.SEN.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] menuChartSettings[g + 1][OverviewMenus.CharType.DEVSLOPE.ordinal] ||
menuChartSettings[g + 1][OverviewMenus.CharType.HR.ordinal]
).toVisibility() ).toVisibility()
secondaryGraphsData[g].performUpdate() secondaryGraphsData[g].performUpdate()
} }

View file

@ -47,7 +47,8 @@ class OverviewMenusImpl @Inject constructor(
BGI(R.string.overview_show_bgi, info.nightscout.core.ui.R.attr.bgiColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.bgi_shortname), BGI(R.string.overview_show_bgi, info.nightscout.core.ui.R.attr.bgiColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.bgi_shortname),
SEN(R.string.overview_show_sensitivity, info.nightscout.core.ui.R.attr.ratioColor, info.nightscout.core.ui.R.attr.menuTextColorInverse, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname), SEN(R.string.overview_show_sensitivity, info.nightscout.core.ui.R.attr.ratioColor, info.nightscout.core.ui.R.attr.menuTextColorInverse, primary = false, secondary = true, shortnameId = R.string.sensitivity_shortname),
ACT(R.string.overview_show_activity, info.nightscout.core.ui.R.attr.activityColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.activity_shortname), ACT(R.string.overview_show_activity, info.nightscout.core.ui.R.attr.activityColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = true, secondary = false, shortnameId = R.string.activity_shortname),
DEVSLOPE(R.string.overview_show_deviation_slope, info.nightscout.core.ui.R.attr.devSlopePosColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.devslope_shortname) DEVSLOPE(R.string.overview_show_deviation_slope, info.nightscout.core.ui.R.attr.devSlopePosColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.devslope_shortname),
HR(R.string.overview_show_heartRate, info.nightscout.core.ui.R.attr.heartRateColor, info.nightscout.core.ui.R.attr.menuTextColor, primary = false, secondary = true, shortnameId = R.string.heartRate_shortname),
} }
companion object { companion object {
@ -202,4 +203,4 @@ class OverviewMenusImpl @Inject constructor(
return -1 return -1
} }
} }

View file

@ -258,5 +258,14 @@ class GraphData(
// draw it // draw it
graph.onDataChanged(false, false) graph.onDataChanged(false, false)
} }
}
fun addHeartRate(useForScale: Boolean, scale: Double) {
val maxHR = overviewData.heartRateGraphSeries.highestValueY
if (useForScale) {
minY = 0.0
maxY = maxHR
}
addSeries(overviewData.heartRateGraphSeries)
overviewData.heartRateScale.multiplier = maxY * scale / maxHR
}
}

View file

@ -291,6 +291,7 @@
<string name="overview_show_predictions">Predictions</string> <string name="overview_show_predictions">Predictions</string>
<string name="overview_show_treatments">Treatments</string> <string name="overview_show_treatments">Treatments</string>
<string name="overview_show_heartRate">Heart Rate</string>
<string name="overview_show_deviation_slope">Deviation slope</string> <string name="overview_show_deviation_slope">Deviation slope</string>
<string name="overview_show_activity">Activity</string> <string name="overview_show_activity">Activity</string>
<string name="overview_show_bgi">Blood Glucose Impact</string> <string name="overview_show_bgi">Blood Glucose Impact</string>
@ -308,6 +309,7 @@
<string name="abs_insulin_shortname">ABS</string> <string name="abs_insulin_shortname">ABS</string>
<string name="devslope_shortname">DEVSLOPE</string> <string name="devslope_shortname">DEVSLOPE</string>
<string name="treatments_shortname">TREAT</string> <string name="treatments_shortname">TREAT</string>
<string name="heartRate_shortname">HR</string>
<string name="sensitivity_shortname">SENS</string> <string name="sensitivity_shortname">SENS</string>
<string name="graph_scale">Graph scale</string> <string name="graph_scale">Graph scale</string>
<string name="graph_menu_divider_header">Graph</string> <string name="graph_menu_divider_header">Graph</string>

View file

@ -3,6 +3,7 @@ package info.nightscout.workflow
import android.content.Context import android.content.Context
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import com.jjoe64.graphview.series.LineGraphSeries
import info.nightscout.core.events.EventIobCalculationProgress import info.nightscout.core.events.EventIobCalculationProgress
import info.nightscout.core.graph.OverviewData import info.nightscout.core.graph.OverviewData
import info.nightscout.core.graph.data.BolusDataPoint import info.nightscout.core.graph.data.BolusDataPoint
@ -10,6 +11,8 @@ import info.nightscout.core.graph.data.CarbsDataPoint
import info.nightscout.core.graph.data.DataPointWithLabelInterface import info.nightscout.core.graph.data.DataPointWithLabelInterface
import info.nightscout.core.graph.data.EffectiveProfileSwitchDataPoint import info.nightscout.core.graph.data.EffectiveProfileSwitchDataPoint
import info.nightscout.core.graph.data.ExtendedBolusDataPoint import info.nightscout.core.graph.data.ExtendedBolusDataPoint
import info.nightscout.core.graph.data.FixedLineGraphSeries
import info.nightscout.core.graph.data.HeartRateDataPoint
import info.nightscout.core.graph.data.PointsWithLabelGraphSeries import info.nightscout.core.graph.data.PointsWithLabelGraphSeries
import info.nightscout.core.graph.data.TherapyEventDataPoint import info.nightscout.core.graph.data.TherapyEventDataPoint
import info.nightscout.core.utils.receivers.DataWorkerStorage import info.nightscout.core.utils.receivers.DataWorkerStorage
@ -129,6 +132,11 @@ class PrepareTreatmentsDataWorker(
data.overviewData.therapyEventSeries = PointsWithLabelGraphSeries(filteredTherapyEvents.toTypedArray()) data.overviewData.therapyEventSeries = PointsWithLabelGraphSeries(filteredTherapyEvents.toTypedArray())
data.overviewData.epsSeries = PointsWithLabelGraphSeries(filteredEps.toTypedArray()) data.overviewData.epsSeries = PointsWithLabelGraphSeries(filteredEps.toTypedArray())
data.overviewData.heartRateGraphSeries = LineGraphSeries<DataPointWithLabelInterface>(
repository.getHeartRatesFromTimeToTime(fromTime, endTime)
.map { hr -> HeartRateDataPoint(hr, rh) }
.toTypedArray()).apply { color = rh.gac(null, info.nightscout.core.ui.R.attr.heartRateColor) }
rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TREATMENTS_DATA, 100, null)) rxBus.send(EventIobCalculationProgress(CalculationWorkflow.ProgressData.PREPARE_TREATMENTS_DATA, 100, null))
return Result.success() return Result.success()
} }
@ -149,4 +157,4 @@ class PrepareTreatmentsDataWorker(
private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> = private fun <E : DataPointWithLabelInterface> List<E>.filterTimeframe(fromTime: Long, endTime: Long): List<E> =
filter { it.x + it.duration >= fromTime && it.x <= endTime } filter { it.x + it.duration >= fromTime && it.x <= endTime }
} }