Charts
Chartsanimated

Sankey

A Sankey diagram whose weighted flow ribbons stream between two node columns.

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
@Composable
fun Sankey(
    flows: List<Triple<Int, Int, Float>> = listOf(
        Triple(0, 0, 0.5f), Triple(0, 1, 0.3f),
        Triple(1, 1, 0.4f), Triple(1, 2, 0.25f),
    ),
    modifier: Modifier = Modifier,
) {
    val reveal by animateFloatAsState(1f, tween(1000, easing = EaseOutCubic), label = "r")
    val palette = listOf(Color(0xFF6366F1), Color(0xFF10B981))
    Canvas(modifier.fillMaxWidth().height(130.dp)) {
        val lx = 12.dp.toPx()
        val rx = size.width - 12.dp.toPx()
        clipRect(right = lx + (rx - lx) * reveal) {
            flows.forEachIndexed { i, (src, dst, w) ->
                val y0 = size.height * (0.25f + src * 0.3f)
                val y1 = size.height * (0.2f + dst * 0.28f)
                val ribbon = Path().apply {
                    moveTo(lx, y0)
                    cubicTo((lx + rx) / 2, y0, (lx + rx) / 2, y1, rx, y1)
                    lineTo(rx, y1 + size.height * w * 0.4f)
                    cubicTo((lx + rx) / 2, y1 + size.height * w * 0.4f, (lx + rx) / 2, y0 + size.height * w * 0.4f, lx, y0 + size.height * w * 0.4f)
                    close()
                }
                drawPath(ribbon, palette[src % 2].copy(alpha = 0.4f))
            }
        }
        listOf(lx, rx).forEach { x ->
            drawRoundRect(Color(0xFF111827), Offset(x - 5.dp.toPx(), 0f), Size(10.dp.toPx(), size.height), CornerRadius(3.dp.toPx()))
        }
    }
}