Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

## [Unreleased]

### Changed
- Line/Bar/Donut/Pie/Radar 차트 렌더링 성능 개선 — Canvas draw 중 선택 상태 변경과 콜백 호출을 제거하고, 데이터/크기 기반 파생값을 캐시해 반복 할당을 줄임
- 선택 콜백 호출 안정화 — 동일한 터치 선택이 redraw/recomposition 되는 동안 `onSelectionChanged` 및 레거시 선택 콜백이 반복 호출되지 않도록 변경

## [1.3.0] - 2026-04-10

### Added
Expand Down
7 changes: 7 additions & 0 deletions CODE_QUALITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ internal fun resolveGridLineColor(color: Color, isDark: Boolean): Color =
- 싱글톤(`object`), 캐싱, 지연 초기화(`lazy`)를 적절히 활용한다.
- 성능이 중요한 계산에서는 기본 자료형 배열(`FloatArray`, `IntArray`)을 사용한다.

### Canvas 렌더링 최적화

- `Canvas`/draw 블록 안에서는 상태 변경, 외부 콜백 호출, 선택 이벤트 방출을 하지 않는다.
- 터치 hit-test 결과와 접근성 선택 설명은 draw 밖에서 파생 상태로 계산하고, 콜백은 선택값 변경 시점에만 호출한다.
- 데이터/크기/style에만 의존하는 좌표, `Path`, 라벨 목록, 누적값은 `remember`, `lazy`, `drawWithCache` 등으로 재사용한다.
- draw 중 반복 생성되는 `Paint`, `PathEffect`, 임시 컬렉션은 호출 단위 캐시나 더 직접적인 draw API로 줄인다.

### 컬렉션 처리 최적화

- 여러 단계의 컬렉션 처리는 `Sequence`를 고려한다 (지연 처리, 최소 연산).
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,12 @@ GaugeChart(
기존의 `onPointSelected`/`onBarSelected`/`onSliceSelected`/`onAxisSelected`
콜백은 소스 호환을 위해 유지되지만 v2.0에서 제거될 예정입니다.

### 성능 팁

- 차트 `data`와 `style`은 가능하면 `remember` 또는 상위 상태로 안정적으로 유지하세요. 같은 값을 매 recomposition마다 새 객체로 만들면 차트가 다시 계산될 수 있습니다.
- 데이터 포인트가 많다면 `showDots`, slice/radar label, 긴 애니메이션을 필요한 화면에서만 켜는 것이 좋습니다.
- `Modifier.chartCaptureModifier()`는 offscreen 기록 비용이 있으므로 실제 이미지 내보내기가 필요한 차트에만 적용하세요.

### 범례

```kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package com.inseong.composechart
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.test.junit4.createComposeRule
Expand All @@ -21,6 +24,7 @@ import com.inseong.composechart.gauge.GaugeChart
import com.inseong.composechart.line.LineChart
import com.inseong.composechart.radar.RadarChart
import com.inseong.composechart.style.DonutChartStyle
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -65,6 +69,39 @@ class SelectionCallbackTest {
composeTestRule.mainClock.advanceTimeBy(500)
}

@Test
fun lineChart_onSelectionChanged_sameTouchRecomposition_doesNotRepeat() {
var callbackCount = 0
var recompositionTick by mutableIntStateOf(0)

composeTestRule.setContent {
LineChart(
data = LineChartData.fromValues(values = listOf(10f, 25f, 18f)),
modifier = wideModifier,
accessibilityLabel = "선 차트 $recompositionTick",
onSelectionChanged = { callbackCount++ },
)
}
composeTestRule.waitForIdle()
composeTestRule.mainClock.advanceTimeBy(1000)
composeTestRule
.onNodeWithContentDescription("선 차트 0, 1개 시리즈, 3개 데이터 포인트")
.performTouchInput {
down(Offset(x = 1f, y = centerY))
}
composeTestRule.mainClock.advanceTimeBy(500)
composeTestRule.runOnIdle {
assertEquals(1, callbackCount)
recompositionTick = 1
}
composeTestRule.waitForIdle()
composeTestRule.runOnIdle {
assertEquals("Selection callback should not repeat for the same touch selection", 1, callbackCount)
}
composeTestRule.onRoot().performTouchInput { up() }
composeTestRule.mainClock.advanceTimeBy(500)
}

@Test
fun barChart_onSelectionChanged_emitsBarSelection() {
var selection: ChartSelection.Bar? = null
Expand Down
Loading