I'm relatively new to Android Compose and as a project, I'm trying to design a bar graph. Gathering various examples, I'm now trying to design the axes in a Canvas composable with a DrawScope. Once I've drawn the lines, my next task was to create the labels for the y-axis on the right hand side of the Canvas. But as shown in the picture, trying to align the text to the proper dividing lines on the y-axis, I've hit a snag where the final drawText is off the canvas at the bottom. Yet, it can draw off on the top of the canvas. I'm not sure why, but it probably is a flaw with the way I've written the composable below.
@OptIn(ExperimentalTextApi::class)
@Composable
fun BarGraphAxes(modifier: Modifier,
verticalLines: Int,
horizontalLines : Int) {
val barAxisColor = colorResource(id = R.color.grey)
val textColor = colorResource(id = R.color.textprimary)
val configuration = LocalConfiguration.current
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Top) {
Box(modifier = Modifier
.width(configuration.screenWidthDp.dp)
.height(300.dp)
.padding(start = 30.dp, top = 20.dp)
) {
val textMeasurer = rememberTextMeasurer()
Canvas(modifier = Modifier.fillMaxSize()) {
val barWidthPx =
1.dp.toPx() //calculate in DP before converting to Px is the Compose preferred way to draw
val yAxisBottomExtraOffset = 20.dp.toPx()
val dashPathEffect =
PathEffect.dashPathEffect(
intervals = floatArrayOf(10f, 10f),
phase = 0f
)
val myWidth = (size.width.toDp() - 50.dp).toPx()
val topPaddingForAxes = 15
//draw X-Axis
drawLine(
color = barAxisColor,
start = Offset(0f, size.height),
end = Offset(myWidth, size.height),
strokeWidth = barWidthPx,
)
//draw Y-Axis right hand
drawLine(
color = barAxisColor,
start = Offset(myWidth, 0f),
end = Offset(myWidth, size.height + yAxisBottomExtraOffset),
strokeWidth = barWidthPx,
pathEffect = dashPathEffect
)
//draw Y-Axis left hand
drawLine(
color = barAxisColor,
start = Offset(0f, 0f),
end = Offset(0f, size.height + yAxisBottomExtraOffset),
strokeWidth = barWidthPx,
pathEffect = dashPathEffect
)
//draw X-Axis top
drawLine(
color = barAxisColor,
start = Offset(0f, 0f),
end = Offset(myWidth, 0f),
strokeWidth = barWidthPx,
)
val horizontalSize = size.height / (horizontalLines + 1)
val verticalSize = myWidth / (verticalLines + 1) //distance between the axes
//Note, this is not where the final drawText will be at, just experimenting with static positioning.
drawText(textMeasurer, text = "Label", style = TextStyle(fontSize = 12.sp, color = textColor), overflow = TextOverflow.Ellipsis, topLeft = Offset(myWidth, 0f - 20f))
drawText(textMeasurer, text = "Label", style = TextStyle(fontSize = 12.sp, color = textColor), overflow = TextOverflow.Ellipsis, topLeft = Offset(myWidth, horizontalSize - 20f))
drawText(textMeasurer, text = "Label", style = TextStyle(fontSize = 12.sp, color = textColor), overflow = TextOverflow.Ellipsis, topLeft = Offset(myWidth, (horizontalSize * 2) - 20f))
drawText(textMeasurer, text = "Label", style = TextStyle(fontSize = 12.sp, color = textColor), overflow = TextOverflow.Ellipsis, topLeft = Offset(myWidth, (horizontalSize * 3) - 20f))
//draw the dividing vertical axes
repeat(verticalLines) { i ->
val startXCoordinate = verticalSize * (i + 1) //start at 0
drawLine(
color = barAxisColor,
start = Offset(startXCoordinate, 0f),
end = Offset(
startXCoordinate,
size.height + yAxisBottomExtraOffset
),
strokeWidth = barWidthPx,
pathEffect = dashPathEffect
)
}
//draw the dividing horizontal axes
repeat(horizontalLines) { i ->
val startYCoordinate = horizontalSize * (i + 1)
drawLine(
color = barAxisColor,
start = Offset(0f, startYCoordinate),
end = Offset(myWidth, startYCoordinate),
strokeWidth = barWidthPx
)
}
}
}
}
}
If anyone can point me in the right direction on why drawText goes off at the bottom but not at the top, I'd appreciate it.
There might be a bug with overload function that takes TextMeasurer
as param. If you use
val textMeasurer = rememberTextMeasurer()
val result = remember {
textMeasurer.measure(
text="Label",
style = TextStyle(fontSize = 12.sp, color = textColor),
overflow = TextOverflow.Ellipsis
)
}
And draw with
drawText(
textLayoutResult = result,
topLeft = Offset(myWidth, (horizontalSize * 3) - 20f)
)
It works fine. You should be able to draw out of Canvas anything unless Canvas or any of its parents are clipped.