Charts in Jetpack compose - Jetpack Compose Bar Chart Tutorial: Build Animated Charts with Code Examples

Last updated Jan 05, 2025

Jetpack Compose has revolutionized Android UI development with its declarative approach to building user interfaces. This tutorial will guide you through building a bar chart using Jetpack Compose, a modern toolkit for crafting beautiful and responsive user interfaces in Android. We will see how to create Bar Chart using Jetpack Compose. Jetpack Compose is a cutting-edge toolkit for creating native Android user interfaces.It simplifies and accelerates UI development on Android by using minimal code, powerful tools, and explicit Kotlin APIs. Compose supports material design ideas. Many of its UI elements are built with material design in mind right out of the box.

A bar chart, also known as a bar graph, is a type of chart or graph that uses rectangular bars with heights or lengths proportional to the values they represent to display categorical data.

 

 

Animated Barchart Table of Contents

  1. Introduction
  2. Basic Bar Chart Implementation
  3. Adding Customization Options
  4. Implementing Animations
  5. Advanced Features
  6. Best Practices
  7. Complete Example

1. Introduction

Bar charts are essential visualization tools that help users understand and compare data. With Jetpack Compose, we can create beautiful, responsive bar charts that seamlessly integrate with modern Android applications.

2. Basic Bar Chart Implementation

Let's start with a basic implementation of a bar chart. First, we'll define our data model:

data class BarChartData(
    val label: String,
    val value: Float,
    val color: Color = Color.Blue
)

 

Now, let's create our basic bar chart composable:

@Composable
fun SimpleBarChart(
    data: List<BarChartData>,
    modifier: Modifier = Modifier
) {
    Canvas(
        modifier = modifier
            .fillMaxWidth()
            .height(300.dp)
    ) {
        val barWidth = size.width / (data.size * 2)
        val maxValue = data.maxOf { it.value }

        data.forEachIndexed { index, item ->
            val barHeight = (item.value / maxValue) * size.height
            val x = index * barWidth * 2 + barWidth / 2
            val y = size.height - barHeight

            drawRect(
                color = item.color,
                topLeft = Offset(x, y),
                size = Size(barWidth, barHeight)
            )
        }
    }
}

 

3. Adding Customization Options

@Composable
fun CustomizableBarChart(
    data: List<BarChartData>,
    modifier: Modifier = Modifier,
    barWidth: Float = 40f,
    barSpacing: Float = 20f,
    maxHeight: Float = 300f,
    showLabels: Boolean = true,
    showValues: Boolean = true
) {
    Box(
        modifier = modifier
            .fillMaxWidth()
            .height(maxHeight.dp + 60.dp)
    ) {
        // Bar Chart Implementation
        Canvas(/* ... */)
        
        // Labels and Values
        Row(/* ... */)
    }
}

 

4. Implementing Animations

Adding animations makes our bar chart more engaging:

@Composable
fun AnimatedBarChart(
    data: List<BarChartData>,
    modifier: Modifier = Modifier
) {
    var animatedData by remember { mutableStateOf(data.map { it.copy(value = 0f) }) }
    
    LaunchedEffect(data) {
        animate(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = tween(
                durationMillis = 1000,
                easing = FastOutSlowInEasing
            )
        ) { value, _ ->
            animatedData = data.map { 
                it.copy(value = it.value * value) 
            }
        }
    }
    
    CustomizableBarChart(
        data = animatedData,
        modifier = modifier
    )
}

 

5. Advanced Features

5.1 Interactive Tooltips

Add tooltips to show detailed information when users tap on bars

@Composable
fun InteractiveBarChart(
    data: List<BarChartData>,
    modifier: Modifier = Modifier
) {
    var selectedBar by remember { mutableStateOf<Int?>(null) }
    
    Box(modifier = modifier) {
        CustomizableBarChart(
            data = data,
            modifier = Modifier.pointerInput(Unit) {
                detectTapGestures { offset ->
                    // Calculate which bar was tapped
                    selectedBar = calculateTappedBar(offset, data.size)
                }
            }
        )
        
        // Show tooltip if a bar is selected
        selectedBar?.let { index ->
            Tooltip(
                data = data[index],
                modifier = Modifier.align(Alignment.TopCenter)
            )
        }
    }
}

 

5.2 Gradient Bars

Enhance visual appeal with gradient colors:

