Fix/2100 graph labels (#2182)

Co-authored-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
DaneEvans
2025-06-21 01:14:06 +10:00
committed by GitHub
parent 03ae071548
commit 1be3979c84
4 changed files with 142 additions and 35 deletions

View File

@@ -50,6 +50,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
@@ -58,12 +59,14 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.R
import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.TelemetryProtos.Telemetry
import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.model.TimeFrame
import com.geeksville.mesh.ui.common.components.BatteryInfo
import com.geeksville.mesh.ui.common.components.OptionLabel
import com.geeksville.mesh.ui.common.components.SlidingSelector
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.common.theme.Orange
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
import com.geeksville.mesh.ui.metrics.CommonCharts.MAX_PERCENT_VALUE
@@ -71,6 +74,7 @@ import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
import com.geeksville.mesh.util.GraphUtil
import com.geeksville.mesh.util.GraphUtil.createPath
import com.geeksville.mesh.util.GraphUtil.plotPoint
import androidx.compose.ui.tooling.preview.PreviewLightDark
private enum class Device(val color: Color) {
BATTERY(Color.Green),
@@ -139,6 +143,7 @@ private fun DeviceMetricsChart(
selectedTime: TimeFrame,
promptInfoDialog: () -> Unit
) {
val graphColor = MaterialTheme.colorScheme.onSurface
ChartHeader(amount = telemetries.size)
if (telemetries.isEmpty()) return
@@ -151,21 +156,31 @@ private fun DeviceMetricsChart(
}
val timeDiff = newest.time - oldest.time
TimeLabels(
oldest = oldest.time,
newest = newest.time
)
Spacer(modifier = Modifier.height(16.dp))
val graphColor = MaterialTheme.colorScheme.onSurface
val scrollState = rememberScrollState()
val screenWidth = LocalWindowInfo.current.containerSize.width
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = timeDiff.toLong()))
}
// Calculate visible time range based on scroll position and chart width
val visibleTimeRange = run {
val totalWidthPx = with(LocalDensity.current) { dp.toPx() }
val scrollPx = scrollState.value.toFloat()
val visibleWidthPx = with(LocalDensity.current) { screenWidth.toDp().toPx() }
val leftRatio = (scrollPx / totalWidthPx).coerceIn(0f, 1f)
val rightRatio = ((scrollPx + visibleWidthPx) / totalWidthPx).coerceIn(0f, 1f)
val visibleOldest = oldest.time + (timeDiff * leftRatio).toInt()
val visibleNewest = oldest.time + (timeDiff * rightRatio).toInt()
visibleOldest to visibleNewest
}
TimeLabels(
oldest = visibleTimeRange.first,
newest = visibleTimeRange.second
)
Spacer(modifier = Modifier.height(16.dp))
Row {
Box(
contentAlignment = Alignment.TopStart,
@@ -265,6 +280,34 @@ private fun DeviceMetricsChart(
Spacer(modifier = Modifier.height(16.dp))
}
@Suppress("detekt:MagicNumber") // fake data
@PreviewLightDark
@Composable
private fun DeviceMetricsChartPreview() {
val now = (System.currentTimeMillis() / 1000).toInt()
val telemetries = List(20) { i ->
Telemetry.newBuilder()
.setTime(now - (19 - i) * 60 * 60) // 1-hour intervals, oldest first
.setDeviceMetrics(
TelemetryProtos.DeviceMetrics.newBuilder()
.setBatteryLevel(80 - i)
.setVoltage(3.7f - i * 0.02f)
.setChannelUtilization(10f + i * 2)
.setAirUtilTx(5f + i)
.setUptimeSeconds(3600 + i * 300)
)
.build()
}
AppTheme {
DeviceMetricsChart(
modifier = Modifier.height(400.dp),
telemetries = telemetries,
selectedTime = TimeFrame.TWENTY_FOUR_HOURS,
promptInfoDialog = {}
)
}
}
@Composable
private fun DeviceMetricsCard(telemetry: Telemetry) {
val deviceMetrics = telemetry.deviceMetrics
@@ -321,3 +364,24 @@ private fun DeviceMetricsCard(telemetry: Telemetry) {
}
}
}
@Suppress("detekt:MagicNumber") // fake data
@PreviewLightDark
@Composable
private fun DeviceMetricsCardPreview() {
val now = (System.currentTimeMillis() / 1000).toInt()
val telemetry = Telemetry.newBuilder()
.setTime(now)
.setDeviceMetrics(
TelemetryProtos.DeviceMetrics.newBuilder()
.setBatteryLevel(75)
.setVoltage(3.65f)
.setChannelUtilization(22.5f)
.setAirUtilTx(12.0f)
.setUptimeSeconds(7200)
)
.build()
AppTheme {
DeviceMetricsCard(telemetry = telemetry)
}
}

View File

@@ -49,6 +49,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -165,7 +166,6 @@ fun EnvironmentMetricsScreen(
}
}
/* TODO need to take the time to understand this. */
@SuppressLint("ConfigurationScreenWidthHeight")
@Suppress("LongMethod")
@Composable
@@ -182,9 +182,30 @@ private fun EnvironmentMetricsChart(
}
val (oldest, newest) = graphData.times
val timeDiff = newest - oldest
val scrollState = rememberScrollState()
val screenWidth = LocalConfiguration.current.screenWidthDp
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = timeDiff.toLong()))
}
// Calculate visible time range based on scroll position and chart width
val visibleTimeRange = run {
val density = LocalDensity.current
val totalWidthPx = with(density) { dp.toPx() }
val scrollPx = scrollState.value.toFloat()
val visibleWidthPx = with(density) { screenWidth.dp.toPx() }
val leftRatio = (scrollPx / totalWidthPx).coerceIn(0f, 1f)
val rightRatio = ((scrollPx + visibleWidthPx) / totalWidthPx).coerceIn(0f, 1f)
val visibleOldest = oldest + (timeDiff * leftRatio).toInt()
val visibleNewest = oldest + (timeDiff * rightRatio).toInt()
visibleOldest to visibleNewest
}
TimeLabels(
oldest = oldest,
newest = newest
oldest = visibleTimeRange.first,
newest = visibleTimeRange.second
)
Spacer(modifier = Modifier.height(16.dp))
@@ -196,12 +217,6 @@ private fun EnvironmentMetricsChart(
var min = rightMin
var diff = rightMax - rightMin
val scrollState = rememberScrollState()
val screenWidth = LocalConfiguration.current.screenWidthDp
val timeDiff = newest - oldest
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = timeDiff.toLong()))
}
val shouldPlot = graphData.shouldPlot
Row {

View File

@@ -51,6 +51,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
@@ -157,9 +158,28 @@ private fun PowerMetricsChart(
}
val timeDiff = newest.time - oldest.time
val scrollState = rememberScrollState()
val screenWidth = LocalWindowInfo.current.containerSize.width
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = (newest.time - oldest.time).toLong()))
}
// Calculate visible time range based on scroll position and chart width
val visibleTimeRange = run {
val density = LocalDensity.current
val totalWidthPx = with(density) { dp.toPx() }
val scrollPx = scrollState.value.toFloat()
val visibleWidthPx = with(density) { screenWidth.toDp().toPx() }
val leftRatio = (scrollPx / totalWidthPx).coerceIn(0f, 1f)
val rightRatio = ((scrollPx + visibleWidthPx) / totalWidthPx).coerceIn(0f, 1f)
val visibleOldest = oldest.time + (timeDiff * leftRatio).toInt()
val visibleNewest = oldest.time + (timeDiff * rightRatio).toInt()
visibleOldest to visibleNewest
}
TimeLabels(
oldest = oldest.time,
newest = newest.time
oldest = visibleTimeRange.first,
newest = visibleTimeRange.second
)
Spacer(modifier = Modifier.height(16.dp))
@@ -168,12 +188,6 @@ private fun PowerMetricsChart(
val currentDiff = Power.CURRENT.difference()
val voltageDiff = Power.VOLTAGE.difference()
val scrollState = rememberScrollState()
val screenWidth = LocalWindowInfo.current.containerSize.width
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = (newest.time - oldest.time).toLong()))
}
Row {
YAxisLabels(
modifier = modifier.weight(weight = .1f),

View File

@@ -49,6 +49,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -148,9 +149,29 @@ private fun SignalMetricsChart(
}
val timeDiff = newest.rxTime - oldest.rxTime
val scrollState = rememberScrollState()
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = (newest.rxTime - oldest.rxTime).toLong()))
}
// Calculate visible time range based on scroll position and chart width
val visibleTimeRange = run {
val density = LocalDensity.current
val totalWidthPx = with(density) { dp.toPx() }
val scrollPx = scrollState.value.toFloat()
val visibleWidthPx = with(density) { screenWidth.dp.toPx() }
val leftRatio = (scrollPx / totalWidthPx).coerceIn(0f, 1f)
val rightRatio = ((scrollPx + visibleWidthPx) / totalWidthPx).coerceIn(0f, 1f)
val visibleOldest = oldest.rxTime + (timeDiff * leftRatio).toInt()
val visibleNewest = oldest.rxTime + (timeDiff * rightRatio).toInt()
visibleOldest to visibleNewest
}
TimeLabels(
oldest = oldest.rxTime,
newest = newest.rxTime
oldest = visibleTimeRange.first,
newest = visibleTimeRange.second
)
Spacer(modifier = Modifier.height(16.dp))
@@ -159,13 +180,6 @@ private fun SignalMetricsChart(
val snrDiff = Metric.SNR.difference()
val rssiDiff = Metric.RSSI.difference()
val scrollState = rememberScrollState()
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp
val dp by remember(key1 = selectedTime) {
mutableStateOf(selectedTime.dp(screenWidth, time = (newest.rxTime - oldest.rxTime).toLong()))
}
Row {
YAxisLabels(
modifier = modifier.weight(weight = .1f),