A Kotlin Multiplatform chart library built with Jetpack Compose.
https://charts.hdcode.dev/demo
https://charts.hdcode.dev/demo/snapshot/
https://charts.hdcode.dev/playground
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}Use the umbrella artifact when you want all chart types with the simplest setup.
commonMain.dependencies {
implementation("io.github.hdcharts:charts:<version>")
}Use independent modules when you want only specific chart types and smaller dependency footprint.
commonMain.dependencies {
implementation("io.github.hdcharts:line:<version>")
implementation("io.github.hdcharts:pie:<version>")
implementation("io.github.hdcharts:bar:<version>")
implementation("io.github.hdcharts:histogram:<version>")
implementation("io.github.hdcharts:stacked-bar:<version>")
implementation("io.github.hdcharts:stacked-area:<version>")
implementation("io.github.hdcharts:radar:<version>")
}Use BOM for version alignment where Gradle platforms are supported.
For KMP commonMain, keep explicit versions as shown above.
dependencies {
implementation(platform("io.github.hdcharts:bom:<version>"))
implementation("io.github.hdcharts:line")
implementation("io.github.hdcharts:pie")
implementation("io.github.hdcharts:bar")
implementation("io.github.hdcharts:histogram")
implementation("io.github.hdcharts:stacked-bar")
implementation("io.github.hdcharts:stacked-area")
implementation("io.github.hdcharts:radar")
}@Composable
fun BasicLineChart() {
val values = listOf(42.0, 38.0, 45.0, 51.0, 47.0, 54.0, 49.0)
val labels = listOf("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
val dataSet = values.toChartData(
categories = labels,
)
LineChart(
data = dataSet,
modifier = Modifier
.fillMaxWidth()
.height(260.dp),
)
}The chart modifier controls its size and placement. Surrounding chrome stays
with the consumer:
Card {
LineChart(
data = dataSet,
modifier = Modifier
.fillMaxWidth()
.height(260.dp),
)
}The chart composable's modifier is the only sizing control. A bounded modifier becomes a
rectangular plot; a one-axis bounded modifier derives a square fallback; a fully unbounded
modifier falls back to a 200.dp default.
LineChart(
data = dataSet,
modifier = Modifier
.fillMaxWidth()
.height(260.dp),
)Background, shadow, shape, and outer spacing are not applied by the chart. Wrap the chart in any Compose surface to add chrome.
Card(modifier = Modifier.padding(16.dp)) {
LineChart(
data = dataSet,
modifier = Modifier.fillMaxWidth().height(260.dp),
)
}ChartContainerDefaults.style() returns a ChartContainerStyle with styleTitle and
contentPadding. Set contentPadding to change the internal spacing reserved for axes,
title, and legend.
LineChart(
data = dataSet,
modifier = Modifier.fillMaxWidth().height(260.dp),
style = LineChartDefaults.style(
chartContainerStyle = ChartContainerDefaults.style(contentPadding = 8.dp),
),
)Each chart module exposes its own *Defaults.style() factory. Use named arguments to customize
only the parts you care about. The example below customizes line color, axis, points, and zoom
controls; everything else keeps its default.
LineChart(
data = dataSet,
title = "Daily Support Tickets",
modifier = Modifier.fillMaxWidth().height(260.dp),
style = LineChartDefaults.style(
chartContainerStyle = ChartContainerDefaults.style(),
line = LineVisualStyle(
color = Color(0xFF1E88E5),
alpha = 1f,
colors = emptyList(),
strokeWidth = 3.dp,
bezier = true,
),
points = LinePointStyle(color = Color(0xFF1E88E5), size = 6.dp, visible = true),
axis = LineAxisStyle(
visible = true,
color = Color.Gray,
lineWidth = 1.dp,
yLabels = AxisLabelStyle(visible = true, color = Color.Gray, size = 12.sp, count = 5),
xLabels = AxisLabelStyle(visible = true, color = Color.Gray, size = 12.sp, count = 7),
),
zoomControlsVisible = true,
),
)By default the Y-axis is derived from the data, which can make small fluctuations look
exaggerated when the axis doesn't start at zero. Set range on *Defaults.style() to pin
min, max, or both, independently of one another; whichever bound you leave null keeps
deriving from the data.
LineChart(
data = dataSet,
modifier = Modifier.fillMaxWidth().height(260.dp),
style = LineChartDefaults.style(
range = LineChartDefaults.range(min = 0.0, max = 100.0),
),
)BarChart exposes the same range block on BarChartDefaults.style().
Use rememberChartSelection() to hoist selection. The chart calls back into it; you read
selectedIndex to drive external UI such as legends or summary cards.
val selection = rememberChartSelection()
val values = listOf(42.0, 38.0, 45.0, 51.0, 47.0, 54.0, 49.0)
LineChart(
data = values.toChartData(),
modifier = Modifier.fillMaxWidth().height(260.dp),
selection = selection,
)
val selected = values.getOrNull(selection.selectedIndex ?: 0)
Text("Selected: $selected")Compose the chart with any layout, including Row, Column, LazyColumn, or BoxWithConstraints.
A bounded modifier is always honored; an unbounded one falls back to the documented defaults.
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
LineChart(
data = lineData,
modifier = Modifier.fillMaxWidth().height(220.dp),
)
Spacer(modifier = Modifier.height(16.dp))
BarChart(
data = barData,
modifier = Modifier.fillMaxWidth().height(220.dp),
)
}animateOnStart controls the initial reveal animation; interactionEnabled disables gestures,
selection, and zoom controls together. Disable interaction to embed a chart inside a tappable
card without conflicts.
LineChart(
data = dataSet,
modifier = Modifier.fillMaxWidth().height(260.dp),
animateOnStart = true,
interactionEnabled = false,
)See CONTRIBUTING.md for contribution guidelines.

