
In this article, we’ll create the following circular chart using Jetpack Compose:

Prerequisites:
Let’s get started.
First, create an empty Compose project and open MainActivity.kt. Create a CircularChart() composable and call it from the onCreate() method. We’ll write our code in it.
MainActivity for the Material 3 Jetpack Compose:
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.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
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
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularChart()
}
}
}
}
}
}
@Composable
fun CircularChart() {
}
MainActivity for the Material 2 version:
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.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
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
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BlogPostsTheme(darkTheme = false) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularChart()
}
}
}
}
}
@Composable
fun CircularChart() {
}
Our circular chart needs arc values, colors, legend, and size. Let’s add them as parameters.
@Composable
fun CircularChart(
values: List<Float> = listOf(65f, 60f, 55f),
colors: List<Color> = listOf(
Color(0xFFbe1558),
Color(0xFFe75874),
Color(0xFFfbcbc9)
),
backgroundCircleColor: Color = Color.LightGray.copy(alpha = 0.3f),
legend: List<String> = listOf("Mango", "Apple", "Melon"),
size: Dp = 280.dp,
thickness: Dp = 16.dp,
gapBetweenCircles: Dp = 42.dp
) {
}
Here size indicates the chart size and thickness indicates the width of each arc.
We need to convert the values into equivalent angles.
@Composable
fun CircularChart(
values: List<Float> = listOf(65f, 60f, 55f),
colors: List<Color> = listOf(
Color(0xFFbe1558),
Color(0xFFe75874),
Color(0xFFfbcbc9)
),
backgroundCircleColor: Color = Color.LightGray.copy(alpha = 0.3f),
legend: List<String> = listOf("Mango", "Apple", "Melon"),
size: Dp = 280.dp,
thickness: Dp = 16.dp,
gapBetweenCircles: Dp = 42.dp
) {
// Convert each value to angle
val sweepAngles = values.map {
360 * it / 100
}
}
In a circle, 100% is equivalent to 360 degrees. So, to convert each value into an angle (degree), we have multiplied it by the ratio of (360/100).
As our sweep angles are ready, let’s add the Canvas and draw the arcs.
@Composable
fun CircularChart(
values: List<Float> = listOf(65f, 60f, 55f),
colors: List<Color> = listOf(
Color(0xFFbe1558),
Color(0xFFe75874),
Color(0xFFfbcbc9)
),
backgroundCircleColor: Color = Color.LightGray.copy(alpha = 0.3f),
legend: List<String> = listOf("Mango", "Apple", "Melon"),
size: Dp = 280.dp,
thickness: Dp = 16.dp,
gapBetweenCircles: Dp = 42.dp
) {
// Convert each value to angle
val sweepAngles = values.map {
360 * it / 100
}
Canvas(
modifier = Modifier
.size(size)
) {
var arcRadius = size.toPx()
for (i in values.indices) {
arcRadius -= gapBetweenCircles.toPx()
drawCircle(
color = backgroundCircleColor,
radius = arcRadius / 2,
style = Stroke(width = thickness.toPx(), cap = StrokeCap.Butt)
)
drawArc(
color = colors[i],
startAngle = -90f,
sweepAngle = sweepAngles[i],
useCenter = false,
style = Stroke(width = thickness.toPx(), cap = StrokeCap.Round),
size = Size(arcRadius, arcRadius),
topLeft = Offset(
x = (size.toPx() - arcRadius) / 2,
y = (size.toPx() - arcRadius) / 2
)
)
}
}
}
It looks like this:

We have set the startAngle = -90f on each arc. -90f represents the 12’0 clock and acts as a starting point. In each iteration, we are changing the arc radius (arcRadius). The outer circle is drawn first, followed by the middle circle, and finally the inner circle.
The size parameter of the drawArc() method sets the size of the arc, and the topLeft parameter positions the circle relative to the center of the canvas.
Next, let’s display the legend. Create a separate composable called DisplayLegend(). We need color and legend values.
@Composable
fun DisplayLegend(color: Color, legend: String) {
}
Each part contains a circle and text. We can make a circle using the Box() and Shape APIs. For the text, use the Text() composable. Put the box and text in a Row layout.
@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
)
}
}
Call it from the CircularChart() method. Our final code looks like this:
@Composable
fun CircularChart(
values: List<Float> = listOf(65f, 60f, 55f),
colors: List<Color> = listOf(
Color(0xFFbe1558),
Color(0xFFe75874),
Color(0xFFfbcbc9)
),
backgroundCircleColor: Color = Color.LightGray.copy(alpha = 0.3f),
legend: List<String> = listOf("Mango", "Apple", "Melon"),
size: Dp = 280.dp,
thickness: Dp = 16.dp,
gapBetweenCircles: Dp = 42.dp
) {
// Convert each value to angle
val sweepAngles = values.map {
360 * it / 100
}
Canvas(
modifier = Modifier
.size(size)
) {
var arcRadius = size.toPx()
for (i in values.indices) {
arcRadius -= gapBetweenCircles.toPx()
drawCircle(
color = backgroundCircleColor,
radius = arcRadius / 2,
style = Stroke(width = thickness.toPx(), cap = StrokeCap.Butt)
)
drawArc(
color = colors[i],
startAngle = -90f,
sweepAngle = sweepAngles[i],
useCenter = false,
style = Stroke(width = thickness.toPx(), cap = StrokeCap.Round),
size = Size(arcRadius, arcRadius),
topLeft = Offset(
x = (size.toPx() - arcRadius) / 2,
y = (size.toPx() - arcRadius) / 2
)
)
}
}
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
)
}
}
Output:

This is all about the circular chart in Jetpack Compose. I hope you have learned something new. If you have any doubts, leave a comment below.
Related Articles:
References: