- TieredBuffer: replace MEAN binning with MIN_MAX (min+max per bin). Mathematically composable: MIN(bin_mins)=global_MIN, MAX(bin_maxes)=global_MAX. Eliminates tier-boundary visual artifacts. TIER1/TIER2 capacity doubled (2 samples/bin). - LodMode: remove MEAN. Only MIN_MAX and LTTB remain. MEAN introduced values never present in raw signal — disinformation for sensor data. - ChartConfig: default lodMode changed to MIN_MAX. - Version: 0.4.1-SNAPSHOT → 0.5.0-SNAPSHOT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| build.gradle.kts | ||
| README.md | ||
chart-realtime
KMP real-time line chart for Android. Renders multiple sensor signals at 200Hz+ without frame drops.
Features
- Multiple signals on one chart, added/removed dynamically
- FIFO circular buffer — lock-free writes at 200Hz+, safe render reads at 60fps
- Level-of-Detail decimation — renders O(pixels) regardless of buffer size (1h+ of data)
- X axis: scrolling window, seconds from configurable t0
- Y axis: auto-fit or fixed range
- Material3 default theme, fully customizable via
ChartTheme
Installation
// settings.gradle.kts
include(":chart-realtime")
Or Maven local:
./gradlew :chart-realtime:publishToMavenLocal
// build.gradle.kts
implementation("eu.henesis:chart-realtime:0.1.0-SNAPSHOT")
Quick start (3 lines)
val state = remember { RealtimeChartState(ChartConfig()) }
LaunchedEffect(state) {
state.addSignal("ecg", SignalConfig(color = Color.Green))
sensorFlow.collect { v -> state.push("ecg", System.currentTimeMillis(), v) }
}
RealtimeChart(state = state, modifier = Modifier.fillMaxSize())
Configuration
ChartConfig(
xWindowSeconds = 10f, // visible window width
yRange = YRange.Auto(), // or YRange.Fixed(-1f, 1f)
t0 = T0.FirstSample, // or T0.Fixed(epochMs)
targetFps = null, // null = display refresh rate
maxBufferSeconds = 3600f, // 1h history per signal
theme = ChartTheme( // optional visual override
backgroundColor = Color(0xFF1A1A2E),
gridColor = Color(0x22FFFFFF),
axisColor = Color(0xFFBBBBBB),
),
)
Flow API
// From Flow<Float> (auto-timestamp)
state.collectFrom("accel_x", accelerometerFlow, viewModelScope)
// From Flow<Pair<Long, Float>> (explicit timestamp)
state.collectFrom("ecg", ecgFlow, viewModelScope) // ecgFlow emits (timestampMs, value)
Performance notes
- Data thread pushes at any rate; render thread reads at display refresh (or
targetFps) - Buffer sized to
maxBufferSeconds × 1000 samples/sper signal - LoD decimation reduces >pixel-count data to min/max per pixel column
- Pre-allocated arrays — zero GC on hot render path (except LoD scratch, see TODO in
LodDecimator.kt)
Requirements
- Android minSdk 26
- Jetpack Compose / Compose Multiplatform 1.8+
- Kotlin 2.1+