charty

CircularProgressIndicator#

Best for one or more concentric progress rings — activity rings, goal completion, multi-metric progress.

CircularProgressIndicator

Kotlin
CircularProgressIndicator(    rings = {        listOf(            CircularRingData(                label = "Steps",                progress = 7500f,                maxValue = 10000f,                color = ChartyColor.Solid(Color(0xFFE91E63)),            ),            CircularRingData(                label = "Calories",                progress = 420f,                maxValue = 600f,                color = ChartyColor.Solid(Color(0xFF6650A4)),            ),            CircularRingData(                label = "Active Minutes",                progress = 35f,                maxValue = 60f,                color = ChartyColor.Solid(Color(0xFF00BCD4)),            ),        )    },    modifier = Modifier.size(280.dp),    config = CircularProgressConfig(        animation = Animation.Default,        gapBetweenRings = 10f,        strokeCap = StrokeCap.Round,    ),    onRingClick = { ring, index -> println("Tapped ${ring.label} (index $index)") },    centerContent = {        Column(horizontalAlignment = Alignment.CenterHorizontally) {            Text(text = "Activity", style = MaterialTheme.typography.labelMedium)            Text(text = "Today", style = MaterialTheme.typography.bodySmall)        }    },    accessibilityDescription = "Daily activity rings: Steps 75%, Calories 70%, Active Minutes 58%",)

Note the parameter name: rings, not data. onRingClick receives (ring: CircularRingData, index: Int).

Rings are drawn concentrically from outermost (index 0) inward. CircularRingData requires a non-blank label, non-negative progress, positive maxValue, and a color: ChartyColor. Its helpers — calculatePercentage(), calculateSweepAngle(), isComplete(), withClampedProgress() — all clamp progress into [0, maxValue], so an overshooting ring never draws past a full turn.

Each ring may also carry a backgroundColor (defaults to its own colour at 20% alpha), plus shadowColor and shadowRadius for use with enableShadows.

Centre content#

Kotlin
centerContent = {    Text(text = "78%", style = MaterialTheme.typography.headlineMedium)}

centerContent is a BoxScope composable rendered centred over the rings. When it is null and config.showCenterText is true, the indicator instead draws the first (outermost) ring's percentage as text — not its label, and not the innermost ring.

Kotlin
config = CircularProgressConfig(    showCenterText = true,    centerTextStyle = TextStyle(fontSize = 28.sp, fontWeight = FontWeight.Bold),)

Rotation#

rotationEnabled starts a continuous automatic rotation of the whole indicator, looping every rotationDurationMs. It is a decorative animation, not a user gesture — there is no drag-to-rotate.

Kotlin
config = CircularProgressConfig(    rotationEnabled = true,    rotationDurationMs = 4000,)

Geometry#

Kotlin
config = CircularProgressConfig(    startAngleDegrees = -90f,    ringDirection = RingDirection.COUNTER_CLOCKWISE,    gapBetweenRings = 12f,    centerHoleRatio = 0.3f,    paddingDp = 24f,)

centerHoleRatio reserves an untouched hole at the middle (0f..0.5f) and also shrinks the tappable area accordingly.

Accessibility#

The indicator attaches a generated summary ("Circular progress indicator, 3 rings. Steps: 75%. Calories: 70%. …"). Override it with accessibilityDescription, or pass an empty string to suppress it.

CircularProgressConfig#

PropertyTypeDefaultDescription
gapBetweenRingsFloat8fGap between concentric rings; non-negative
startAngleDegreesFloat-90fAngle progress starts from (12 o'clock)
ringDirectionRingDirectionCLOCKWISECLOCKWISE or COUNTER_CLOCKWISE
strokeCapStrokeCapStrokeCap.RoundCap of each ring's stroke
animationAnimationAnimation.DefaultProgress sweep-in animation
enableShadowsBooleanfalseDraws a ring's shadow when it sets shadowColor and shadowRadius
centerHoleRatioFloat0fUntouched centre hole; 0f..0.5f
rotationEnabledBooleanfalseContinuous automatic rotation
rotationDurationMsInt3000Period of one rotation; must be positive
interactionEnabledBooleantrueEnables the tap handling behind onRingClick
showCenterTextBooleanfalseDraws the outermost ring's percentage; ignored when centerContent is set
paddingDpFloat16fPadding around the whole indicator; non-negative
centerTextStyleTextStyle24 sp, bold, blackStyle of that centre text

Limitations#