Vico

2.8. CartesianValueFormatter

2.8.1. Overview

x and y values are numerical. You can use CartesianValueFormatter to format them for display. They can remain numbers, or they can be transformed to dates, category names, and so on.

There are two CartesianValueFormatter factory functions: CartesianValueFormatter.decimal and CartesianValueFormatter.yPercent. For more complex use cases, create custom implementations can be created. CartesianValueFormatters are most commonly used with HorizontalAxis and VerticalAxis—see the valueFormatter parameters and properties. However, these aren’t the only APIs that accept CartesianValueFormatter instances.

When the values remain numerical, formatting is straightforward. DecimalFormat, which is used by CartesianValueFormatter.decimal, is typically involved. Thus, on this page, we focus on formatting with nonnumerical results. The aim in such cases is to find a predictable mapping that doesn’t involve overly large values (as these can lead to precision loss). The optimal approach depends on the use case. Some common situations are discussed below.

2.8.2. Categories

A chart’s domain can be a list of categories. An easy way to implement this pattern is to use x values that serve as indices. As previously discussed, the series-creating functions have overloads that add such x values automatically.

val data = mapOf("A" to 8f, "B" to 4f, "C" to 6f)
val labelListKey = ExtraStore.Key<List<String>>()
cartesianChartModelProducer.runTransaction {
    columnSeries { series(data.values) }
    updateExtras { it[labelListKey] = data.keys.toList() }
}
CartesianValueFormatter { context, x, _ ->
    context.model.extraStore[labelListKey][x.toInt()]
}

2.8.3. Dates

Another common use case is mapping dates to y values.

The dates will be spaced out proportionally. If you need nonproportional spacing, use the approach from 2.8.2. This is also worth considering if there are no gaps in your data, in which case there’s no distinction between proportional and nonproportional spacing—the category approach will be simpler.

val data =
    mapOf(
        LocalDate.parse("2022-07-01") to 2f,
        LocalDate.parse("2022-07-02") to 6f,
        LocalDate.parse("2022-07-04") to 4f,
    )
val xToDateMapKey = ExtraStore.Key<Map<Float, LocalDate>>()
val xToDates = data.keys.associateBy { it.toEpochDay().toFloat() }
cartesianChartModelProducer.runTransaction {
    lineSeries { series(xToDates.keys, data.values) }
    updateExtras { it[xToDateMapKey] = xToDates }
}
val dateTimeFormatter = DateTimeFormatter.ofPattern("d MMM")
CartesianValueFormatter { context, x, _ ->
    (context.model.extraStore[xToDateMapKey][x] ?: LocalDate.ofEpochDay(x.toLong()))
        .format(dateTimeFormatter)
}

2.8.3. Sample charts