fun Canvas.drawGradientBar(
    startColor: Color,
    endColor: Color,
    topLeft: Offset,
    size: Size
) {
    val brush = Brush.verticalGradient(
        colors = listOf(startColor, endColor),
        startY = topLeft.y,
        endY = topLeft.y + size.height
    )
    
    drawRect(
        brush = brush,
        topLeft = topLeft,
        size = size
    )
}

 

 

6. Best Practices

  1. Performance: Use remember and mutableStateOf judiciously to prevent unnecessary recompositions.
  2. Accessibility: Include content descriptions for screen readers.
  3. Responsiveness: Design your chart to handle different screen sizes and orientations.
  4. Error Handling: Gracefully handle edge cases like empty data sets or null values

 

7. Complete Example

Here's a complete example implementing all the features we've discussed

package com.example.composeexamples

 

import androidx.compose.animation.core.*

import androidx.compose.foundation.Canvas

import androidx.compose.foundation.layout.*

 

import androidx.compose.material3.MaterialTheme

import androidx.compose.material3.Text

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.text.style.TextAlign

import androidx.compose.ui.tooling.preview.Preview

import androidx.compose.ui.unit.dp

import androidx.compose.ui.unit.sp

import kotlinx.coroutines.delay

import kotlin.math.roundToInt

 

data class BarChartData(

   val label: String,

   val value: Float,

   val color: Color = Color.Blue,

   val description: String = ""

)

 

data class BarChartStyle(

   val barWidth: Float = 40f,

   val barSpacing: Float = 20f,

   val maxHeight: Float = 300f,

   val showLabels: Boolean = true,

   val showValues: Boolean = true,

   val valueTextSize: Float = 12f,

   val labelTextSize: Float = 14f,

   val valueTextColor: Color = Color.Gray,

   val labelTextColor: Color = Color.Black,

   val gridLineColor: Color = Color.LightGray,

   val showGridLines: Boolean = true,

   val gridLineCount: Int = 5,

   val animationDuration: Int = 1000

)

 

@Composable

fun CustomizableBarChart(

   data: List<BarChartData>,

   modifier: Modifier = Modifier,

   style: BarChartStyle = BarChartStyle(),

   animated: Boolean = true

) {

   if (data.isEmpty()) return

 

   val maxValue = data.maxOf { it.value }

 

   // Animation progress

   val animationProgress = remember { Animatable(0f) }

 

   LaunchedEffect(data) {

       if (animated) {

           animationProgress.snapTo(0f)

           animationProgress.animateTo(

               targetValue = 1f,

               animationSpec = tween(

                   durationMillis = style.animationDuration,

                   easing = FastOutSlowInEasing

               )

           )

       } else {

           animationProgress.snapTo(1f)

       }

   }

 

   Box(

       modifier = modifier

           .fillMaxWidth()

           .height(style.maxHeight.dp + 60.dp)

   ) {

       Canvas(

           modifier = Modifier

               .fillMaxWidth()

               .height(style.maxHeight.dp)

               .align(Alignment.TopCenter)

       ) {

           val availableWidth = size.width

           val startX = (availableWidth - (data.size * (style.barWidth + style.barSpacing)) + style.barSpacing) / 2

 

           // Draw grid lines

           if (style.showGridLines) {

               val gridSpacing = size.height / style.gridLineCount

               repeat(style.gridLineCount + 1) { index ->

                   val y = index * gridSpacing

                   drawLine(

                       color = style.gridLineColor,

                       start = Offset(0f, y),

                       end = Offset(size.width, y),

                       strokeWidth = 1f

                   )

               }

           }

 

           // Draw animated bars

           data.forEachIndexed { index, item ->

               val animatedHeight = (item.value / maxValue) * style.maxHeight * animationProgress.value

               val x = startX + index * (style.barWidth + style.barSpacing)

               val y = style.maxHeight - animatedHeight

 

               drawRect(

                   color = item.color,

                   topLeft = Offset(x, y),

                   size = Size(style.barWidth, animatedHeight)

               )

           }

       }

 

       // Labels and values with animation

       Row(

           modifier = Modifier

               .padding(horizontal = 16.dp)

               .align(Alignment.BottomCenter),

           horizontalArrangement = Arrangement.SpaceAround

       ) {

           data.forEach { item ->

               Column(

                   horizontalAlignment = Alignment.CenterHorizontally,

                   modifier = Modifier.width(style.barWidth.dp)

               ) {

                   if (style.showValues) {

                       val animatedValue = (item.value * animationProgress.value).roundToInt()

                       Text(

                           text = animatedValue.toString(),

                           fontSize = style.valueTextSize.sp,

                           color = style.valueTextColor,

                           textAlign = TextAlign.Center

                       )

                   }

                   if (style.showLabels) {

                       Text(

                           text = item.label,

                           fontSize = style.labelTextSize.sp,

                           color = style.labelTextColor,

                           textAlign = TextAlign.Center,

                           modifier = Modifier.padding(top = 4.dp)

                       )

                   }

               }

           }

       }

   }

}

 

