Ships v0.5.0 via kmp-manager 6-phase flow. 13 plan tasks (D1, T1-T10, D3, D4), 14 agent calls, 6 P-groups. 107 → 207 tests on iosSimulatorArm64. Toolchain (D1): - Kotlin 2.1.0 → 2.3.21 - Compose-Multiplatform 1.8.0 → 1.11.0 - Android Gradle Plugin 8.7.3 → 9.2.0 - Gradle 8.11.1 → 9.5.1 - coroutines 1.9.0 → 1.11.0, kotlin-test → 2.3.21 - Drop kotlinx-datetime; use stdlib kotlin.time.Clock - Drop iosX64 target (Compose-MP 1.11.0 has no ios_x64 variant) Architecture (T1-T6, T10): - TieredBuffer.snapshotWindow: bisect-based windowed snapshot - New lod/ package: LodStrategy interface + MinMax/Lttb/MinMaxLttb impls (MinMaxLttb SOTA per arXiv 2305.00332, 1.80× faster than pure LTTB) - New render/SignalRenderer: public interface + LineSignalRenderer object - New render/AxisFormatter: 4 default impls (Time, Decimal, DateTime, Unit) - HARD BREAK: deleted LodMode, LodDecimator, ChartConfig.targetFps - ChartConfig split: DataConfig + AxisConfig + RenderConfig + FrameRate sealed - @Immutable/@Stable on all public types (0 unstable) - RealtimeChartState.clear() API Interaction layer (T7): - New interaction/ package - ChartInteractionState + rememberChartInteractionState() - ViewportMode sealed: Following / Frozen / History(anchorMs) - Pinch zoom + drag pan + tap crosshair gestures - Swipe-to-edge resumes Following - InverseProjection: pixel → ms + bisect nearest-sample Perf finishing (T8, T9, D3): - LineSignalRenderer Stroke cache, AxisRenderer TextStyle cache - resolveYRange Pair<Float,Float> → FloatArray out-param - RealtimeChartState.signalsArray cached (invalidated on add/remove only) - LTTB upper-bound aligned to half-open [start, start+windowMs) semantic Correctness (D4): - NumberFormat.formatFixed Long overflow guard @ |v|≥1e19 ABI baseline regenerated: - chart-realtime.api: 161 → 428 LOC - chart-realtime.klib.api: 211 → 501 LOC Modules touched: chart-realtime (lib), app (consumer), gradle (toolchain), .paul (state). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
43 lines
1.9 KiB
Kotlin
43 lines
1.9 KiB
Kotlin
package dev.dtrentin.chart.render
|
|
|
|
import dev.dtrentin.chart.model.ChartConfig
|
|
import dev.dtrentin.chart.model.DataConfig
|
|
import dev.dtrentin.chart.model.YRange
|
|
import kotlin.test.Test
|
|
import kotlin.test.assertEquals
|
|
import kotlin.test.assertTrue
|
|
|
|
class AxisRendererTest {
|
|
|
|
// T9: resolveYRange writes into out param (Fixed range).
|
|
@Test fun resolveYRange_writesIntoOutArray_fixed() {
|
|
val cfg = ChartConfig(data = DataConfig(yRange = YRange.Fixed(min = -5f, max = 12f)))
|
|
val out = FloatArray(2)
|
|
AxisRenderer.resolveYRange(cfg, dataMin = -100f, dataMax = 100f, outYRange = out)
|
|
assertEquals(-5f, out[0])
|
|
assertEquals(12f, out[1])
|
|
}
|
|
|
|
// T9: resolveYRange writes into out param (Auto with padding).
|
|
@Test fun resolveYRange_writesIntoOutArray_autoWithPadding() {
|
|
val cfg = ChartConfig(data = DataConfig(yRange = YRange.Auto(paddingFraction = 0.1f)))
|
|
val out = FloatArray(2)
|
|
AxisRenderer.resolveYRange(cfg, dataMin = 0f, dataMax = 10f, outYRange = out)
|
|
// padding = (10 - 0) * 0.1 = 1 → expect [-1, 11].
|
|
assertEquals(-1f, out[0])
|
|
assertEquals(11f, out[1])
|
|
}
|
|
|
|
// T9: resolveYRange handles flat data — uses range floor 1e-6 to avoid div-by-zero.
|
|
// With paddingFraction 0.1 and floor 1e-6, the effective padding is 1e-7. Below Float
|
|
// resolution at value 5f, so out[0] == out[1] == 5f is permitted (downstream renderers
|
|
// re-apply their own (yMax-yMin).coerceAtLeast(1e-6f)). Just assert no NaN/Infinity.
|
|
@Test fun resolveYRange_flatData_writesFiniteValues() {
|
|
val cfg = ChartConfig(data = DataConfig(yRange = YRange.Auto(paddingFraction = 0.1f)))
|
|
val out = FloatArray(2)
|
|
AxisRenderer.resolveYRange(cfg, dataMin = 5f, dataMax = 5f, outYRange = out)
|
|
assertTrue(out[0].isFinite())
|
|
assertTrue(out[1].isFinite())
|
|
assertTrue(out[1] >= out[0])
|
|
}
|
|
}
|