fix(metrics): air-quality chart legend follows plotted data, not selection (#6145)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
James Rich
2026-07-07 20:11:53 -05:00
committed by GitHub
parent 93b24572c8
commit 9cfe72ec3c
2 changed files with 27 additions and 1 deletions

View File

@@ -97,6 +97,14 @@ internal enum class AirQuality(val labelRes: StringResource, val unit: String, v
}
}
/**
* The subset of [candidates] with at least one reading in [telemetries]. The chart only draws series that have data, so
* the legend must use this rather than the raw selection — otherwise a default-selected series (PM2.5) shows a legend
* entry on nodes that never report it (issue #5873). Internal so it can be unit-tested.
*/
internal fun metricsWithData(candidates: List<AirQuality>, telemetries: List<Telemetry>): List<AirQuality> =
candidates.filter { metric -> telemetries.any { metric.getValue(it) != null } }
private val LEGEND_DATA =
AirQuality.entries.map { metric -> LegendData(nameRes = metric.labelRes, color = metric.color, isLine = true) }
@@ -193,10 +201,11 @@ private fun AirQualityChart(
modifier: Modifier = Modifier,
) {
val activeMetrics = AirQuality.entries.filter { it in selectedMetrics }
val drawnMetrics = metricsWithData(activeMetrics, telemetries)
val metricLabels = activeMetrics.associateWith { stringResource(it.labelRes) }
MetricChartScaffold(
isEmpty = telemetries.isEmpty() || activeMetrics.isEmpty(),
legendData = LEGEND_DATA.filter { ld -> activeMetrics.any { it.labelRes == ld.nameRes } },
legendData = LEGEND_DATA.filter { ld -> drawnMetrics.any { it.labelRes == ld.nameRes } },
modifier = modifier,
) { modelProducer, chartModifier ->
val marker =

View File

@@ -69,4 +69,21 @@ class AirQualityMetricsTest {
assertNull(it.getValue(t), "${it.name} should be null without air_quality_metrics")
}
}
@Test
fun `metricsWithData drops selected series that have no reading so the legend is not ever-present`() {
// Issue 5873, CO2-only node: PM2.5 is default-selected but never reported, so it must not survive into the
// legend.
val co2Only = listOf(telemetry(AirQualityMetrics(co2 = 450)))
assertEquals(listOf(AirQuality.CO2), metricsWithData(listOf(AirQuality.PM2_5, AirQuality.CO2), co2Only))
}
@Test
fun `metricsWithData keeps a series once it has any reading in the frame`() {
val mixed = listOf(telemetry(AirQualityMetrics(co2 = 450)), telemetry(AirQualityMetrics(pm25_standard = 8)))
assertEquals(
listOf(AirQuality.PM2_5, AirQuality.CO2),
metricsWithData(listOf(AirQuality.PM2_5, AirQuality.CO2), mixed),
)
}
}