Charts
Chartsanimated

Box plot

A box-and-whisker plot of quartiles and medians that wipes in across categories.

Installation

caveui components are copy-paste Jetpack Compose built entirely on Material 3 — there's no caveui dependency to add. Make sure Material 3 is on your classpath (it ships with the Compose BOM), then copy the Usage snippet below into your project.

kotlin
// build.gradle.kts (module)
dependencies {
    implementation(platform("androidx.compose:compose-bom:2025.06.00"))
    implementation("androidx.compose.material3:material3")
}

Usage

kotlin
data class BoxStat(val min: Float, val q1: Float, val med: Float, val q3: Float, val max: Float)

@Composable
fun BoxPlot(
    boxes: List<BoxStat> = listOf(
        BoxStat(0.2f, 0.35f, 0.5f, 0.65f, 0.85f),
        BoxStat(0.3f, 0.45f, 0.55f, 0.7f, 0.9f),
        BoxStat(0.15f, 0.3f, 0.42f, 0.55f, 0.75f),
        BoxStat(0.35f, 0.5f, 0.62f, 0.78f, 0.95f),
    ),
    accent: Color = Color(0xFF6366F1),
    modifier: Modifier = Modifier,
) {
    val reveal = remember { Animatable(0f) }
    LaunchedEffect(Unit) { reveal.animateTo(1f, tween(900, easing = EaseOutCubic)) }
    Canvas(modifier.fillMaxWidth().height(130.dp)) {
        val slot = size.width / boxes.size
        val bw = slot * 0.46f
        fun y(v: Float) = size.height * (1f - v)
        clipRect(right = size.width * reveal.value) {
            boxes.forEachIndexed { i, b ->
                val cx = i * slot + slot / 2
                drawLine(accent, Offset(cx, y(b.max)), Offset(cx, y(b.min)), 1.5.dp.toPx())
                drawLine(accent, Offset(cx - bw / 3, y(b.max)), Offset(cx + bw / 3, y(b.max)), 1.5.dp.toPx())
                drawLine(accent, Offset(cx - bw / 3, y(b.min)), Offset(cx + bw / 3, y(b.min)), 1.5.dp.toPx())
                val top = y(b.q3)
                val h = y(b.q1) - top
                drawRoundRect(accent.copy(alpha = 0.22f), Offset(cx - bw / 2, top), Size(bw, h), CornerRadius(3.dp.toPx()))
                drawRoundRect(accent, Offset(cx - bw / 2, top), Size(bw, h), CornerRadius(3.dp.toPx()), style = Stroke(1.5.dp.toPx()))
                drawLine(accent, Offset(cx - bw / 2, y(b.med)), Offset(cx + bw / 2, y(b.med)), 2.dp.toPx())
            }
        }
    }
}