From 811858c72ab3143276bd1fcf0d21b3eed4d027cf Mon Sep 17 00:00:00 2001 From: grill-glitch Date: Sun, 15 Mar 2026 20:14:56 +0800 Subject: [PATCH 1/2] fix:App Usage Chart Index Mismatch After Scrolling --- .../dev/pranav/reef/util/TimeColumnChart.kt | 88 ++++++++++--------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt b/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt index c312e5c..daf1136 100644 --- a/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt +++ b/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt @@ -2,7 +2,8 @@ * Copyright (c) 2025 Nishant Mishra * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. + * If not, see . */ package org.nsh07.pomodoro.ui.statsScreen @@ -34,6 +35,7 @@ import com.patrykandpatrick.vico.compose.common.MarkerCornerBasedShape import com.patrykandpatrick.vico.compose.common.ProvideVicoTheme import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent import com.patrykandpatrick.vico.compose.m3.common.rememberM3VicoTheme +import kotlin.math.roundToInt @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -51,46 +53,43 @@ internal fun TimeColumnChart( ) { if (dataValues.isEmpty()) return - val radius = with(LocalDensity.current) { - (thickness / 2).toPx() - } - var chartSize by remember { mutableStateOf(IntSize.Zero) } val density = LocalDensity.current - val primaryColor = MaterialTheme.colorScheme.primary + // Extract scrollState to a variable so it can be accessed in the pointerInput block + val vicoScrollState = rememberVicoScrollState(initialScroll = Scroll.Absolute.End) + ProvideVicoTheme(rememberM3VicoTheme()) { CartesianChartHost( - chart = - rememberCartesianChart( - rememberColumnCartesianLayer( - ColumnCartesianLayer.ColumnProvider.series( - dataValues.indices.map { _ -> - rememberLineComponent( - fill = Fill(primaryColor), - thickness = thickness, - shape = MarkerCornerBasedShape(RoundedCornerShape(16.dp)) - ) - } - ), - columnCollectionSpacing = columnCollectionSpacing - ), - startAxis = VerticalAxis.rememberStart( - valueFormatter = yValueFormatter + chart = rememberCartesianChart( + rememberColumnCartesianLayer( + ColumnCartesianLayer.ColumnProvider.series( + dataValues.indices.map { _ -> + rememberLineComponent( + fill = Fill(primaryColor), + thickness = thickness, + shape = MarkerCornerBasedShape(RoundedCornerShape(16.dp)) + ) + } ), - bottomAxis = HorizontalAxis.rememberBottom( - guideline = rememberLineComponent(Fill.Transparent), - valueFormatter = xValueFormatter - ) + columnCollectionSpacing = columnCollectionSpacing ), + startAxis = VerticalAxis.rememberStart( + valueFormatter = yValueFormatter + ), + bottomAxis = HorizontalAxis.rememberBottom( + guideline = rememberLineComponent(Fill.Transparent), + valueFormatter = xValueFormatter + ) + ), modelProducer = modelProducer, zoomState = rememberVicoZoomState( zoomEnabled = false, initialZoom = Zoom.fixed(), minZoom = Zoom.min(Zoom.Content, Zoom.fixed()) ), - scrollState = rememberVicoScrollState(initialScroll = Scroll.Absolute.End), + scrollState = vicoScrollState, animationSpec = animationSpec, modifier = modifier .onSizeChanged { chartSize = it } @@ -104,6 +103,7 @@ internal fun TimeColumnChart( val endPadding = with(density) { 16.dp.toPx() } val bottomAxisHeight = with(density) { 32.dp.toPx() } val topPadding = with(density) { 8.dp.toPx() } + val availableWidth = chartWidth - startAxisWidth - endPadding val availableHeight = chartHeight - bottomAxisHeight - topPadding @@ -111,21 +111,29 @@ internal fun TimeColumnChart( val spacing = with(density) { columnCollectionSpacing.toPx() } val totalColumnWidth = columnWidth + spacing - val clickX = offset.x - startAxisWidth + // Get current scroll offset to align click coordinate with chart content + val scrollOffset = vicoScrollState.value + + // Calculate click X relative to content by adding the scroll offset + val clickXWithScroll = (offset.x - startAxisWidth) + scrollOffset val clickY = offset.y - topPadding - if (clickX in 0.0f..availableWidth && clickY >= 0 && clickY <= availableHeight) { - val columnIndex = (clickX / totalColumnWidth).toInt() - if (columnIndex >= 0 && columnIndex < dataValues.size) { - val maxValue = dataValues.maxOrNull() ?: 1f - val barHeightRatio = - if (maxValue > 0) dataValues[columnIndex] / maxValue else 0f - val barHeight = availableHeight * barHeightRatio - val barTop = availableHeight - barHeight + // Validate if tap is within the chart drawing area + if (offset.x in startAxisWidth..(chartWidth - endPadding) && + clickY in 0f..availableHeight) { + + // Use rounding to find the nearest column index + val columnIndex = (clickXWithScroll / totalColumnWidth).roundToInt() + .coerceIn(0, dataValues.size - 1) - if (clickY in barTop..availableHeight) { - onColumnClick(columnIndex) - } + val maxValue = dataValues.maxOrNull() ?: 1f + val barHeightRatio = if (maxValue > 0) dataValues[columnIndex] / maxValue else 0f + val barHeight = availableHeight * barHeightRatio + val barTop = availableHeight - barHeight + + // Trigger callback if the tap falls within the column's vertical range + if (clickY in barTop..availableHeight) { + onColumnClick(columnIndex) } } } @@ -186,4 +194,4 @@ internal fun TimeLineChart( modifier = modifier ) } -} +} \ No newline at end of file From 62c06746c4941e55372afeafe3d4f83ac984a27f Mon Sep 17 00:00:00 2001 From: grill-glitch Date: Sun, 15 Mar 2026 20:34:40 +0800 Subject: [PATCH 2/2] fix the inconsistency when in both directions --- .../dev/pranav/reef/util/TimeColumnChart.kt | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt b/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt index daf1136..45c4b2b 100644 --- a/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt +++ b/Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt @@ -57,7 +57,7 @@ internal fun TimeColumnChart( val density = LocalDensity.current val primaryColor = MaterialTheme.colorScheme.primary - // Extract scrollState to a variable so it can be accessed in the pointerInput block + // Use a dedicated scrollState variable to track horizontal movement val vicoScrollState = rememberVicoScrollState(initialScroll = Scroll.Absolute.End) ProvideVicoTheme(rememberM3VicoTheme()) { @@ -104,26 +104,24 @@ internal fun TimeColumnChart( val bottomAxisHeight = with(density) { 32.dp.toPx() } val topPadding = with(density) { 8.dp.toPx() } - val availableWidth = chartWidth - startAxisWidth - endPadding val availableHeight = chartHeight - bottomAxisHeight - topPadding val columnWidth = with(density) { thickness.toPx() } val spacing = with(density) { columnCollectionSpacing.toPx() } val totalColumnWidth = columnWidth + spacing - // Get current scroll offset to align click coordinate with chart content + // Correcting coordinate: Add current scroll offset to the tap position relative to the chart start val scrollOffset = vicoScrollState.value - - // Calculate click X relative to content by adding the scroll offset - val clickXWithScroll = (offset.x - startAxisWidth) + scrollOffset + val relativeTapX = offset.x - startAxisWidth + val absoluteX = relativeTapX + scrollOffset val clickY = offset.y - topPadding - // Validate if tap is within the chart drawing area + // Ensure tap is within the horizontal bounds of the chart content if (offset.x in startAxisWidth..(chartWidth - endPadding) && clickY in 0f..availableHeight) { - // Use rounding to find the nearest column index - val columnIndex = (clickXWithScroll / totalColumnWidth).roundToInt() + // Using integer division for more predictable column mapping + val columnIndex = (absoluteX / totalColumnWidth).toInt() .coerceIn(0, dataValues.size - 1) val maxValue = dataValues.maxOrNull() ?: 1f @@ -131,7 +129,7 @@ internal fun TimeColumnChart( val barHeight = availableHeight * barHeightRatio val barTop = availableHeight - barHeight - // Trigger callback if the tap falls within the column's vertical range + // Trigger callback if the tap falls within the column's vertical area if (clickY in barTop..availableHeight) { onColumnClick(columnIndex) }