Bar Chart | Jetpack Compose Canvas | Custom Android Chart

In this article we will learn how to make Bar Chart using jetpack compose Canvas in Android. This article is in continuation with our Canvas series in Android using Kotlin and Jetpack Compose. You can read about how to make Line Chart here. If you want to watch full video on how to create Bar Chart then watch here.

To draw bar chart first we need some sample to draw chart. Our sample data is list of Pair of String and Integer. First part of the Pair is String which will contain the info about the Data point and Second part of Pair will contain the actual Integer value based on which we will draw the graph.

val chartData = listOf(
        Pair("I", 90),
        Pair("II", 110),
        Pair("III", 70),
        Pair("IV", 205),
        Pair("V", 150),
        Pair("VI", 175)
    )

Now we need to create some variable for the our graph.

val spacingFromLeft = 100f
    val spacingFromBottom = 40f

    val upperValue = remember { (chartData.maxOfOrNull { it.second }?.plus(1)) ?: 0 }
    val lowerValue = remember { (chartData.minOfOrNull { it.second }?.toInt() ?: 0) }

    val density = LocalDensity.current

In jetpack compose canvas we need to define Text Paint for any type of text that we need to draw on the canvas. In this Paint Object we decide what property we want to give to our text like its color, text size, text font etc.

//paint for the text shown in data values
    val textPaint = remember(density) {
        Paint().apply {
            color = android.graphics.Color.BLACK
            textAlign = Paint.Align.CENTER
            textSize = density.run { 12.sp.toPx() }
        }
    }

Now we need to find canvas height and width to accurately display the content. Create a variable which maintain the space b/w each vertical bar along the horizontal axis. Then loop through data and each time draw text along vertical and horizontal axis.

        val canvasHeight = size.height
        val canvasWidth = size.width

        val spacePerData = (canvasWidth - spacingFromLeft) / chartData.size

        //loop through each index by step of 1
        //data shown horizontally
        (chartData.indices step 1).forEach { i ->
            val text = chartData[i].first
            drawContext.canvas.nativeCanvas.apply {
                drawText(
                    text,
                    spacingFromLeft + 30f + i * spacePerData,
                    canvasHeight,
                    textPaint
                )
            }
        }

Now we will show the vertical text at 5 points and also show a small line in front of text to accurately visualize the bar value. For this we will loop 5 times and each time draw text along with a small horizontal line along the vertical axis.

       val valuesToShow = 5f  //we will show 5 data values on vertical line

        val eachStep = (upperValue - lowerValue) / valuesToShow
        //data shown vertically
        (0..4).forEach { i ->
            drawContext.canvas.nativeCanvas.apply {
                drawText(
                    round(lowerValue + eachStep * i).toString(),
                    20f,
                    canvasHeight - 30f - i * canvasHeight / 5f,
                    textPaint
                )
            }

            //draw horizontal line at each value
            drawLine(
                start = Offset(
                    spacingFromLeft - 20f,
                    canvasHeight - spacingFromBottom - i * canvasHeight / 5f
                ),
                end = Offset(
                    spacingFromLeft,
                    canvasHeight - spacingFromBottom - i * canvasHeight / 5f
                ),
                color = Color.Black,
                strokeWidth = 3f
            )
        }

Now draw the full length horizontal and vertical line along the X & Y axis using drawLine function.

        //Vertical line
        drawLine(
            start = Offset(spacingFromLeft, canvasHeight - spacingFromBottom),
            end = Offset(spacingFromLeft, 0f),
            color = Color.Black,
            strokeWidth = 3f
        )

        //Horizontal line
        drawLine(
            start = Offset(spacingFromLeft, canvasHeight - spacingFromBottom),
            end = Offset(canvasWidth - 40f, canvasHeight - spacingFromBottom),
            color = Color.Black,
            strokeWidth = 3f
        )

Now come to our final step and draw the bars. We will loop upon the our graph data and fetching its index and value. We are showing the round corner bars and a text above the bar which will show the bar value. The drawRoundRect function takes following arguments :

  1. topLeft – this is a Offset(top left corner location of Rect) which takes two coordinates points(X and Y)
  2. size – this is of Type Size(size in X, size in Y) define width and height of the rectangle.
  3. cornerRadius – here you define the radius of corner of the rectangle.
        //draw bars
        chartData.forEachIndexed { index, chartPair ->

            //draw text at top of each bar
            drawContext.canvas.nativeCanvas.apply {
                drawText(
                    chartPair.second.toString(),
                    spacingFromLeft + 40f + index * spacePerData,
                    (upperValue - chartPair.second.toFloat()) / upperValue * canvasHeight - 10f,
                    textPaint
                )
            }

            //draw Bar for each value
            drawRoundRect(
                color = Color.Magenta,
                topLeft = Offset(
                    spacingFromLeft + 10f + index * spacePerData,
                    (upperValue - chartPair.second.toFloat()) / upperValue * canvasHeight
                ),
                size = Size(
                    55f,
                    (chartPair.second.toFloat() / upperValue) * canvasHeight - spacingFromBottom
                ),
                cornerRadius = CornerRadius(10f, 10f)
            )
        }

GitHub gist for this video: here

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top