Created
May 23, 2024 06:41
-
-
Save decodeandroid/6d72d8c9cfea07288218681885c66ce5 to your computer and use it in GitHub Desktop.
Line Chart Jetpack Compose Canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @Preview(showBackground = true, showSystemUi = true) | |
| @Composable | |
| fun ShowLineChartPreview() { | |
| val chartData = listOf( | |
| Pair(1, 1.5), | |
| Pair(2, 1.75), | |
| Pair(3, 3.45), | |
| Pair(4, 2.25), | |
| Pair(5, 6.45), | |
| Pair(6, 3.35), | |
| Pair(7, 8.65), | |
| Pair(8, 0.15), | |
| Pair(9, 3.05), | |
| Pair(10, 4.25) | |
| ) | |
| Column( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .background(Color.White) | |
| .padding(16.dp), | |
| verticalArrangement = Arrangement.Center | |
| ) { | |
| LineChart( | |
| data = chartData, | |
| modifier = Modifier | |
| .fillMaxWidth() | |
| .height(300.dp) | |
| .align(CenterHorizontally) | |
| ) | |
| } | |
| } | |
| @Composable | |
| fun LineChart( | |
| data: List<Pair<Int, Double>> = emptyList(), | |
| modifier: Modifier = Modifier | |
| ) { | |
| val spacingFromLeft = 80f | |
| val graphColor = Color.Green //color for your graph | |
| val transparentGraphColor = remember { graphColor.copy(alpha = 0.5f) } | |
| val upperValue = remember { (data.maxOfOrNull { it.second }?.plus(1))?.roundToInt() ?: 0 } | |
| val lowerValue = remember { (data.minOfOrNull { it.second }?.toInt() ?: 0) } | |
| val density = LocalDensity.current | |
| //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() } | |
| } | |
| } | |
| Canvas(modifier = modifier) { | |
| val spacePerData = (size.width - spacingFromLeft) / data.size | |
| //loop through each index by step of 1 | |
| //data shown horizontally | |
| (data.indices step 1).forEach { i -> | |
| val hour = data[i].first | |
| drawContext.canvas.nativeCanvas.apply { | |
| drawText( | |
| hour.toString(), | |
| spacingFromLeft + i * spacePerData, | |
| size.height, | |
| textPaint | |
| ) | |
| } | |
| } | |
| val priceStep = (upperValue - lowerValue) / 5f | |
| //data shown vertically | |
| (0..4).forEach { i -> | |
| drawContext.canvas.nativeCanvas.apply { | |
| drawText( | |
| round(lowerValue + priceStep * i).toString(), | |
| 30f, | |
| size.height - spacingFromLeft - i * size.height / 5f, | |
| textPaint | |
| ) | |
| } | |
| } | |
| //Vertical line | |
| drawLine( | |
| start = Offset(spacingFromLeft, size.height - spacingFromLeft), | |
| end = Offset(spacingFromLeft, 0f), | |
| color = Color.Black, | |
| strokeWidth = 3f | |
| ) | |
| //Horizontal line | |
| drawLine( | |
| start = Offset(spacingFromLeft, size.height - spacingFromLeft), | |
| end = Offset(size.width - 40f, size.height - spacingFromLeft), | |
| color = Color.Black, | |
| strokeWidth = 3f | |
| ) | |
| //Use this to show straight line path | |
| val straightLinePath = Path().apply { | |
| val height = size.height | |
| //loop through index only not value | |
| data.indices.forEach { i -> | |
| val info = data[i] | |
| val x1 = spacingFromLeft + i * spacePerData | |
| val y1 = | |
| (upperValue - info.second).toFloat() / upperValue * height - spacingFromLeft | |
| if (i == 0) { | |
| moveTo(x1, y1) | |
| } | |
| lineTo(x1, y1) | |
| //drawCircle(color = Color.Black, radius = 5f, center = Offset(x1,y1)) //Uncomment it to see the end points | |
| } | |
| } | |
| //Use this to show curved path | |
| var medX: Float | |
| var medY: Float | |
| val curvedLinePath = Path().apply { | |
| val height = size.height | |
| data.indices.forEach { i -> | |
| val nextInfo = data.getOrNull(i + 1) ?: data.last() | |
| val x1 = spacingFromLeft + i * spacePerData | |
| val y1 = | |
| (upperValue - data[i].second).toFloat() / upperValue * height - spacingFromLeft | |
| val x2 = spacingFromLeft + (i + 1) * spacePerData | |
| val y2 = | |
| (upperValue - nextInfo.second).toFloat() / upperValue * height - spacingFromLeft | |
| if (i == 0) { | |
| moveTo(x1, y1) | |
| } else { | |
| medX = (x1 + x2) / 2f | |
| medY = (y1 + y2) / 2f | |
| quadraticBezierTo(x1 = x1, y1 = y1, x2 = medX, y2 = medY) | |
| } | |
| //drawCircle(color = Color.White, radius = 5f, center = Offset(x1,y1)) | |
| //drawCircle(color = Color.Magenta, radius = 9f, center = Offset(medX,medY)) | |
| //drawCircle(color = Color.Blue, radius = 7f, center = Offset(x2,y2)) //Uncomment these to see the control Points | |
| } | |
| } | |
| //Now draw path on canvas | |
| drawPath( | |
| path = straightLinePath, | |
| color = graphColor, | |
| style = Stroke( | |
| width = 1.dp.toPx(), | |
| cap = StrokeCap.Round | |
| ) | |
| ) | |
| //To show the background transparent gradient | |
| val fillPath = android.graphics.Path(straightLinePath.asAndroidPath()).asComposePath().apply { | |
| lineTo(size.width - spacePerData, size.height - spacingFromLeft) | |
| lineTo(spacingFromLeft, size.height - spacingFromLeft) | |
| close() | |
| } | |
| drawPath( | |
| path = fillPath, | |
| brush = Brush.verticalGradient( | |
| colors = listOf( | |
| transparentGraphColor, | |
| Color.Transparent | |
| ), | |
| endY = size.height - spacingFromLeft | |
| ) | |
| ) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment