Skip to content

Commit 9da8b79

Browse files
authored
Fix index mismatch in app usage chart during scrolling (#63)
1 parent 40a6daa commit 9da8b79

1 file changed

Lines changed: 47 additions & 41 deletions

File tree

Reef/src/main/java/dev/pranav/reef/util/TimeColumnChart.kt

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* Copyright (c) 2025 Nishant Mishra
33
*
44
* You should have received a copy of the GNU General Public License
5-
* along with this program. If not, see <https://www.gnu.org/licenses/>.
5+
* along with this program.
6+
* If not, see <https://www.gnu.org/licenses/>.
67
*/
78

89
package org.nsh07.pomodoro.ui.statsScreen
@@ -34,6 +35,7 @@ import com.patrykandpatrick.vico.compose.common.MarkerCornerBasedShape
3435
import com.patrykandpatrick.vico.compose.common.ProvideVicoTheme
3536
import com.patrykandpatrick.vico.compose.common.component.rememberLineComponent
3637
import com.patrykandpatrick.vico.compose.m3.common.rememberM3VicoTheme
38+
import kotlin.math.roundToInt
3739

3840
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
3941
@Composable
@@ -51,46 +53,43 @@ internal fun TimeColumnChart(
5153
) {
5254
if (dataValues.isEmpty()) return
5355

54-
val radius = with(LocalDensity.current) {
55-
(thickness / 2).toPx()
56-
}
57-
5856
var chartSize by remember { mutableStateOf(IntSize.Zero) }
5957
val density = LocalDensity.current
60-
6158
val primaryColor = MaterialTheme.colorScheme.primary
6259

60+
// Use a dedicated scrollState variable to track horizontal movement
61+
val vicoScrollState = rememberVicoScrollState(initialScroll = Scroll.Absolute.End)
62+
6363
ProvideVicoTheme(rememberM3VicoTheme()) {
6464
CartesianChartHost(
65-
chart =
66-
rememberCartesianChart(
67-
rememberColumnCartesianLayer(
68-
ColumnCartesianLayer.ColumnProvider.series(
69-
dataValues.indices.map { _ ->
70-
rememberLineComponent(
71-
fill = Fill(primaryColor),
72-
thickness = thickness,
73-
shape = MarkerCornerBasedShape(RoundedCornerShape(16.dp))
74-
)
75-
}
76-
),
77-
columnCollectionSpacing = columnCollectionSpacing
78-
),
79-
startAxis = VerticalAxis.rememberStart(
80-
valueFormatter = yValueFormatter
65+
chart = rememberCartesianChart(
66+
rememberColumnCartesianLayer(
67+
ColumnCartesianLayer.ColumnProvider.series(
68+
dataValues.indices.map { _ ->
69+
rememberLineComponent(
70+
fill = Fill(primaryColor),
71+
thickness = thickness,
72+
shape = MarkerCornerBasedShape(RoundedCornerShape(16.dp))
73+
)
74+
}
8175
),
82-
bottomAxis = HorizontalAxis.rememberBottom(
83-
guideline = rememberLineComponent(Fill.Transparent),
84-
valueFormatter = xValueFormatter
85-
)
76+
columnCollectionSpacing = columnCollectionSpacing
8677
),
78+
startAxis = VerticalAxis.rememberStart(
79+
valueFormatter = yValueFormatter
80+
),
81+
bottomAxis = HorizontalAxis.rememberBottom(
82+
guideline = rememberLineComponent(Fill.Transparent),
83+
valueFormatter = xValueFormatter
84+
)
85+
),
8786
modelProducer = modelProducer,
8887
zoomState = rememberVicoZoomState(
8988
zoomEnabled = false,
9089
initialZoom = Zoom.fixed(),
9190
minZoom = Zoom.min(Zoom.Content, Zoom.fixed())
9291
),
93-
scrollState = rememberVicoScrollState(initialScroll = Scroll.Absolute.End),
92+
scrollState = vicoScrollState,
9493
animationSpec = animationSpec,
9594
modifier = modifier
9695
.onSizeChanged { chartSize = it }
@@ -104,28 +103,35 @@ internal fun TimeColumnChart(
104103
val endPadding = with(density) { 16.dp.toPx() }
105104
val bottomAxisHeight = with(density) { 32.dp.toPx() }
106105
val topPadding = with(density) { 8.dp.toPx() }
107-
val availableWidth = chartWidth - startAxisWidth - endPadding
106+
108107
val availableHeight = chartHeight - bottomAxisHeight - topPadding
109108

110109
val columnWidth = with(density) { thickness.toPx() }
111110
val spacing = with(density) { columnCollectionSpacing.toPx() }
112111
val totalColumnWidth = columnWidth + spacing
113112

114-
val clickX = offset.x - startAxisWidth
113+
// Correcting coordinate: Add current scroll offset to the tap position relative to the chart start
114+
val scrollOffset = vicoScrollState.value
115+
val relativeTapX = offset.x - startAxisWidth
116+
val absoluteX = relativeTapX + scrollOffset
115117
val clickY = offset.y - topPadding
116118

117-
if (clickX in 0.0f..availableWidth && clickY >= 0 && clickY <= availableHeight) {
118-
val columnIndex = (clickX / totalColumnWidth).toInt()
119-
if (columnIndex >= 0 && columnIndex < dataValues.size) {
120-
val maxValue = dataValues.maxOrNull() ?: 1f
121-
val barHeightRatio =
122-
if (maxValue > 0) dataValues[columnIndex] / maxValue else 0f
123-
val barHeight = availableHeight * barHeightRatio
124-
val barTop = availableHeight - barHeight
119+
// Ensure tap is within the horizontal bounds of the chart content
120+
if (offset.x in startAxisWidth..(chartWidth - endPadding) &&
121+
clickY in 0f..availableHeight) {
122+
123+
// Using integer division for more predictable column mapping
124+
val columnIndex = (absoluteX / totalColumnWidth).toInt()
125+
.coerceIn(0, dataValues.size - 1)
125126

126-
if (clickY in barTop..availableHeight) {
127-
onColumnClick(columnIndex)
128-
}
127+
val maxValue = dataValues.maxOrNull() ?: 1f
128+
val barHeightRatio = if (maxValue > 0) dataValues[columnIndex] / maxValue else 0f
129+
val barHeight = availableHeight * barHeightRatio
130+
val barTop = availableHeight - barHeight
131+
132+
// Trigger callback if the tap falls within the column's vertical area
133+
if (clickY in barTop..availableHeight) {
134+
onColumnClick(columnIndex)
129135
}
130136
}
131137
}
@@ -186,4 +192,4 @@ internal fun TimeLineChart(
186192
modifier = modifier
187193
)
188194
}
189-
}
195+
}

0 commit comments

Comments
 (0)