- 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>
106 lines
4.3 KiB
Markdown
106 lines
4.3 KiB
Markdown
# chart-realtime
|
|
|
|
## What This Is
|
|
|
|
Standalone KMP module (Android-first) providing a high-performance Compose-based real-time line chart for sensor data visualization. Renders N simultaneous signals at 200Hz+ using Canvas with FIFO circular buffers and Level-of-Detail decimation. Packaged as a dependency consumable by any Android/KMP app.
|
|
|
|
## Core Value
|
|
|
|
Android developers can plot multiple high-frequency sensor signals (200Hz+) on a single chart without dropping frames or overloading the device.
|
|
|
|
## Current State
|
|
|
|
| Attribute | Value |
|
|
|-----------|-------|
|
|
| Type | Application (library module) |
|
|
| Version | 0.0.0 |
|
|
| Status | Initializing |
|
|
| Last Updated | 2026-05-20 |
|
|
|
|
## Requirements
|
|
|
|
### Core Features
|
|
|
|
- **Multi-signal real-time rendering** — N signals on same chart, each with own color/style, added/removed dynamically
|
|
- **FIFO circular buffer** — pre-allocated, lock-free write (data thread) / safe read (render thread), maxBufferSeconds configurable by caller
|
|
- **Level-of-Detail decimation** — min/max envelope per pixel column when buffer > pixel width; O(pixels) render cost regardless of buffer size
|
|
- **Flexible axis control** — X axis: seconds from t0 (T0.FirstSample or T0.Fixed); Y axis: YRange.Auto(padding) or YRange.Fixed(min, max)
|
|
- **Render rate decoupling** — data ingestion at any rate; rendering via withFrameNanos at display refresh rate (throttleable via targetFps param)
|
|
- **Flow-based API** — addSignal(name, flow, config) + manual push(name, timestampMs, value)
|
|
|
|
### Validated (Shipped)
|
|
None yet.
|
|
|
|
### Active (In Progress)
|
|
None yet.
|
|
|
|
### Planned (Next)
|
|
- Phase 1: KMP module scaffold + Gradle config
|
|
- Phase 2: CircularBuffer + LodDecimator + unit tests
|
|
- Phase 3: RealtimeChartState + Flow integration
|
|
- Phase 4: Canvas rendering engine (RenderLoop, AxisRenderer, SignalRenderer)
|
|
- Phase 5: Demo Android app (5+ signals at 200Hz)
|
|
- Phase 6: Material3 polish + API documentation
|
|
|
|
### Out of Scope
|
|
|
|
- iOS support (deferred to future milestone)
|
|
- Touch interaction / zoom / pan
|
|
- SciChart/paid-lib integration
|
|
- Network data sources (consumer provides Flow)
|
|
|
|
## Target Users
|
|
|
|
**Primary:** Android/KMP developers building sensor dashboards (IoT, medical, industrial)
|
|
- Need 200Hz+ real-time plotting
|
|
- Want drop-in Compose composable
|
|
- Expect Material3 visual consistency
|
|
|
|
## Constraints
|
|
|
|
### Technical Constraints
|
|
|
|
- KMP commonMain for all rendering logic (Compose Multiplatform Canvas)
|
|
- No object allocation in draw loop (pre-allocated FloatArray paths)
|
|
- Single draw call per signal (drawLines with FloatArray)
|
|
- Lock-free circular buffer (data thread != render thread)
|
|
- maxBufferSeconds: caller responsibility, no hard cap enforced by lib
|
|
|
|
### Business Constraints
|
|
|
|
- Standalone publishable module (Maven local / GitHub Packages)
|
|
- No paid/commercial dependencies
|
|
|
|
## Key Decisions
|
|
|
|
| Decision | Rationale | Date | Status |
|
|
|----------|-----------|------|--------|
|
|
| Canvas custom rendering over existing libs | Vico/MPAndroidChart cannot handle 200Hz+ without axis jumps or FPS drops | 2026-05-20 | Active |
|
|
| Render/data rate decoupling via withFrameNanos | 200Hz data != 60fps display; buffer absorbs delta | 2026-05-20 | Active |
|
|
| LoD min/max decimation | 1h buffer at 200Hz = 720k pts; screen = ~800px; must reduce O(n)→O(px) | 2026-05-20 | Active |
|
|
| maxBufferSeconds configurable, user responsibility | Avoid arbitrary caps; user knows their memory budget | 2026-05-20 | Active |
|
|
| Android-first, iOS deferred | Reduce scope, ship faster | 2026-05-20 | Active |
|
|
|
|
## Success Metrics
|
|
|
|
| Metric | Target | Current | Status |
|
|
|--------|--------|---------|--------|
|
|
| Sustained FPS at 200Hz, 5 signals | ≥55fps | - | Not started |
|
|
| Memory per signal per hour at 200Hz | ≤3MB | - | Not started |
|
|
| Demo app compiles and runs | Yes | - | Not started |
|
|
| API: add signal in ≤3 lines of code | Yes | - | Not started |
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Technology | Notes |
|
|
|-------|------------|-------|
|
|
| Language | Kotlin (KMP) | commonMain + androidMain |
|
|
| UI | Jetpack Compose / Compose Multiplatform | Canvas DrawScope |
|
|
| Build | Gradle + Version Catalog | Convention plugin if needed |
|
|
| DI | None (library, no DI framework) | Consumer wires state |
|
|
| Testing | kotlin.test + JUnit5 (androidTest) | Buffer + LoD unit tests |
|
|
| Demo | Android app module | 5+ signals, 200Hz simulation |
|
|
|
|
---
|
|
*PROJECT.md — Updated when requirements or context change*
|
|
*Last updated: 2026-05-20*
|