patrykandpatrick / vico

A light and extensible chart library for Android.
https://patrykandpatrick.com/vico/wiki
Apache License 2.0
2.08k stars 125 forks source link

Incorrect values displayed on X axis #544

Closed mastroalberto closed 8 months ago

mastroalberto commented 8 months ago

How to reproduce

Starting with implementing the variables, where dateGradeMap is a vairbale contaning a Map<LocalDate, Float>

val dateGradeMap = materiaVoti.associate {
        it.date to it.grade
    }
    val xValuesToDates = dateGradeMap.keys.associateBy { it.toEpochDay().toFloat() }
    val chartEntryModel = entryModelOf(xValuesToDates.keys.zip(dateGradeMap.values, ::entryOf))
    val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("d MMM")
    val horizontalAxisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, _ ->
        (xValuesToDates[value] ?: LocalDate.ofEpochDay(value.toLong())).format(dateTimeFormatter)
    }
    val context = LocalContext.current
    val chartEntryModelProducer = ChartEntryModelProducer(chartEntryModel.entries)

Then i call the chart function:

Chart(
        chart = lineChart(context),
        chartModelProducer = modelProducer,
        startAxis = rememberStartAxis(itemPlacer = verticalItemPlacer),
        bottomAxis = rememberBottomAxis(valueFormatter = horizontalAxisValueFormatter),
        marker = rememberGradesMarker()
    )

NOTE: VerticalItemPlacer is indeed a custom implementation of the object AxisItemPlacer.Vertical, it has the function getLabelValues modified:

override fun getLabelValues(
            context: ChartDrawContext,
            axisHeight: Float,
            maxLabelHeight: Float,
            position: AxisPosition.Vertical
        ): List<Float> = listOf(2f, 4f, 6f, 8f, 10f)

Observed behavior

The observed behavoir is particular: if there is not a big distance between two or more dates, the chart is displayed properly without any kind of unexpected behavoir, if the dates have a month or more distance between them, the chart does not display correctly and x axis if filled with others dates between them, presumably all the dates separating the two values For a further comprehension here are two pictures describing the problem:

photo_2024-01-15_16-14-56 photo_2024-01-15_16-15-10

Expected behavior

Following the wiki, with the same variables declared is to have on the x axis a number n of dates formatted with d MMM (e.g 2 jul, 4 sep) regardless the distance between the dates themeselves

Vico version(s)

Latest stable version

Android version(s)

14 (Tested on CrDroid 10)

Additional information

No response

patrickmichalik commented 8 months ago

Hello! Vico interprets x values in a mathematically accurate fashion. Entries whose x values aren’t equally spaced out on the number line won’t be equally spaced out on a chart. Please use a set of x values that accurately represents the desired result (for example, consecutive integers). A similar question was asked in discussion #509—see here for an explanation of what’s happening, and here for the solution. Discussion #400 is relevant too. Let me know if you’d like me to prepare an example solution based on your code.

By the way, I’ve noticed a few issues with your setup:

These problems severely hurt performance and may cause unexpected behavior. If you’d like me to expand on any of the points above or update your code snippet with the suggested fixes, I’ll be happy to do so.

Because the behavior described isn’t a bug, I’ll be closing this issue, but please feel free to comment.

mastroalberto commented 8 months ago

Thank you very much for the explanation, very clear, really approcieated it. Yes I would really like to have an example based on my code, thank you in advance!

patrickmichalik commented 7 months ago

Here you go, @al6263. Let me know if you have any questions.

@Composable
fun Example(materiaVoti: /* ... */) {
    val chartEntryModelProducer = remember { ChartEntryModelProducer() }
    LaunchedEffect(chartEntryModelProducer, materiaVoti) {
        withContext(Dispatchers.Default) {
            chartEntryModelProducer.setEntries(
                entries = listOf(materiaVoti.map { it.grade }.mapIndexed(::entryOf)),
                updateExtras = { extraStore ->
                    extraStore[dateKey] = materiaVoti.map { gradeInfo -> gradeInfo.date.format(dateTimeFormatter) }
                },
            )
        }
    }
    Chart(
        chart = lineChart(),
        chartModelProducer = chartEntryModelProducer,
        startAxis = rememberStartAxis(itemPlacer = verticalItemPlacer),
        bottomAxis =
            rememberBottomAxis(
                valueFormatter =
                    remember { { x, chartValues -> chartValues.chartEntryModel.extraStore[dateKey][x.toInt()] } },
            ),
    )
}

private val dateTimeFormatter = DateTimeFormatter.ofPattern("d MMM")
private val dateKey = ExtraStore.Key<List<String>>()
// ...