KMPCharts/chart-realtime
dtrentin 554b67e630 refactor(buffer): MIN_MAX tier binning, remove MEAN lod mode
- 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>
2026-05-21 08:57:58 +02:00
..
src refactor(buffer): MIN_MAX tier binning, remove MEAN lod mode 2026-05-21 08:57:58 +02:00
build.gradle.kts refactor(buffer): MIN_MAX tier binning, remove MEAN lod mode 2026-05-21 08:57:58 +02:00
README.md Initial commit: chart-realtime KMP library v0.4.1-SNAPSHOT 2026-05-21 01:48:51 +02:00

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/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+