feat(graphs): add markers to graphs that appear on click

#118
This commit is contained in:
Nishant Mishra
2025-11-15 18:26:42 +05:30
parent 56ef71aafa
commit b7d44a279c
2 changed files with 98 additions and 6 deletions

View File

@@ -1,20 +1,34 @@
/* /*
* Copyright (c) 2025 Nishant Mishra * Copyright (c) 2025 Nishant Mishra
* *
* You should have received a copy of the GNU General Public License * This file is part of Tomato - a minimalist pomodoro timer for Android.
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tomato.
* If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.nsh07.pomodoro.ui.statsScreen package org.nsh07.pomodoro.ui.statsScreen
import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -37,10 +51,16 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer import com.patrykandpatrick.vico.core.cartesian.layer.ColumnCartesianLayer
import com.patrykandpatrick.vico.core.cartesian.marker.ColumnCartesianLayerMarkerTarget
import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker
import com.patrykandpatrick.vico.core.common.Fill import com.patrykandpatrick.vico.core.common.Fill
import com.patrykandpatrick.vico.core.common.Insets
import com.patrykandpatrick.vico.core.common.component.ShapeComponent
import com.patrykandpatrick.vico.core.common.component.TextComponent
import com.patrykandpatrick.vico.core.common.shape.CorneredShape import com.patrykandpatrick.vico.core.common.shape.CorneredShape
import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.utils.millisecondsToHours import org.nsh07.pomodoro.utils.millisecondsToHours
import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
import org.nsh07.pomodoro.utils.millisecondsToMinutes import org.nsh07.pomodoro.utils.millisecondsToMinutes
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -58,6 +78,18 @@ fun TimeColumnChart(
millisecondsToMinutes(value.toLong()) millisecondsToMinutes(value.toLong())
} }
}, },
markerValueFormatter: DefaultCartesianMarker.ValueFormatter = DefaultCartesianMarker.ValueFormatter { _, targets ->
val first = targets.firstOrNull()
val value = if (first is ColumnCartesianLayerMarkerTarget) {
first.columns.sumOf { it.entry.y.toLong() }
} else 0L
if (value >= 60 * 60 * 1000) {
millisecondsToHoursMinutes(value)
} else {
millisecondsToMinutes(value)
}
},
animationSpec: AnimationSpec<Float>? = motionScheme.slowEffectsSpec() animationSpec: AnimationSpec<Float>? = motionScheme.slowEffectsSpec()
) { ) {
ProvideVicoTheme(rememberM3VicoTheme()) { ProvideVicoTheme(rememberM3VicoTheme()) {
@@ -88,6 +120,20 @@ fun TimeColumnChart(
guideline = rememberLineComponent(Fill.Transparent), guideline = rememberLineComponent(Fill.Transparent),
valueFormatter = xValueFormatter valueFormatter = xValueFormatter
), ),
marker = DefaultCartesianMarker(
TextComponent(
color = colorScheme.surface.toArgb(),
background = ShapeComponent(
fill = fill(colorScheme.onSurface),
shape = CorneredShape.Pill
),
textSizeSp = typography.labelSmall.fontSize.value,
lineHeightSp = typography.labelSmall.fontSize.value,
padding = Insets(verticalDp = 4f, horizontalDp = 8f),
margins = Insets(bottomDp = 2f)
),
valueFormatter = markerValueFormatter
),
fadingEdges = FadingEdges() fadingEdges = FadingEdges()
), ),
modelProducer = modelProducer, modelProducer = modelProducer,
@@ -97,7 +143,7 @@ fun TimeColumnChart(
minZoom = Zoom.min(Zoom.Content, Zoom.fixed()) minZoom = Zoom.min(Zoom.Content, Zoom.fixed())
), ),
animationSpec = animationSpec, animationSpec = animationSpec,
modifier = modifier, modifier = modifier.height(224.dp),
) )
} }
} }

View File

@@ -1,15 +1,28 @@
/* /*
* Copyright (c) 2025 Nishant Mishra * Copyright (c) 2025 Nishant Mishra
* *
* You should have received a copy of the GNU General Public License * This file is part of Tomato - a minimalist pomodoro timer for Android.
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tomato.
* If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.nsh07.pomodoro.ui.statsScreen package org.nsh07.pomodoro.ui.statsScreen
import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.layout.height
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.motionScheme import androidx.compose.material3.MaterialTheme.motionScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -41,10 +54,17 @@ import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.LineFill.Companion.single import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.LineFill.Companion.single
import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker
import com.patrykandpatrick.vico.core.cartesian.marker.LineCartesianLayerMarkerTarget
import com.patrykandpatrick.vico.core.common.Fill import com.patrykandpatrick.vico.core.common.Fill
import com.patrykandpatrick.vico.core.common.Insets
import com.patrykandpatrick.vico.core.common.component.ShapeComponent
import com.patrykandpatrick.vico.core.common.component.TextComponent
import com.patrykandpatrick.vico.core.common.shader.ShaderProvider import com.patrykandpatrick.vico.core.common.shader.ShaderProvider
import com.patrykandpatrick.vico.core.common.shape.CorneredShape
import org.nsh07.pomodoro.ui.theme.TomatoTheme import org.nsh07.pomodoro.ui.theme.TomatoTheme
import org.nsh07.pomodoro.utils.millisecondsToHours import org.nsh07.pomodoro.utils.millisecondsToHours
import org.nsh07.pomodoro.utils.millisecondsToHoursMinutes
import org.nsh07.pomodoro.utils.millisecondsToMinutes import org.nsh07.pomodoro.utils.millisecondsToMinutes
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -62,6 +82,18 @@ fun TimeLineChart(
millisecondsToMinutes(value.toLong()) millisecondsToMinutes(value.toLong())
} }
}, },
markerValueFormatter: DefaultCartesianMarker.ValueFormatter = DefaultCartesianMarker.ValueFormatter { _, targets ->
val first = targets.firstOrNull()
val value = if (first is LineCartesianLayerMarkerTarget) {
first.points.sumOf { it.entry.y.toLong() }
} else 0L
if (value >= 60 * 60 * 1000) {
millisecondsToHoursMinutes(value)
} else {
millisecondsToMinutes(value)
}
},
animationSpec: AnimationSpec<Float>? = motionScheme.slowEffectsSpec() animationSpec: AnimationSpec<Float>? = motionScheme.slowEffectsSpec()
) { ) {
ProvideVicoTheme(rememberM3VicoTheme()) { ProvideVicoTheme(rememberM3VicoTheme()) {
@@ -102,6 +134,20 @@ fun TimeLineChart(
guideline = rememberLineComponent(Fill.Transparent), guideline = rememberLineComponent(Fill.Transparent),
valueFormatter = xValueFormatter valueFormatter = xValueFormatter
), ),
marker = DefaultCartesianMarker(
TextComponent(
color = colorScheme.surface.toArgb(),
background = ShapeComponent(
fill = fill(colorScheme.onSurface),
shape = CorneredShape.Pill
),
textSizeSp = typography.labelSmall.fontSize.value,
lineHeightSp = typography.labelSmall.fontSize.value,
padding = Insets(verticalDp = 4f, horizontalDp = 8f),
margins = Insets(bottomDp = 2f)
),
valueFormatter = markerValueFormatter
),
fadingEdges = FadingEdges() fadingEdges = FadingEdges()
), ),
modelProducer = modelProducer, modelProducer = modelProducer,
@@ -111,7 +157,7 @@ fun TimeLineChart(
minZoom = Zoom.min(Zoom.Content, Zoom.fixed()) minZoom = Zoom.min(Zoom.Content, Zoom.fixed())
), ),
animationSpec = animationSpec, animationSpec = animationSpec,
modifier = modifier, modifier = modifier.height(224.dp),
) )
} }
} }