patrykandpatrick / vico

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

Issue showing bottom axis labels, only first bar label is shown. #829

Closed seyone22 closed 1 month ago

seyone22 commented 1 month ago

How to reproduce

@Composable fun ReportCard( modifier: Modifier = Modifier, category: Int, viewModel: ReportViewModel ) { val categoryName = remember { mutableStateOf("") } val values = remember { mutableStateOf<Map<Int?, Double>>(emptyMap()) }

val labelListKey = ExtraStore.Key<List<String>>()

val scrollState = rememberVicoScrollState()
val zoomState = rememberVicoZoomState()

val modelProducer = remember { CartesianChartModelProducer() }

// Use LaunchedEffect to run a coroutine within the Composable
LaunchedEffect(Unit) {
    categoryName.value = viewModel.categoryNameOf(category)
    values.value = viewModel.getExpensesFromCategory(category)

    val doubleValues = values.value.values.toList()
    val keysList = values.value.keys.toList()

    val resultsList = keysList.map { key ->
        viewModel.categoryNameOf(key ?: 0)
    }

    Log.d("TAG", "ReportCard: $resultsList")
    Log.d("TAG", "ReportCard: $doubleValues")

    modelProducer.runTransaction {
        columnSeries { series(doubleValues) }
        extras { it[labelListKey] = resultsList }
    }
}
val xx =
    CartesianValueFormatter { x, chartValues, _ -> chartValues.model.extraStore[labelListKey][x.toInt()] }

Card(
    modifier = modifier
        .padding(24.dp)
) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .height(240.dp)
            .fillMaxSize()
    ) {
        Text(text = "By Category - ${categoryName.value}")

        CartesianChartHost(
            chart = rememberCartesianChart(
                rememberColumnCartesianLayer(
                    ColumnCartesianLayer.ColumnProvider.series(
                        rememberLineComponent(
                            color = MaterialTheme.colorScheme.primary,
                            thickness = 16.dp,
                            shape = remember { Shape.rounded(allPercent = 24) },
                        )
                    )
                ),
                startAxis = rememberStartAxis(),
                bottomAxis =
                rememberBottomAxis(
                    valueFormatter = xx,
                    itemPlacer =
                    remember {
                        HorizontalAxis.ItemPlacer.default(
                            spacing = 3,
                            addExtremeLabelPadding = true
                        )
                    },
                ),
                horizontalLayout = HorizontalLayout.fullWidth(),
            ),
            scrollState = scrollState,
            zoomState = zoomState,
            modelProducer = modelProducer,
            placeholder = {
                Text(text = "Error")
            },
            modifier = Modifier.fillMaxSize()
        )
    }
}

}

Observed behavior

image

Here, only the label for the first bar (A) is shown. I tried just hardcoding two lists to see if my incoming data was faulty, but even in that case, only the first bar's label is shown.

Expected behavior

Labels for the second, third, and other bars should also be visible.

Vico version(s)

2.0.0-alpha.27

Android version(s)

Pixel 7 API 34

Additional information

No response

patrickmichalik commented 1 month ago

Hello! This behavior is expected. The label spacing is set to 3, so a label is shown for every third column. From your code:

HorizontalAxis.ItemPlacer.default(spacing = 3, addExtremeLabelPadding = true)

To obtain the desired behavior, please ensure that spacing is set to 1, which is the default value:

HorizontalAxis.ItemPlacer.default(addExtremeLabelPadding = true)

Each column will then be labeled.

By the way, in your code, a new ExtraStore.Key is created whenever ReportCard is recomposed. As described here, this is an error. To avoid exceptions, please use remember, as shown below, or move labelListKey to the file level or a singleton. I’d also suggest ensuring that the CartesianValueFormatter isn’t recreated more often than needed.

val labelListKey = remember { ExtraStore.Key<List<String>>() }