Some checks failed
CI / build (push) Has been cancelled
Multi-scene Compose app demonstrating chart-realtime v0.5.0 features
end-to-end. Single Activity, sealed Screen enum + mutableStateOf for
nav (no nav library dep).
Scenes:
- LiveSensor — synthetic waveform sandbox with full control panel
(relocated from DemoScreen.kt; behavior unchanged)
- Ecg — single-signal 250Hz ECG with P/QRS/T complex synthesis, dark
theme, UnitAxisFormatter("mV"), MinMaxLttbLodStrategy
- StressTest — 8 signals @ 200Hz (1600 samples/sec total), MinMaxLod
for peak preservation, live push-rate readout
- HistoryScrubber — preloads 300k samples in coroutine, then pauses
production; demonstrates tiered buffer (Tier0 60s full-rate +
Tier1/Tier2 LoD-decimated history) and interaction History mode
- CustomRenderer — ships AreaSignalRenderer + ScatterSignalRenderer
in app/demo/renderers/ to demo SignalRenderer extensibility (~30
LoC each, no library code change)
- Formatters — 4 stacked mini-charts, same data, 4 different
AxisFormatter impls (Time / Decimal / DateTime / Unit("Hz"))
- ThemeGallery — 4 ChartTheme presets side-by-side (Light, Dark,
Cyberpunk, Newspaper)
Material3 NavigationBar + TopAppBar. Unicode emoji as nav icons
(skips material-icons-extended dep). enableEdgeToEdge() with Scaffold
inset handling. Dark mode toggle in TopAppBar actions.
Files:
- app/demo/ShowcaseApp.kt — root composable, Screen enum, nav
- app/demo/{LiveSensor,Ecg,StressTest,HistoryScrubber,
CustomRenderer,FormattersShowcase,ThemeGallery}Screen.kt
- app/demo/renderers/{AreaSignalRenderer,ScatterSignalRenderer}.kt
- app/demo/generators/EcgGenerator.kt
- app/demo/MainActivity.kt — hosts ShowcaseApp + dark-mode state
- app/demo/DemoScreen.kt — deleted (content moved to
LiveSensorScreen.kt)
Preserved (used by LiveSensorScreen):
- ControlPanel.kt, DemoConfig.kt, SignalGenerator.kt,
WaveformType.kt, PushCounter.kt
Build: assembleDebug green, APK 11.1 MB. No new Maven deps. No
library code changes. iosSimulatorArm64Test still 207/207 green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
128 lines
4.9 KiB
Kotlin
128 lines
4.9 KiB
Kotlin
package dev.dtrentin.chart.demo
|
|
|
|
import androidx.compose.foundation.layout.Box
|
|
import androidx.compose.foundation.layout.Column
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.padding
|
|
import androidx.compose.material3.MaterialTheme
|
|
import androidx.compose.material3.Surface
|
|
import androidx.compose.material3.Text
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
import androidx.compose.runtime.remember
|
|
import androidx.compose.ui.Modifier
|
|
import androidx.compose.ui.unit.dp
|
|
import dev.dtrentin.chart.RealtimeChart
|
|
import dev.dtrentin.chart.RealtimeChartState
|
|
import dev.dtrentin.chart.demo.renderers.AreaSignalRenderer
|
|
import dev.dtrentin.chart.demo.renderers.ScatterSignalRenderer
|
|
import dev.dtrentin.chart.interaction.InteractionConfig
|
|
import dev.dtrentin.chart.interaction.rememberChartInteractionState
|
|
import dev.dtrentin.chart.lod.MinMaxLttbLodStrategy
|
|
import dev.dtrentin.chart.model.AxisConfig
|
|
import dev.dtrentin.chart.model.AxisLabelMode
|
|
import dev.dtrentin.chart.model.ChartConfig
|
|
import dev.dtrentin.chart.model.DataConfig
|
|
import dev.dtrentin.chart.model.RenderConfig
|
|
import dev.dtrentin.chart.model.SignalConfig
|
|
import dev.dtrentin.chart.model.YRange
|
|
import dev.dtrentin.chart.model.rememberMaterialChartTheme
|
|
import dev.dtrentin.chart.render.DecimalAxisFormatter
|
|
import dev.dtrentin.chart.render.TimeAxisFormatter
|
|
import kotlin.math.PI
|
|
import kotlin.math.sin
|
|
import kotlin.random.Random
|
|
import kotlinx.coroutines.delay
|
|
import kotlinx.coroutines.isActive
|
|
|
|
/**
|
|
* Demonstrates the [dev.dtrentin.chart.render.SignalRenderer] extensibility surface.
|
|
* Two signals on the same chart:
|
|
* - "area": filled curve via [AreaSignalRenderer]
|
|
* - "scatter": dot scatter via [ScatterSignalRenderer]
|
|
*
|
|
* Both renderers are ~30 LoC each (see `demo/renderers/`).
|
|
*/
|
|
private const val CUSTOM_SAMPLE_HZ = 100
|
|
|
|
@Composable
|
|
fun CustomRendererScreen() {
|
|
val chartTheme = rememberMaterialChartTheme()
|
|
val primary = MaterialTheme.colorScheme.primary
|
|
val secondary = MaterialTheme.colorScheme.tertiary
|
|
|
|
val state = remember(chartTheme) {
|
|
RealtimeChartState(
|
|
config = ChartConfig(
|
|
data = DataConfig(
|
|
xWindowSeconds = 10f,
|
|
yRange = YRange.Auto(),
|
|
),
|
|
axis = AxisConfig(
|
|
xLabelMode = AxisLabelMode.BESIDE,
|
|
yLabelMode = AxisLabelMode.BESIDE,
|
|
yLabelDecimals = 2,
|
|
showGrid = true,
|
|
xFormatter = TimeAxisFormatter,
|
|
yFormatter = DecimalAxisFormatter(decimals = 2),
|
|
),
|
|
render = RenderConfig(
|
|
theme = chartTheme,
|
|
lodStrategy = MinMaxLttbLodStrategy(),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
val interaction = rememberChartInteractionState(InteractionConfig())
|
|
|
|
LaunchedEffect(state, primary, secondary) {
|
|
state.addSignal(
|
|
"area",
|
|
SignalConfig(color = primary, strokeWidth = 2f, renderer = AreaSignalRenderer),
|
|
)
|
|
state.addSignal(
|
|
"scatter",
|
|
SignalConfig(color = secondary, strokeWidth = 2f, renderer = ScatterSignalRenderer),
|
|
)
|
|
val tickIntervalMs = (1_000L / CUSTOM_SAMPLE_HZ).coerceAtLeast(1L)
|
|
val startMs = System.currentTimeMillis()
|
|
val twoPi = 2f * PI.toFloat()
|
|
var n = 0L
|
|
while (isActive) {
|
|
val ts = startMs + (n * 1000L) / CUSTOM_SAMPLE_HZ
|
|
val t = n.toFloat() / CUSTOM_SAMPLE_HZ
|
|
val areaV = sin(twoPi * 0.3f * t)
|
|
val scatterV = (Random.nextFloat() * 2f - 1f) * 0.7f
|
|
state.push("area", ts, areaV)
|
|
state.push("scatter", ts, scatterV)
|
|
n++
|
|
delay(tickIntervalMs)
|
|
}
|
|
}
|
|
|
|
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
|
Column(modifier = Modifier.fillMaxSize().padding(8.dp)) {
|
|
Column(modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 6.dp)) {
|
|
Text(
|
|
"Custom SignalRenderer impls",
|
|
style = MaterialTheme.typography.titleSmall,
|
|
)
|
|
Text(
|
|
"Same SignalRenderer interface as LineSignalRenderer. ~30 LoC each.",
|
|
style = MaterialTheme.typography.labelSmall,
|
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
)
|
|
}
|
|
Box(modifier = Modifier.fillMaxWidth().weight(1f)) {
|
|
RealtimeChart(
|
|
state = state,
|
|
modifier = Modifier.fillMaxSize(),
|
|
xWindowSeconds = 10f,
|
|
theme = chartTheme,
|
|
interaction = interaction,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|