-
-
Save L10n42/80a28255deb46e817d7fa7e4e05ff989 to your computer and use it in GitHub Desktop.
3D Pie Chart in Jetpack Compose
This file contains 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
import android.graphics.BlurMaskFilter | |
import android.graphics.PorterDuff | |
import android.graphics.PorterDuffXfermode | |
import androidx.compose.animation.core.Animatable | |
import androidx.compose.animation.core.AnimationSpec | |
import androidx.compose.animation.core.LinearOutSlowInEasing | |
import androidx.compose.animation.core.tween | |
import androidx.compose.foundation.Canvas | |
import androidx.compose.foundation.layout.aspectRatio | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.rotate | |
import androidx.compose.ui.draw.scale | |
import androidx.compose.ui.geometry.toRect | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.Paint | |
import androidx.compose.ui.graphics.drawscope.DrawScope | |
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas | |
import kotlinx.coroutines.launch | |
/** | |
* Composable function to display a convex pie chart. | |
* | |
* This function renders a pie chart using a convex arc shape for each slice. | |
* | |
* @param modifier The modifier for layout customization. | |
* @param data The list of data points for the pie chart. | |
* @param startAngle The starting angle (in degrees) for the first slice of the pie chart. | |
* @param rotationsCount The number of complete rotations during animation. | |
* @param pieSliceStyle The style settings for the pie chart slices. | |
* @param animationSpec The animation specification for scaling and rotation. | |
*/ | |
@Composable | |
fun ConvexPieChart( | |
modifier: Modifier, | |
data: List<PieChartData>, | |
startAngle: Float = -90f, | |
rotationsCount: Int = 4, | |
pieSliceStyle: ConvexStyle = ConvexStyle(), | |
animationSpec: AnimationSpec<Float> = tween(1_000, easing = LinearOutSlowInEasing) | |
) { | |
val totalValuesSum = remember(data) { data.sumOf(PieChartData::value) } | |
val pieChartScale = remember { Animatable(0f) } | |
val pieChartRotation = remember { Animatable(0f) } | |
LaunchedEffect(Unit) { | |
launch { | |
pieChartScale.animateTo(1f, animationSpec) | |
} | |
launch { | |
pieChartRotation.animateTo(360f * rotationsCount, animationSpec) | |
} | |
} | |
Canvas( | |
modifier | |
.aspectRatio(1f) | |
.scale(pieChartScale.value) | |
.rotate(pieChartRotation.value) | |
) { | |
var lastValue = startAngle | |
data.forEach { chartData -> | |
val pieSweepAngle = 360f * (chartData.value.toFloat() / totalValuesSum.toFloat()) | |
drawConvexArc( | |
color = chartData.color, | |
startAngle = lastValue, | |
sweepAngle = pieSweepAngle, | |
style = pieSliceStyle, | |
useCenter = true | |
) | |
lastValue += pieSweepAngle | |
} | |
} | |
} | |
private fun DrawScope.drawConvexArc( | |
color: Color, | |
startAngle: Float, | |
sweepAngle: Float, | |
useCenter: Boolean, | |
style: ConvexStyle, | |
) = drawIntoCanvas { canvas -> | |
val rect = this.size.toRect() | |
val paint = Paint() | |
paint.color = color | |
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint) | |
fun drawShadowArc(offsetX: Float, offsetY: Float, shadowColor: Color) { | |
val shadowPaint = Paint() | |
shadowPaint.color = shadowColor | |
canvas.saveLayer(rect, shadowPaint) | |
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, shadowPaint) | |
shadowPaint.asFrameworkPaint().apply { | |
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) | |
maskFilter = BlurMaskFilter(style.blur.toPx(), BlurMaskFilter.Blur.NORMAL) | |
} | |
shadowPaint.color = Color.Black | |
canvas.translate(offsetX, offsetY) | |
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, shadowPaint) | |
canvas.restore() | |
} | |
val offsetPx = style.offset.toPx() | |
drawShadowArc(-offsetPx, -offsetPx, style.shadowColor) | |
drawShadowArc(offsetPx, offsetPx, style.glareColor) | |
} |
This file contains 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
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.unit.Dp | |
import androidx.compose.ui.unit.dp | |
data class ConvexStyle( | |
val blur: Dp = 5.dp, | |
val offset: Dp = 4.dp, | |
val glareColor: Color = Color.White.copy(0.48f), | |
val shadowColor: Color = Color.Black.copy(0.48f) | |
) |
This file contains 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
import androidx.compose.ui.graphics.Color | |
data class PieChartData( | |
val label: String, | |
val value: Int, | |
val color: Color | |
) |
This file contains 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
import androidx.compose.foundation.background | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.BoxScope | |
import androidx.compose.foundation.layout.aspectRatio | |
import androidx.compose.foundation.layout.padding | |
import androidx.compose.foundation.shape.CircleShape | |
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.unit.Dp | |
import androidx.compose.ui.unit.dp | |
@Composable | |
fun PieChartPanel( | |
modifier: Modifier, | |
platesColor: Color = Color(0xFFD5F3FF), | |
platesGap: Dp = 32.dp, | |
style: ConvexStyle = ConvexStyle( | |
blur = 12.dp, | |
offset = 8.dp, | |
glareColor = Color.White.copy(0.32f), | |
shadowColor = Color.Black.copy(0.32f) | |
), | |
content: @Composable BoxScope.() -> Unit | |
) { | |
Box( | |
modifier = modifier | |
.aspectRatio(1f) | |
.innerShadow(CircleShape, style.glareColor, style.blur, -style.offset, -style.offset) | |
.innerShadow(CircleShape, style.shadowColor, style.blur, style.offset, style.offset) | |
.dropShadow(CircleShape, style.glareColor, style.blur, -style.offset, -style.offset) | |
.dropShadow(CircleShape, style.shadowColor, style.blur, style.offset, style.offset) | |
.background(platesColor, CircleShape), | |
contentAlignment = Alignment.Center | |
) { | |
Box( | |
modifier = Modifier | |
.matchParentSize() | |
.padding(platesGap) | |
.dropShadow(CircleShape, style.glareColor, style.blur, -style.offset, -style.offset) | |
.dropShadow(CircleShape, style.shadowColor, style.blur, style.offset, style.offset) | |
.background(platesColor, CircleShape), | |
contentAlignment = Alignment.Center, | |
content = content | |
) | |
} | |
} |
This file contains 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
import androidx.compose.animation.core.Animatable | |
import androidx.compose.animation.core.AnimationSpec | |
import androidx.compose.animation.core.FastOutSlowInEasing | |
import androidx.compose.animation.core.VectorConverter | |
import androidx.compose.animation.core.tween | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.LaunchedEffect | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.unit.sp | |
@Composable | |
fun TotalView( | |
total: Int, | |
modifier: Modifier = Modifier, | |
animationSpec: AnimationSpec<Int> = | |
tween(1000, easing = FastOutSlowInEasing) | |
) { | |
val totalToDisplay = remember { | |
Animatable(initialValue = 0, typeConverter = Int.VectorConverter) | |
} | |
LaunchedEffect(total) { | |
totalToDisplay.animateTo(total, animationSpec) | |
} | |
Column( | |
modifier = modifier, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
Text( | |
text = "Total value", | |
fontSize = 14.sp, | |
color = Color(0xFF464646) | |
) | |
Text( | |
text = "${totalToDisplay.value}$", | |
fontSize = 18.sp, | |
fontWeight = FontWeight.Medium, | |
color = Color(0xFF010203) | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment