- RealtimeChart composable with Canvas-based rendering - TieredBuffer: 3-tier ring buffer (5min full-rate, 10min @10Hz, 45min @1Hz) for 1h data support with ~5MB footprint - LodDecimator: MEAN / MIN_MAX / MIN_MAX(default) / LTTB render-time decimation - AxisRenderer: X/Y axis with INSIDE/BESIDE/HIDDEN label modes, T0-relative timestamps, scientific notation - SignalRenderer: clipRect-bounded path rendering with LoD - ChartTheme: Material3-compatible dynamic theming (light/dark) - package: dev.dtrentin.chart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
80 lines
2.4 KiB
Markdown
80 lines
2.4 KiB
Markdown
# 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
|
||
|
||
```kotlin
|
||
// settings.gradle.kts
|
||
include(":chart-realtime")
|
||
```
|
||
|
||
Or Maven local:
|
||
```bash
|
||
./gradlew :chart-realtime:publishToMavenLocal
|
||
```
|
||
|
||
```kotlin
|
||
// build.gradle.kts
|
||
implementation("eu.henesis:chart-realtime:0.1.0-SNAPSHOT")
|
||
```
|
||
|
||
## Quick start (3 lines)
|
||
|
||
```kotlin
|
||
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
|
||
|
||
```kotlin
|
||
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
|
||
|
||
```kotlin
|
||
// 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/s` per 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+
|