// Example usage with animation

@Preview

@Composable

fun AnimatedBarChartExample() {

   var showData by remember { mutableStateOf(false) }

   val data = listOf(

       BarChartData("Jan", 50f, Color(0xFF2196F3)),

       BarChartData("Feb", 75f, Color(0xFF4CAF50)),

       BarChartData("Mar", 30f, Color(0xFFF44336)),

       BarChartData("Apr", 90f, Color(0xFF9C27B0)),

       BarChartData("May", 60f, Color(0xFF00BCD4))

   )

 

   val customStyle = BarChartStyle(

       barWidth = 50f,

       barSpacing = 30f,

       maxHeight = 450f,

       showGridLines = true,

       gridLineCount = 4,

       valueTextSize = 14f,

       labelTextSize = 16f,

       valueTextColor = Color.DarkGray,

       gridLineColor = Color.LightGray.copy(alpha = 0.5f),

       animationDuration = 5000 // 1 second animation

   )

 

   Column(

       modifier = Modifier

           .fillMaxWidth()

           .padding(16.dp)

   ) {

       Text(

           text = "Monthly Sales",

           style = MaterialTheme.typography.headlineSmall,

           modifier = Modifier.padding(bottom = 16.dp)

       )

 

       // LaunchedEffect to trigger animation after a delay

       LaunchedEffect(Unit) {

           delay(200) // Small delay before starting animation

           showData = true

       }

 

       if (showData) {

           CustomizableBarChart(

               data = data,

               style = customStyle,

               modifier = Modifier

                   .fillMaxWidth()

                   .height(250.dp),

               animated = true

           )

       }

   }

}

 

// Screen implementation with animation

@Preview

@Composable

fun AnimatedBarChartScreen() {

   Column(

       modifier = Modifier

           .fillMaxSize()

           .padding(16.dp)

   ) {

       Text(

           text = "Sales Dashboard",

           style = MaterialTheme.typography.headlineSmall,

           modifier = Modifier.padding(bottom = 24.dp)

       )

 

       AnimatedBarChartExample()

 

       Text(

           text = "Chart shows monthly sales performance",

           style = MaterialTheme.typography.labelSmall,

           modifier = Modifier.padding(top = 16.dp)

       )

   }

}

 


 

 

Jetpack compose Bar Chart  Example 2

 

Step 1. Create a new Project in Android Studio.

File > New > New Project > Select (Empty Compose Activity) > Next > Enter Name (BarChartJetpack) > FINISH.

After creating the new project, Android Studio starts Gradle and builds your project, which may take a few seconds.


Step 2.  At first, Open MainActivity.kt file

First we will create a composable function of BarChart() in which we use Canvas for display bar chart where we define point list, animate the graph, draw lines, rectangle and so on..

@Composable
fun BarChart() {
    val point = listOf(
        Point(10, 200),
        Point(90, 100),
        Point(170, 40),
        Point(250, 200),
        Point(330, 120),
        Point(410, 100),
        Point(490, 200),
    )

    val context = LocalContext.current
    var start by remember { mutableStateOf(false) }
    val heightPre by animateFloatAsState(
        targetValue = if (start) 1f else 0f,
        animationSpec = FloatTweenSpec(duration = 1000)
    )

    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(400.dp)
            .background(Color.Yellow).padding(40.dp)
            .pointerInput(Unit) {
                detectTapGestures(
                    onTap = {
                        val i = identifyCLickItem(point, it.x, it.y)
                        Toast
                            .makeText(context, "onTap: $i", Toast.LENGTH_SHORT)
                            .show()
                    }
                )
            }
    ) {
        drawLine(
            start = Offset(10f, 600f),
            end = Offset(10f, 0f),
            color = Color.Black,
            strokeWidth = 2f
        )
        drawLine(
            start = Offset(10f, 600f),
            end = Offset(600f, 600f),
            color = Color.Black,
            strokeWidth = 1f
        )
        start = true

        for (p in point) {
            drawRect(
                color = Color.DarkGray,
                topLeft = Offset(p.x + 30f, 600 - (600 - p.y) * heightPre),
                size = Size(55f, (600 - p.y) * heightPre)
            )
        }
    }
}


