Jetpack Compose์์ ์ฌ์ฉํ ์ ์๋ ๊ฐ๋ณ๊ณ ๋ ๋ฆฝ์ ์ธ ์ฐจํธ UI ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
๋ฐ์ดํฐ๋ง ๋๊ธฐ๋ฉด ๋์ ๋๋ค.
| Line Chart | Bar Chart | Donut Chart |
![]() |
![]() |
![]() |
| Gauge Chart | Radar Chart | Pie Chart |
![]() |
![]() |
![]() |
Color.Unspecified + isSystemInDarkTheme() ํจํด์ผ๋ก ๊ทธ๋ฆฌ๋/์ถ/๊ฒ์ด์ง ํธ๋/
๋ ์ด๋ ์น ์์์ด ๋คํฌ ๋ชจ๋์ ์๋ ๋์ํฉ๋๋ค. ๋คํฌ ๋ชจ๋ ์ ์ฉ ์คํ์ผ ์ค์ ์
ํ์ํ์ง ์์ต๋๋ค.
๐ท ๋คํฌ ๋ชจ๋ ์คํฌ๋ฆฐ์ท ์ถ๊ฐ ์์ :
screenshots/{chart}-dark.png
๋ฐ์ดํฐ ์๊ฐํ๋ ์ฑ์ ํต์ฌ ๊ธฐ๋ฅ์ด์ง๋ง, Compose์์ ๋ฐ๋ก ์ธ ์ ์๋ ๊ฐ๋ฒผ์ด ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ถ์กฑํ์ต๋๋ค.
๊ธฐ์กด ์ ๊ทผ ๋ฐฉ์๋ค์ Material3์ ์์กดํ๊ฑฐ๋, ์ค์ ์ด ๋ณต์กํ๊ฑฐ๋, ํน์ ํ๋ก์ ํธ์ ๊ฐํ๊ฒ ๊ฒฐํฉ๋์ด ์์์ต๋๋ค. compose-chart๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ง๋ค์์ต๋๋ค:
- UI ์์กด์ฑ ์ ๋ก โ Compose Foundation๋ง ์ฌ์ฉํฉ๋๋ค. Material3๊ฐ ํ์ ์์ต๋๋ค.
- 6๊ฐ์ง ์ฐจํธ โ Line, Bar, Donut, Gauge, Radar, Pie๋ฅผ ํ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก.
- ์์ ํ ์ปค์คํฐ๋ง์ด์ง โ ์์, ์ถ, ์ ๋๋ฉ์ด์ , ํฐ์น ์ํธ์์ฉ๊น์ง ๋ชจ๋ ๊ฒ์ ํ๋ผ๋ฏธํฐ๋ก ์ค์ ํ ์ ์๊ณ , ๊ธฐ๋ณธ๊ฐ๋ ์ ์กํ ์์ต๋๋ค.
- ์์ ํ ์ ๋ ฅ ์ฒ๋ฆฌ โ NaN, Infinity, ์์, ๋น ๋ฐ์ดํฐ๋ ํฌ๋์ ์์ด ์์ ํ๊ฒ ์ฒ๋ฆฌํฉ๋๋ค.
- ์ ๊ทผ์ฑ ๊ธฐ๋ณธ ์ง์ โ ์ฐจํธ ์์ฝ๊ณผ ์ ํ ์ํ๋ฅผ ์คํฌ๋ฆฐ๋ฆฌ๋์ฉ semantics๋ก ์ ๊ณตํฉ๋๋ค.
ํ์ฌ ๋ฌธ์ ๊ธฐ์ค ์ต์ ๋ฆด๋ฆฌ์ค๋ v1.3.1์ ๋๋ค.
// build.gradle.kts
dependencies {
implementation("io.github.ois0886:compose-chart:1.3.1")
}LineChart(
data = LineChartData.fromValues(
values = listOf(10f, 25f, 18f, 32f, 22f),
xLabels = listOf("1์", "2์", "3์", "4์", "5์"),
),
modifier = Modifier.fillMaxWidth().height(200.dp),
)BarChart(
data = BarChartData.simple(
values = listOf(30f, 45f, 25f, 60f),
labels = listOf("1๋ถ๊ธฐ", "2๋ถ๊ธฐ", "3๋ถ๊ธฐ", "4๋ถ๊ธฐ"),
),
modifier = Modifier.fillMaxWidth().height(200.dp),
)DonutChart(
data = DonutChartData(
slices = listOf(
DonutSlice(40f, "์๋น"),
DonutSlice(25f, "๊ตํต"),
DonutSlice(35f, "๊ธฐํ"),
),
),
modifier = Modifier.size(200.dp),
)GaugeChart(
data = GaugeChartData(value = 72f, maxValue = 100f, label = "์ ์"),
modifier = Modifier.size(180.dp),
)RadarChart(
data = RadarChartData.single(
values = listOf(80f, 65f, 90f, 70f, 85f),
axisLabels = listOf("STR", "DEX", "INT", "WIS", "CHA"),
),
modifier = Modifier.size(200.dp),
)PieChart(
data = DonutChartData.fromValues(30f, 25f, 45f),
modifier = Modifier.size(200.dp),
)README ์์ ์ฝ๋๋ ํ ์คํธ์์๋ ํจ๊ป ์ ์ง๋์ด, ๋ฌธ์ ์ค๋ํซ์ด ์ค์ API์ ์ด๊ธ๋์ง ์๋๋ก ๊ด๋ฆฌํฉ๋๋ค. v1.3.1์ ๊ณต๊ฐ API์ ์๊ฐ ๊ฒฐ๊ณผ๋ฅผ ์ ์งํ๋ฉด์ Line/Bar/Donut/Pie/Radar ๋ ๋๋ง hot path์ ๋ฐ๋ณต ๊ณ์ฐ๊ณผ ํ ๋น์ ์ค์ธ ์ฑ๋ฅ ๊ฐ์ ๋ฆด๋ฆฌ์ค์ ๋๋ค.
Line, Bar ์ฐจํธ์์ ํ์น ์ค๊ณผ ๋๋๊ทธ ํฌ์ ์ง์ํฉ๋๋ค. ๋๋ธํญ์ผ๋ก ์ด๊ธฐํ๋ฉ๋๋ค.
val zoomState = rememberChartZoomState()
LineChart(
data = data,
zoomState = zoomState,
)์ฐจํธ๋ฅผ ImageBitmap์ผ๋ก ๋ด๋ณด๋ผ ์ ์์ต๋๋ค. capture()๋ suspend ํจ์์ด๋ฏ๋ก
rememberCoroutineScope() ํน์ LaunchedEffect์์ ํธ์ถํฉ๋๋ค.
val captureState = rememberChartCaptureState()
val scope = rememberCoroutineScope()
Column {
LineChart(
data = data,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.chartCaptureModifier(captureState),
)
Button(onClick = {
scope.launch {
val bitmap: ImageBitmap = captureState.capture()
// bitmap.asAndroidBitmap()์ ํ์ผ๋ก ์ ์ฅํ๊ฑฐ๋ ๊ณต์ Intent์ ์ฒจ๋ถ
}
}) {
Text("์ด๋ฏธ์ง๋ก ์ ์ฅ")
}
}Modifier.chartCaptureModifier(captureState)๋ ์ด๋ ์ฐจํธ์๋ ๋์ผํ๊ฒ ์ ์ฉํ ์
์์ผ๋ฉฐ, ์ ๋๋ฉ์ด์
์ด ๋๋ ๋ค ์บก์ฒํ๋ฉด ์ต์ข
์ํ๊ฐ ๋ด๊ธด ์ด๋ฏธ์ง๊ฐ ๋ฐํ๋ฉ๋๋ค.
๋ฒ๋ก ํญ๋ชฉ์ ํญํ์ฌ ์๋ฆฌ์ฆ๋ฅผ ํ ๊ธํ ์ ์์ต๋๋ค.
val items = remember {
mutableStateListOf(
LegendItem(color = Color.Blue, label = "๋งค์ถ", enabled = true),
LegendItem(color = Color.Red, label = "๋น์ฉ", enabled = true),
)
}
ChartLegend(
items = items,
onItemClick = { index ->
items[index] = items[index].copy(enabled = !items[index].enabled)
},
)๋ฐ์ดํฐ ๊ฐ์ฒด๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์์ฑํ ์ ์์ต๋๋ค.
// ๊ทธ๋ฃน ๋ง๋ ์ฐจํธ
BarChartData.grouped(
seriesValues = listOf(listOf(10f, 20f), listOf(15f, 25f)),
labels = listOf("1์", "2์"),
)
// Map์์ ๋ฉํฐ ์๋ฆฌ์ฆ ๋ผ์ธ ์ฐจํธ
LineChartData.fromMap(
seriesMap = mapOf("๋งค์ถ" to listOf(10f, 20f), "๋น์ฉ" to listOf(5f, 15f)),
xLabels = listOf("Q1", "Q2"),
)
// ๊ฐ๋ง์ผ๋ก ๋๋ ์ฐจํธ
DonutChartData.fromValues(40f, 25f, 20f, 15f)LineChart(
data = data,
style = LineChartStyle(
axis = AxisStyle(
showYAxis = true,
yAxisMin = 0f, // ์ต์๊ฐ ๊ณ ์
yAxisMax = 100f, // ์ต๋๊ฐ ๊ณ ์
),
),
)์ถ, ํดํ, ๋ฒ๋ก, ๊ฒ์ด์ง ์ค์ ํ ์คํธ์ ํฐํธ ๊ตต๊ธฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
LineChart(
data = data,
style = LineChartStyle(
axis = AxisStyle(fontWeight = FontWeight.Bold),
tooltip = TooltipStyle(fontWeight = FontWeight.Medium),
),
)๊ธฐ๋ณธ ํ๋ ํธ(ChartDefaults.colors)๋ 6๊ฐ ์์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ, ๋ฉํฐ ์๋ฆฌ์ฆ ์ฐจํธ์์
์์๋๋ก ๋ฐฐ์ ๋๊ณ ์๋ฆฌ์ฆ ์๊ฐ 6๊ฐ๋ฅผ ๋์ผ๋ฉด ์ฒ์๋ถํฐ ๋ค์ ์ํํฉ๋๋ค.
| # | Name | HEX |
|---|---|---|
| 1 | Blue | #3182F6 |
| 2 | Green | #48BB78 |
| 3 | Orange | #ED8936 |
| 4 | Red | #E53E3E |
| 5 | Purple | #9F7AEA |
| 6 | Teal | #38B2AC |
// ์ปค์คํ
์์ ํ๋ ํธ
LineChart(
data = data,
colors = listOf(Color.Red, Color.Blue, Color.Green),
)
// ์๋ฆฌ์ฆ๋ณ ๊ฐ๋ณ ์์
LineChart(
data = LineChartData(
series = listOf(
LineSeries(points = points1, color = Color.Red),
LineSeries(points = points2, color = Color.Blue),
),
),
)LineChart(
data = data,
style = LineChartStyle(
curved = true, // ๊ณก์ /์ง์
showDots = true, // ๋ฐ์ดํฐ ํฌ์ธํธ ํ์
gradientFill = true, // ๊ทธ๋๋์ธํธ ์ฑ์ฐ๊ธฐ
lineWidth = 3.dp, // ์ ๋๊ป
animationDurationMs = 1000, // ์ ๋๋ฉ์ด์
์๊ฐ
axis = AxisStyle(
showYAxis = true,
yLabelCount = 5,
yAxisFormatter = { value -> "${value.toLong()}%" },
),
grid = GridStyle(
showHorizontalLines = true,
dashPattern = listOf(10f, 5f),
),
),
)onSelectionChanged๋ 6๊ฐ ์ฐจํธ์์ ๊ณตํต์ผ๋ก ์ ๊ณตํ๋ ์ฝ๋ฐฑ์ผ๋ก, ์ฐจํธ๋ณ
ChartSelection ํ์ ํ์
(Line/Bar/Donut/Radar/Gauge)์ ๋ฐฉ์ถํฉ๋๋ค.
LineChart(
data = data,
onSelectionChanged = { selection: ChartSelection.Line ->
println("์๋ฆฌ์ฆ ${selection.seriesIndex}, " +
"ํฌ์ธํธ ${selection.pointIndex}: ${selection.point.y}")
},
)
DonutChart(
data = data,
onSelectionChanged = { selection: ChartSelection.Donut ->
println("${selection.slice.label}: ${selection.slice.value}")
},
)
GaugeChart(
data = data,
onSelectionChanged = { selection: ChartSelection.Gauge ->
println("ํ์ฌ ๊ฐ ${selection.value}, ์งํ๋ฅ ${selection.ratio}")
},
)๊ธฐ์กด์ onPointSelected/onBarSelected/onSliceSelected/onAxisSelected
์ฝ๋ฐฑ์ ์์ค ํธํ์ ์ํด ์ ์ง๋์ง๋ง v2.0์์ ์ ๊ฑฐ๋ ์์ ์
๋๋ค. ์ ํ ์ฝ๋ฐฑ์
๋์ผํ ์ ํ ์ํ์ redraw/recomposition๋ง๋ค ๋ฐ๋ณต ํธ์ถ๋์ง ์๊ณ , ์ ํ ๊ฐ์ด ๋ฐ๋
๋ ํธ์ถ๋ฉ๋๋ค.
- v1.3.1๋ถํฐ ์ฐจํธ ๋ด๋ถ์์ ์ฃผ์ ํ์ ๋ฐ์ดํฐ์ draw ๋ฆฌ์์ค๋ฅผ ์บ์ํ์ง๋ง, ํธ์ถํ๋ ์ชฝ์์๋ ๋ถํ์ํ ์ ๊ฐ์ฒด ์์ฑ์ ์ค์ด๋ฉด ํจ๊ณผ๊ฐ ๋ ์์ ์ ์ ๋๋ค.
- ์ฐจํธ
data์style์ ๊ฐ๋ฅํ๋ฉดremember๋๋ ์์ ์ํ๋ก ์์ ์ ์ผ๋ก ์ ์งํ์ธ์. ๊ฐ์ ๊ฐ์ ๋งค recomposition๋ง๋ค ์ ๊ฐ์ฒด๋ก ๋ง๋ค๋ฉด ์ฐจํธ๊ฐ ๋ค์ ๊ณ์ฐ๋ ์ ์์ต๋๋ค. - ๋ฐ์ดํฐ ํฌ์ธํธ๊ฐ ๋ง๋ค๋ฉด
showDots, X์ถ/slice/radar label, ๊ธด ์ ๋๋ฉ์ด์ ์ ํ์ํ ํ๋ฉด์์๋ง ์ผ๋ ๊ฒ์ด ์ข์ต๋๋ค. - ๋๋์
LineChart๋ฐ์ดํฐ๋ ์๊ฐ์/์ธ๋ฑ์ค์์ฒ๋ผ x ๊ฐ์ด ์ค๋ฆ์ฐจ์์ด ๋๋๋ก ์ ๋ฌํ๋ฉด ํฐ์น ์ ํ ํ์์ด ๋ ๋น ๋ฅด๊ฒ ๋์ํฉ๋๋ค. ์ ๋ ฌ๋์ง ์์ ๋ฐ์ดํฐ๋ ๊ณ์ ์ง์๋ฉ๋๋ค. Modifier.chartCaptureModifier()๋ offscreen ๊ธฐ๋ก ๋น์ฉ์ด ์์ผ๋ฏ๋ก ์ค์ ์ด๋ฏธ์ง ๋ด๋ณด๋ด๊ธฐ๊ฐ ํ์ํ ์ฐจํธ์๋ง ์ ์ฉํ์ธ์.
Column {
LineChart(data = data, colors = colors)
ChartLegend(
items = data.series.mapIndexed { i, s ->
LegendItem(color = colors[i % colors.size], label = s.label)
},
)
}| ์ฐจํธ | Composable | ๋ฐ์ดํฐ ํด๋์ค | ์ฉ๋ |
|---|---|---|---|
| ์ ์ฐจํธ | LineChart |
LineChartData |
์๊ณ์ด, ์ถ์ธ |
| ๋ง๋ ์ฐจํธ | BarChart |
BarChartData |
๋น๊ต, ๋ถํฌ |
| ๋๋ ์ฐจํธ | DonutChart |
DonutChartData |
๋น์จ, ๊ตฌ์ฑ |
| ๊ฒ์ด์ง ์ฐจํธ | GaugeChart |
GaugeChartData |
์งํ๋, ๋ฌ์ฑ๋ฅ |
| ๋ ์ด๋ ์ฐจํธ | RadarChart |
RadarChartData |
๋ค์ถ ๋น๊ต |
| ํ์ด ์ฐจํธ | PieChart |
DonutChartData |
๋น์จ (๋๋์ ํน์ ์ผ์ด์ค) |
- Pure Compose Foundation โ Material3 ์์กด ์์ด Foundation์
Canvas,BasicText๋ง ์ฌ์ฉํ์ฌ, ์ด๋ค ๋์์ธ ์์คํ ์ ์ฐ๋ ํ๋ก์ ํธ์์๋ ์ถฉ๋ ์์ด ๋์ํฉ๋๋ค. - ์์ ํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ โ NaN, Infinity, ์์, ๋น ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ฐจํธ์์ ๋ฐฉ์ด์ ์ผ๋ก ์ฒ๋ฆฌํฉ๋๋ค.
safeX,safeYํจํด์ผ๋ก ์๋ชป๋ ์ ๋ ฅ์ด ํฌ๋์๋ฅผ ์ผ์ผํค์ง ์์ต๋๋ค. - ์์ ํจ์ & ํ
์คํธ โ ์ขํ ๊ณ์ฐ, ๋ฒ์ ๊ณ์ฐ, ํฐ์น ๊ฐ์ง ๋ก์ง์ด
internal/math/์ ์์ ํจ์๋ก ๋ถ๋ฆฌ๋์ด ์์ผ๋ฉฐ, JVM ์ ๋ ํ ์คํธ์ Compose UI ํ ์คํธ๊ฐ ์ ์ฒด ์ฐจํธ๋ฅผ ๊ฒ์ฆํฉ๋๋ค. - ๋ ๋๋ง ์ฑ๋ฅ โ Canvas draw ์ค side effect๋ฅผ ํผํ๊ณ , ๋ฐ์ดํฐ/ํฌ๊ธฐ ๊ธฐ๋ฐ ๋ ์ด์์๊ณผ ๊ณตํต draw ๋ฆฌ์์ค๋ฅผ ์บ์ํด animation redraw ๋น์ฉ์ ์ค์ ๋๋ค.
- ์ค & ํฌ โ
ChartZoomState๋ฅผ ํตํ ํ์น ์ค/๋๋๊ทธ ํฌ ์ง์. CanvaswithTransform์ผ๋ก ์ขํ ์ญ๋ณํ ์ฒ๋ฆฌ. - ์ด๋ฏธ์ง ์บก์ฒ โ
GraphicsLayerAPI๋ฅผ ํ์ฉํ์ฌ ์ฐจํธ๋ฅผImageBitmap์ผ๋ก ๋ด๋ณด๋ด๊ธฐ. - ํ
๋ง ์ธ์ โ
Color.Unspecifiedํจํด์ผ๋ก ๋คํฌ/๋ผ์ดํธ ํ ๋ง์ ๋ง๋ ๊ธฐ๋ณธ ์์์ ์๋ ์ ์ฉํฉ๋๋ค. - ์ ๊ทผ์ฑ ์ง์ โ ๊ฐ ์ฐจํธ์
semantics๋ก ์ฝํ ์ธ ์ค๋ช ์ ์ ๊ณตํ์ฌ ์คํฌ๋ฆฐ ๋ฆฌ๋(TalkBack)๋ฅผ ์ง์ํฉ๋๋ค.
- Min SDK 24 (Android 7.0)
- Jetpack Compose (Foundation)
Copyright 2026 Inseong
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.





