Right align texts in Canvas using Jetpack Compose


I want to draw a chart but I'm not able to right-align the Y axis labels:

1

@Composable
fun Chart(
    min: Double = 0.0,
    max: Double = 3823.0,
) {
    val textMeasurer = rememberTextMeasurer()

    val padding = 40f
    val yAxisLabelMaxWidth = textMeasurer.measure(text = "${max.toInt()}").size.width.toFloat()
    val yAxisLabelMinWidth = textMeasurer.measure(text = "${min.toInt()}").size.width.toFloat()

    Canvas(Modifier.size(100.dp)) {
        drawText(
            textMeasurer = textMeasurer,
            text = "${max.toInt()}",
            topLeft = Offset(
                x = 0f,
                y = padding - textMeasurer.measure(text = "$max").size.height / 2,
            ),
            style = TextStyle(
                fontSize = 10.sp,
                background = Color.Green,
            ),
        )

        drawText(
            textMeasurer = textMeasurer,
            text = "${min.toInt()}",
            topLeft = Offset(
                x = yAxisLabelMaxWidth - yAxisLabelMinWidth,
                y = size.height - padding - textMeasurer.measure(text = "$min").size.height / 2,
            ),
            style = TextStyle(
                fontSize = 10.sp,
                background = Color.Green,
            ),
        )
    }
}

There is always a shift when min has less digits than max. However yAxisLabelMaxWidth and yAxisLabelMinWidth have a good value. For example, if max has 4 digits and min just 1, max width is measured at 100px and min width is 25px.

1
Feb 20 at 5:43 PM
User AvatarTurvy
#android#android-jetpack-compose#android-jetpack-compose-canvas

Accepted Answer

You measure the text based on the string characters only. But when you actually display the text you modify its size, so the calculation using the TextMeasurer is off.

Simply also include the style in the calculation, and everything works out:

val yAxisLabelMaxWidth = textMeasurer.measure(text = "${max.toInt()}", style = style).size.width.toFloat()
val yAxisLabelMinWidth = textMeasurer.measure(text = "${min.toInt()}", style = style).size.width.toFloat()

For that you should extract the style into a dedicated variable to prevent writing the same piece of code four times:

val style = TextStyle(
    fontSize = 10.sp,
    background = Color.Green,
)

You should probably do the same for the texts.

This is the result:

User Avatartyg
Feb 20 at 7:03 PM
1