Now we will create a private function identifyClickItem() which will help to get position by clicking on chart whether its a no.(1,2,3...) or -1.

private fun getPositionFromAngle(
    angles: List,
    touchAngle: Double
): Int {
    var totalAngle = 0f
    for ((i, angle) in angles.withIndex()) {
        totalAngle += angle
        if (touchAngle <= totalAngle) {
            return i
        }
    }
    return -1
}


Finally we will call our BarChart() function inside onCreate() function, we will use scaffold and TopAppBar for display the title in action bar.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BarChartJetpackTheme {
                Surface(color = MaterialTheme.colors.background) {
                    Scaffold(
                        topBar = {
                            TopAppBar(
                                title = {
                                    Text(
                                        text = "Bar Chart",
                                        modifier = Modifier.fillMaxWidth(),
                                        textAlign = TextAlign.Center
                                    )
                                }
                            )
                        }
                    ) {
                        Column(
                            modifier = Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center
                        ) {
                            BarChart()
                        }
                    }
                }
            }
        }
    }
}

 

Complete example code create Bar chart using Jetpack Compose

import android.graphics.Point
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.FloatTweenSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
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.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.nishajain.barchartjetpack.ui.theme.BarChartJetpackTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BarChartJetpackTheme {
                Surface(color = MaterialTheme.colors.background) {
                    Scaffold(
                        topBar = {
                            TopAppBar(
                                title = {
                                    Text(
                                        text = "Bar Chart",
                                        modifier = Modifier.fillMaxWidth(),
                                        textAlign = TextAlign.Center
                                    )
                                }
                            )
                        }
                    ) {
                        Column(
                            modifier = Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center
                        ) {
                            BarChart()
                        }
                    }
                }
            }
        }
    }
}

private fun identifyCLickItem(
    points: List,
    x: Float,
    y: Float
): Int {
    for ((index, point) in points.withIndex()) {
        if (x > point.x + 20 && x < point.x + 20 + 40) {
            return index
        }
    }
    return -1
}

@Composable
fun BarChart() {
    val point = listOf(
        Point(10, 200),
        Point(90, 100),
        Point(170, 40),
        Point(250, 200),
        Point(330, 120),
        Point(410, 100),
        Point(490, 200),
    )

    val context = LocalContext.current
    var start by remember { mutableStateOf(false) }
    val heightPre by animateFloatAsState(
        targetValue = if (start) 1f else 0f,
        animationSpec = FloatTweenSpec(duration = 1000)
    )

    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .height(400.dp)
            .background(Color.Yellow).padding(40.dp)
            .pointerInput(Unit) {
                detectTapGestures(
                    onTap = {
                        val i = identifyCLickItem(point, it.x, it.y)
                        Toast
                            .makeText(context, "onTap: $i", Toast.LENGTH_SHORT)
                            .show()
                    }
                )
            }
    ) {
        drawLine(
            start = Offset(10f, 600f),
            end = Offset(10f, 0f),
            color = Color.Black,
            strokeWidth = 2f
        )
        drawLine(
            start = Offset(10f, 600f),
            end = Offset(600f, 600f),
            color = Color.Black,
            strokeWidth = 1f
        )
        start = true

        for (p in point) {
            drawRect(
                color = Color.DarkGray,
                topLeft = Offset(p.x + 30f, 600 - (600 - p.y) * heightPre),
                size = Size(55f, (600 - p.y) * heightPre)
            )
        }
    }
}

 

Step 3. Add the following colors to your Color.kt file

import androidx.compose.ui.graphics.Color

val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFFFFC107)
val Purple700 = Color(0xFFFFEB3B)
val Teal200 = Color(0xFF03DAC5)

 


Step 4. Run the app in your emulator or real device and you will get the following output:

OUTPUT

Bar Chart Using Jetpack Compose

 

Tap on Bar 

 

Bar Chart Using Jetpack Compose 2


Conclusion: In this article we have covered how to create Bar Chart using JetPack Compose.

Jetpack compose Chart Examples

Create Pie Chart with Jetpack compose

Create Line Chart with Jetpack compose

Article Contributed By :
https://www.rrtutors.com/site_assets/profile/assets/img/avataaars.svg

2457 Views