Jetpack Compose Doughnut Chart with Source Code

jetpack compose charts

This is a doughnut chart made with Android Jetpack Compose. It utilizes the power of Canvas API to draw the arc segments. You can download the source code for free below.

Each arc contains a different color. I have also added a legend below the chart. You can pass the colors using the colors parameter. Make a list of colors using the listOf() method in Kotlin and assign it to the colors.

Along with colors, the Jetpack Compose doughnut chart takes multiple parametes. They are values for the arc sizes, legend to display the text, size of the Canvas and thickness of the arcs. You can adjust them according to your requirements. Make sure that size of the colors, values, and legend is the same.

Coming to the logic, each arc segment is drawn by using drawArc() method of Canvas. The startAngle of each arc is equal to the sum of the sweepAngles of the previous arcs. Each one has a different color and size. To calculate the size of the arc, first, calculate the sum of all the values. Next, calculate the proportion of each segment by using the formula value * 100 / sumOfValues. Next, convert the proportion to angle using 360 * proportion / 100. This angle is the sweepAngle for the each arc.

To display the legend, put the text and circle inside a Row layout. You can create the circle using the Box. Put each Row inside a Column. Finally, place the Column below the chart.

Before adding the code to your project, make sure that you are using the latest Android Studio. Jetpack Compose doesn’t work in the older versions.

Final output:

If the video isn’t working, watch it on the YouTube.

Helpful links to understand the code:

Here are the Gradle files used in the project.

Here is the source code:

MainActivity.kt:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp


/*
You can use the following code for commercial purposes with some restrictions.
Read the full license here: https://semicolonspace.com/semicolonspace-license/
For more designs with source code,
visit: https://semicolonspace.com/jetpack-compose-samples/
 */
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BlogPostsTheme(darkTheme = false) {
                Column(
                    modifier = Modifier.fillMaxSize(),
                    horizontalAlignment = Alignment.CenterHorizontally,
                    verticalArrangement = Arrangement.Center
                ) {
                    DoughnutChart1()
                }
            }
        }
    }
}

@Composable
fun DoughnutChart1(
    values: List<Float> = listOf(65f, 40f, 25f, 20f),
    colors: List<Color> = listOf(
        Color(0xFFFF6384),
        Color(0xFFFFCE56),
        Color(0xFF36A2EB),
        Color(0xFF448AFF)
    ),
    legend: List<String> = listOf("Mango", "Banana", "Apple", "Melon"),
    size: Dp = 200.dp,
    thickness: Dp = 36.dp
) {

    // Sum of all the values
    val sumOfValues = values.sum()

    // Calculate each proportion
    val proportions = values.map {
        it * 100 / sumOfValues
    }

    // Convert each proportion to angle
    val sweepAngles = proportions.map {
        360 * it / 100
    }

    Canvas(
        modifier = Modifier
            .size(size = size)
    ) {

        var startAngle = -90f

        for (i in values.indices) {
            drawArc(
                color = colors[i],
                startAngle = startAngle,
                sweepAngle = sweepAngles[i],
                useCenter = false,
                style = Stroke(width = thickness.toPx(), cap = StrokeCap.Butt)
            )
            startAngle += sweepAngles[i]
        }

    }

    Spacer(modifier = Modifier.height(32.dp))

    Column {
        for (i in values.indices) {
            DisplayLegend(color = colors[i], legend = legend[i])
        }
    }
}

@Composable
fun DisplayLegend(color: Color, legend: String) {

    Row(
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Box(
            modifier = Modifier
                .size(10.dp)
                .background(color = color, shape = CircleShape)
        )

        Spacer(modifier = Modifier.width(4.dp))

        Text(
            text = legend,
            color = Color.Black
        )
    }
}

Related:

Leave a Comment