show filled gaps with different color
This commit is contained in:
parent
a3711f0c0c
commit
556c1a048f
9 changed files with 88 additions and 10 deletions
|
@ -2,6 +2,7 @@ package info.nightscout.core.graph.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
import com.jjoe64.graphview.series.DataPointInterface
|
import com.jjoe64.graphview.series.DataPointInterface
|
||||||
|
|
||||||
interface DataPointWithLabelInterface : DataPointInterface {
|
interface DataPointWithLabelInterface : DataPointInterface {
|
||||||
|
@ -15,5 +16,5 @@ interface DataPointWithLabelInterface : DataPointInterface {
|
||||||
val shape: PointsWithLabelGraphSeries.Shape
|
val shape: PointsWithLabelGraphSeries.Shape
|
||||||
val size: Float
|
val size: Float
|
||||||
val paintStyle: Paint.Style
|
val paintStyle: Paint.Style
|
||||||
fun color(context: Context?): Int
|
@ColorInt fun color(context: Context?): Int
|
||||||
}
|
}
|
|
@ -5,14 +5,12 @@ import android.graphics.Paint
|
||||||
import info.nightscout.database.entities.GlucoseValue
|
import info.nightscout.database.entities.GlucoseValue
|
||||||
import info.nightscout.interfaces.Constants
|
import info.nightscout.interfaces.Constants
|
||||||
import info.nightscout.interfaces.GlucoseUnit
|
import info.nightscout.interfaces.GlucoseUnit
|
||||||
import info.nightscout.interfaces.profile.DefaultValueHelper
|
|
||||||
import info.nightscout.interfaces.profile.Profile
|
import info.nightscout.interfaces.profile.Profile
|
||||||
import info.nightscout.interfaces.profile.ProfileFunction
|
import info.nightscout.interfaces.profile.ProfileFunction
|
||||||
import info.nightscout.shared.interfaces.ResourceHelper
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
|
||||||
class GlucoseValueDataPoint(
|
class GlucoseValueDataPoint(
|
||||||
val data: GlucoseValue,
|
val data: GlucoseValue,
|
||||||
private val defaultValueHelper: DefaultValueHelper,
|
|
||||||
private val profileFunction: ProfileFunction,
|
private val profileFunction: ProfileFunction,
|
||||||
private val rh: ResourceHelper
|
private val rh: ResourceHelper
|
||||||
) : DataPointWithLabelInterface {
|
) : DataPointWithLabelInterface {
|
||||||
|
|
|
@ -2,6 +2,8 @@ package info.nightscout.core.graph.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
import info.nightscout.interfaces.Constants
|
import info.nightscout.interfaces.Constants
|
||||||
import info.nightscout.interfaces.GlucoseUnit
|
import info.nightscout.interfaces.GlucoseUnit
|
||||||
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
|
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
|
||||||
|
@ -28,15 +30,17 @@ class InMemoryGlucoseValueDataPoint(
|
||||||
override val size = 1f
|
override val size = 1f
|
||||||
override val paintStyle: Paint.Style = Paint.Style.FILL
|
override val paintStyle: Paint.Style = Paint.Style.FILL
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
override fun color(context: Context?): Int {
|
override fun color(context: Context?): Int {
|
||||||
val units = profileFunction.getUnits()
|
val units = profileFunction.getUnits()
|
||||||
val lowLine = defaultValueHelper.determineLowLine()
|
val lowLine = defaultValueHelper.determineLowLine()
|
||||||
val highLine = defaultValueHelper.determineHighLine()
|
val highLine = defaultValueHelper.determineHighLine()
|
||||||
return when {
|
val color = when {
|
||||||
valueToUnits(units) < lowLine -> rh.gac(context, info.nightscout.core.ui.R.attr.bgLow)
|
valueToUnits(units) < lowLine -> rh.gac(context, info.nightscout.core.ui.R.attr.bgLow)
|
||||||
valueToUnits(units) > highLine -> rh.gac(context, info.nightscout.core.ui.R.attr.highColor)
|
valueToUnits(units) > highLine -> rh.gac(context, info.nightscout.core.ui.R.attr.highColor)
|
||||||
else -> rh.gac(context, info.nightscout.core.ui.R.attr.bgInRange)
|
else -> rh.gac(context, info.nightscout.core.ui.R.attr.bgInRange)
|
||||||
}
|
}
|
||||||
|
return if (data.filledGap) ColorUtils.setAlphaComponent(color, 128) else color
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package info.nightscout.core.graph.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import info.nightscout.interfaces.GlucoseUnit
|
||||||
|
import info.nightscout.interfaces.iob.InMemoryGlucoseValue
|
||||||
|
import info.nightscout.interfaces.profile.DefaultValueHelper
|
||||||
|
import info.nightscout.interfaces.profile.ProfileFunction
|
||||||
|
import info.nightscout.shared.interfaces.ResourceHelper
|
||||||
|
import org.junit.jupiter.api.Assertions
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.junit.jupiter.MockitoSettings
|
||||||
|
import org.mockito.kotlin.any
|
||||||
|
import org.mockito.quality.Strictness
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||||
|
internal class InMemoryGlucoseValueDataPointTest {
|
||||||
|
|
||||||
|
@Mock lateinit var defaultValueHelper: DefaultValueHelper
|
||||||
|
@Mock lateinit var profileFunction: ProfileFunction
|
||||||
|
@Mock lateinit var rh: ResourceHelper
|
||||||
|
@Mock lateinit var context: Context
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
Mockito.`when`(profileFunction.getUnits()).thenReturn(GlucoseUnit.MGDL)
|
||||||
|
Mockito.`when`(rh.gac(any(), any())).thenReturn(Color.GREEN)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun alphaShouldBeAddedForFilledGaps() {
|
||||||
|
val gv = InMemoryGlucoseValue(1000, 100.0)
|
||||||
|
val sut = InMemoryGlucoseValueDataPoint(gv, defaultValueHelper, profileFunction, rh)
|
||||||
|
|
||||||
|
var alpha = sut.color(context).ushr(24)
|
||||||
|
Assertions.assertEquals(255, alpha)
|
||||||
|
gv.filledGap = true
|
||||||
|
alpha = sut.color(context).ushr(24)
|
||||||
|
Assertions.assertEquals(128, alpha)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,35 @@ package info.nightscout.interfaces.iob
|
||||||
|
|
||||||
import info.nightscout.database.entities.GlucoseValue
|
import info.nightscout.database.entities.GlucoseValue
|
||||||
|
|
||||||
class InMemoryGlucoseValue constructor(var timestamp: Long = 0L, var value: Double = 0.0, var trendArrow: GlucoseValue.TrendArrow = GlucoseValue.TrendArrow.NONE, var smoothed: Double? = null) {
|
/**
|
||||||
|
* Simplified [GlucoseValue] for storing in memory and calculations
|
||||||
|
* It may correspond to GlucoseValue value in db
|
||||||
|
* but because of 5 min recalculations and smoothing it may not
|
||||||
|
*/
|
||||||
|
class InMemoryGlucoseValue constructor(
|
||||||
|
var timestamp: Long = 0L,
|
||||||
|
/**
|
||||||
|
* Value in mg/dl
|
||||||
|
*/
|
||||||
|
var value: Double = 0.0,
|
||||||
|
var trendArrow: GlucoseValue.TrendArrow = GlucoseValue.TrendArrow.NONE,
|
||||||
|
/**
|
||||||
|
* Smoothed value. Value is added by smoothing plugin
|
||||||
|
* or null if smoothing was not done
|
||||||
|
*/
|
||||||
|
var smoothed: Double? = null,
|
||||||
|
/**
|
||||||
|
* if true value is not corresponding to received value,
|
||||||
|
* but it was recalculated to fill gap between BGs
|
||||||
|
*/
|
||||||
|
var filledGap: Boolean = false
|
||||||
|
) {
|
||||||
|
|
||||||
constructor(gv: GlucoseValue) : this(gv.timestamp, gv.value, gv.trendArrow)
|
constructor(gv: GlucoseValue) : this(gv.timestamp, gv.value, gv.trendArrow)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide smoothed value if available,
|
||||||
|
* non smoothed value as a fallback
|
||||||
|
*/
|
||||||
val recalculated: Double get() = smoothed ?: value
|
val recalculated: Double get() = smoothed ?: value
|
||||||
}
|
}
|
|
@ -859,7 +859,7 @@ class DataHandlerMobile @Inject constructor(
|
||||||
val finalLastRun = loop.lastRun
|
val finalLastRun = loop.lastRun
|
||||||
if (sp.getBoolean(rh.gs(R.string.key_wear_predictions), true) && finalLastRun?.request?.hasPredictions == true && finalLastRun.constraintsProcessed != null) {
|
if (sp.getBoolean(rh.gs(R.string.key_wear_predictions), true) && finalLastRun?.request?.hasPredictions == true && finalLastRun.constraintsProcessed != null) {
|
||||||
val predArray = finalLastRun.constraintsProcessed!!.predictions
|
val predArray = finalLastRun.constraintsProcessed!!.predictions
|
||||||
.stream().map { bg: GlucoseValue -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) }
|
.stream().map { bg: GlucoseValue -> GlucoseValueDataPoint(bg, profileFunction, rh) }
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
if (predArray.isNotEmpty())
|
if (predArray.isNotEmpty())
|
||||||
for (bg in predArray) if (bg.data.value > 39)
|
for (bg in predArray) if (bg.data.value > 39)
|
||||||
|
|
|
@ -242,8 +242,10 @@ class AutosensDataStoreObject : AutosensDataStore {
|
||||||
} else {
|
} else {
|
||||||
val bgDelta = newer.value - older.value
|
val bgDelta = newer.value - older.value
|
||||||
val timeDiffToNew = newer.timestamp - currentTime
|
val timeDiffToNew = newer.timestamp - currentTime
|
||||||
|
val timeDiffToOlder = currentTime - older.timestamp
|
||||||
|
val filledGap = timeDiffToOlder > T.mins(5).msecs() || timeDiffToNew > T.mins(5).msecs()
|
||||||
val currentBg = newer.value - timeDiffToNew.toDouble() / (newer.timestamp - older.timestamp) * bgDelta
|
val currentBg = newer.value - timeDiffToNew.toDouble() / (newer.timestamp - older.timestamp) * bgDelta
|
||||||
val newBgReading = InMemoryGlucoseValue(currentTime, currentBg.roundToLong().toDouble())
|
val newBgReading = InMemoryGlucoseValue(currentTime, currentBg.roundToLong().toDouble(), filledGap = filledGap)
|
||||||
newBucketedData.add(newBgReading)
|
newBucketedData.add(newBgReading)
|
||||||
//log.debug("BG: " + newBgReading.value + " (" + new Date(newBgReading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")");
|
//log.debug("BG: " + newBgReading.value + " (" + new Date(newBgReading.date).toLocaleString() + ") Prev: " + older.value + " (" + new Date(older.date).toLocaleString() + ") Newer: " + newer.value + " (" + new Date(newer.date).toLocaleString() + ")");
|
||||||
}
|
}
|
||||||
|
@ -279,7 +281,7 @@ class AutosensDataStoreObject : AutosensDataStore {
|
||||||
val gapDelta = bgReadings[i].value - lastBg
|
val gapDelta = bgReadings[i].value - lastBg
|
||||||
//console.error(gapDelta, lastBg, elapsed_minutes);
|
//console.error(gapDelta, lastBg, elapsed_minutes);
|
||||||
val nextBg = lastBg + 5.0 / elapsedMinutes * gapDelta
|
val nextBg = lastBg + 5.0 / elapsedMinutes * gapDelta
|
||||||
val newBgReading = InMemoryGlucoseValue(nextBgTime, nextBg.roundToLong().toDouble())
|
val newBgReading = InMemoryGlucoseValue(nextBgTime, nextBg.roundToLong().toDouble(), filledGap = true)
|
||||||
//console.error("Interpolated", bData[j]);
|
//console.error("Interpolated", bData[j]);
|
||||||
bData.add(newBgReading)
|
bData.add(newBgReading)
|
||||||
aapsLogger.debug(LTag.AUTOSENS) { "Adding. bgTime: ${dateUtil.toISOString(bgTime)} lastBgTime: ${dateUtil.toISOString(lastBgTime)} $newBgReading" }
|
aapsLogger.debug(LTag.AUTOSENS) { "Adding. bgTime: ${dateUtil.toISOString(bgTime)} lastBgTime: ${dateUtil.toISOString(lastBgTime)} $newBgReading" }
|
||||||
|
|
|
@ -49,7 +49,7 @@ class PrepareBgDataWorker(
|
||||||
for (bg in data.overviewData.bgReadingsArray) {
|
for (bg in data.overviewData.bgReadingsArray) {
|
||||||
if (bg.timestamp < fromTime || bg.timestamp > toTime) continue
|
if (bg.timestamp < fromTime || bg.timestamp > toTime) continue
|
||||||
if (bg.value > data.overviewData.maxBgValue) data.overviewData.maxBgValue = bg.value
|
if (bg.value > data.overviewData.maxBgValue) data.overviewData.maxBgValue = bg.value
|
||||||
bgListArray.add(GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh))
|
bgListArray.add(GlucoseValueDataPoint(bg, profileFunction, rh))
|
||||||
}
|
}
|
||||||
bgListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) }
|
bgListArray.sortWith { o1: DataPointWithLabelInterface, o2: DataPointWithLabelInterface -> o1.x.compareTo(o2.x) }
|
||||||
data.overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
|
data.overviewData.bgReadingGraphSeries = PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })
|
||||||
|
|
|
@ -80,7 +80,7 @@ class PreparePredictionsWorker(
|
||||||
|
|
||||||
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
|
val bgListArray: MutableList<DataPointWithLabelInterface> = ArrayList()
|
||||||
val predictions: MutableList<GlucoseValueDataPoint>? = apsResult?.predictions
|
val predictions: MutableList<GlucoseValueDataPoint>? = apsResult?.predictions
|
||||||
?.map { bg -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) }
|
?.map { bg -> GlucoseValueDataPoint(bg, profileFunction, rh) }
|
||||||
?.toMutableList()
|
?.toMutableList()
|
||||||
if (predictions != null) {
|
if (predictions != null) {
|
||||||
predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
|
predictions.sortWith { o1: GlucoseValueDataPoint, o2: GlucoseValueDataPoint -> o1.x.compareTo(o2.x) }
|
||||||
|
|
Loading…
Reference in a new issue