Line Chart
Overview
LineChart shows how one or more numeric series change across ordered categories. It derives the
vertical range from the data by default and morphs between updates that keep the same number of
series and points. Use LiveLineChart for a continuously advancing window where new points slide
into view.
implementation("io.github.hdcharts:line:3.1.0-SNAPSHOT")
Recording source: LineExample.kt.
Basic Usage
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.github.hdcharts.core.model.toChartData
import io.github.hdcharts.line.LineChart
internal data class DailyMeasurement(
val day: String,
val temperatureCelsius: Double,
) {
companion object {
const val SERIES_NAME = "Temperature"
val sample =
listOf(
DailyMeasurement("Mon", 18.0),
DailyMeasurement("Tue", 21.0),
DailyMeasurement("Wed", 19.0),
DailyMeasurement("Thu", 24.0),
DailyMeasurement("Fri", 22.0),
)
}
}
@Composable
fun ShowDailyTemperatures(modifier: Modifier = Modifier) {
val measurements = DailyMeasurement.sample
val data =
measurements.toChartData(
value = { it.temperatureCelsius },
category = { it.day },
seriesName = DailyMeasurement.SERIES_NAME,
)
LineChart(
data = data,
modifier = modifier,
title = "Daily High Temperature (°C)",
)
}
Capabilities
Multiple Series
Use one named value list per series and provide one shared category list in matching order. Each series must have the same number of values, and the category count must match that length. The chart uses its line palette to distinguish series and shows their names in the legend. This focused example reuses the imports from Basic Usage.

Recording source: MultiLineExample.kt.
@Composable
fun ShowWeeklyRevenueByChannel() {
val weeks = listOf("W1", "W2", "W3", "W4", "W5")
val data =
listOf(
"Web Store" to listOf(180.0, 310.0, 420.0, 560.0, 640.0),
"Mobile App" to listOf(120.0, 240.0, 350.0, 430.0, 520.0),
"Partner Sales" to listOf(60.0, 130.0, 190.0, 240.0, 300.0),
).toChartData(categories = weeks)
LineChart(
data = data,
modifier = Modifier.fillMaxWidth().height(300.dp),
title = "Weekly Revenue by Channel",
)
}Fixed Value Range
The default Y-axis range follows the minimum and maximum values in the data. Set either or both
bounds to compare changing measurements on a consistent scale. A null bound continues to follow
the data; this is useful when the measurements are meaningful relative to their observed values,
without implying a zero baseline.

Recording source: LineWithRangeExample.kt.
@Composable
fun CompareTemperatures(data: ChartData) {
LineChart(
data = data,
style = LineChartDefaults.style(range = LineChartDefaults.range(min = 0.0, max = 40.0)),
)
}Selection and Interaction
LineChart supports user selection and programmatic selection through ChartSelection. Its
selectedIndex is the selected source X index shared by every series; in a multi-series chart,
the selection represents that category across the aligned series. interactionEnabled = false
disables user controls while programmatic selection can still be displayed. This excerpt reuses
data from Basic Usage.
@Composable
fun SelectTemperature(
data: ChartData,
modifier: Modifier = Modifier,
selection: ChartSelection = rememberChartSelection(),
) {
Column(modifier = modifier) {
LineChart(
data = data,
modifier = Modifier.weight(1f).fillMaxWidth(),
selection = selection,
)
Text("Selected point index: ${selection.selectedIndex ?: "none"}")
}
}
Gradients
Use a line gradient to color the line across the plot. This example reuses data from Basic Usage.
For gradient directions, spans, and stops, see the shared Gradients guide.

Recording source: LineGradientExample.kt.
@Composable
fun GradientTemperature(data: ChartData) {
val gradient =
ChartGradients.vertical(
colors = listOf(Color(0xFF2563EB), Color(0xFFF59E0B), Color(0xFFDC2626)),
span = GradientSpan.Plot,
)
LineChart(
data = data,
style = LineChartDefaults.style(line = LineChartDefaults.line(gradient = gradient)),
)
}Morphing
When new LineChart data keeps the same series count and the same number of points in each series,
the chart morphs between the old and new line shapes. A change to either count redraws the chart
instead. Keep categories aligned across updates so each point continues to represent the same
position in the series. Morphing transitions between datasets; it does not advance a sliding
timeline.

Recording source: MorphingLineExample.kt.
@Composable
fun MorphWeeklyMeasurements() {
val weeks =
listOf(
listOf(42.0, 38.0, 45.0, 51.0, 47.0),
listOf(46.0, 52.0, 48.0, 44.0, 50.0),
)
var weekIndex by remember { mutableIntStateOf(0) }
LaunchedEffect(Unit) {
while (true) {
delay(2_000.milliseconds)
weekIndex = (weekIndex + 1) % weeks.size
}
}
val data =
weeks[weekIndex].toChartData(
categories = listOf("Mon", "Tue", "Wed", "Thu", "Fri"),
seriesName = "Daily Measurements",
)
LineChart(
data = data,
title = "Daily Measurements",
animateOnStart = false,
)
}Live Line Chart
LiveLineChart displays a rolling window of one or more aligned series. When each update removes
the oldest point and appends a new point, the plot and its category labels shift together, while
the Y-axis range follows values in the current window. Other updates with the same series and
point counts morph over shiftDuration; changing either count redraws immediately.
Update data at a cadence close to shiftDuration so each shift can finish as the next point
arrives. The example uses a fixed-size window, a deterministic repeating reading sequence, and a
500 ms update cadence. LiveLineChart is display-only and ignores gestures; it does not expose
selection or interactionEnabled parameters. For multiple live series, supply equally sized
named series and shared categories through the same ChartData structure used by LineChart.

Recording source: LiveLineExample.kt.
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.github.hdcharts.core.model.toChartData
import io.github.hdcharts.line.LiveLineChart
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.milliseconds
@Composable
fun ShowLiveSensorReadings() {
val readings = listOf(18.0, 18.5, 19.0, 18.7, 19.4, 20.0, 19.6, 19.1)
val windowSize = 12
var firstSample by remember { mutableIntStateOf(0) }
LaunchedEffect(Unit) {
while (true) {
delay(500.milliseconds)
firstSample++
}
}
val sampleNumbers = firstSample until (firstSample + windowSize)
val values = sampleNumbers.map { sample -> readings[sample % readings.size] }
val categories = sampleNumbers.map { "#${it + 1}" }
val data = values.toChartData(categories = categories, seriesName = "Sensor")
LiveLineChart(
data = data,
modifier = Modifier.fillMaxWidth().height(300.dp),
title = "Live Sensor Reading",
shiftDuration = 500.milliseconds,
animateOnStart = false,
)
}The live gradient GIF demonstrates colors progressing from older to newer points as the window slides.

Recording source: LiveLineGradientExample.kt.