feat: add X/Y axis labels and grid lines to NIRS charts
Y-axis shows data range with 5 tick labels, X-axis shows elapsed time in seconds, horizontal grid lines for readability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+66
-12
@@ -355,6 +355,12 @@ private fun MetricBox(title: String, value: Double, unit: String, color: Color,
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LineChartCard(label: String, data: List<Double>, color: Color, unit: String) {
|
private fun LineChartCard(label: String, data: List<Double>, color: Color, unit: String) {
|
||||||
|
val axisColor = MlSecondaryText.copy(alpha = 0.4f)
|
||||||
|
val gridColor = MlSecondaryText.copy(alpha = 0.1f)
|
||||||
|
val sampleRate = 4f // Hz
|
||||||
|
val yLabelWidth = 36.dp
|
||||||
|
val xLabelHeight = 18.dp
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = MlCardBackground),
|
colors = CardDefaults.cardColors(containerColor = MlCardBackground),
|
||||||
@@ -380,21 +386,69 @@ private fun LineChartCard(label: String, data: List<Double>, color: Color, unit:
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) { Text("No data", fontSize = 11.sp, color = MlSecondaryText) }
|
) { Text("No data", fontSize = 11.sp, color = MlSecondaryText) }
|
||||||
} else {
|
} else {
|
||||||
Canvas(modifier = Modifier.fillMaxWidth().height(140.dp)) {
|
val minVal = data.min()
|
||||||
if (data.size < 2) return@Canvas
|
val maxVal = data.max()
|
||||||
val minVal = data.min()
|
val range = if (maxVal - minVal > 0.001) maxVal - minVal else 1.0
|
||||||
val maxVal = data.max()
|
val yMin = minVal - range * 0.05
|
||||||
val range = if (maxVal - minVal > 0.001) maxVal - minVal else 1.0
|
val yMax = maxVal + range * 0.05
|
||||||
val stepX = size.width / (data.size - 1).toFloat()
|
val yRange = yMax - yMin
|
||||||
|
|
||||||
val path = Path()
|
// Y-axis tick values (5 ticks)
|
||||||
data.forEachIndexed { index, value ->
|
val yTicks = (0..4).map { yMin + yRange * it / 4.0 }
|
||||||
val x = index * stepX
|
|
||||||
val y = size.height - ((value - minVal) / range * size.height).toFloat()
|
Row(modifier = Modifier.fillMaxWidth().height(140.dp + xLabelHeight)) {
|
||||||
if (index == 0) path.moveTo(x, y) else path.lineTo(x, y)
|
// Y-axis labels
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.width(yLabelWidth).height(140.dp),
|
||||||
|
verticalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
yTicks.reversed().forEach { tick ->
|
||||||
|
Text(
|
||||||
|
String.format("%.1f", tick),
|
||||||
|
fontSize = 8.sp,
|
||||||
|
color = MlSecondaryText,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
// Chart area
|
||||||
|
Canvas(modifier = Modifier.fillMaxWidth().height(140.dp)) {
|
||||||
|
// Grid lines
|
||||||
|
for (i in 0..4) {
|
||||||
|
val y = size.height * i / 4f
|
||||||
|
drawLine(gridColor, Offset(0f, y), Offset(size.width, y), strokeWidth = 1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data line
|
||||||
|
if (data.size >= 2) {
|
||||||
|
val stepX = size.width / (data.size - 1).toFloat()
|
||||||
|
val path = Path()
|
||||||
|
data.forEachIndexed { index, value ->
|
||||||
|
val x = index * stepX
|
||||||
|
val y = size.height - ((value - yMin) / yRange * size.height).toFloat()
|
||||||
|
if (index == 0) path.moveTo(x, y) else path.lineTo(x, y)
|
||||||
|
}
|
||||||
|
drawPath(path, color = color, style = Stroke(width = 3f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-axis labels
|
||||||
|
val totalSeconds = data.size / sampleRate
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(xLabelHeight),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(String.format("%.0fs", 0f), fontSize = 8.sp, color = MlSecondaryText)
|
||||||
|
Text(String.format("%.0fs", totalSeconds / 2), fontSize = 8.sp, color = MlSecondaryText)
|
||||||
|
Text(String.format("%.0fs", totalSeconds), fontSize = 8.sp, color = MlSecondaryText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
drawPath(path, color = color, style = Stroke(width = 3f))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unit label
|
||||||
|
Text(unit, fontSize = 8.sp, color = MlSecondaryText, modifier = Modifier.padding(start = yLabelWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user