feat(ios): iOS build pipeline + XCFramework, fix K/Native source bugs
Modules: chart-realtime (commonMain, iosMain), root, .paul
- chart-realtime/build.gradle.kts: applyDefaultHierarchyTemplate() + XCFramework("ChartRealtime") over iosX64/iosArm64/iosSimulatorArm64, baseName CamelCase
- commonMain RealtimeChartState/CircularBuffer: switch to kotlin.concurrent.Volatile, add kotlin.jvm.JvmName import (OptionalExpectation)
- commonMain AxisRenderer: replace JVM-only String.format with multiplatform formatFixed/formatScientific/pow10 helpers
- iosMain Platform.ios.kt: NSDate() + timeIntervalSinceReferenceDate + 978_307_200s 1970-epoch offset (K/Native binding has no direct timeIntervalSince1970)
- gradle.properties: remove Linux JDK pin (rely on JAVA_HOME)
- .paul: phases plan, STATE+ROADMAP updates
Acceptance: A1 compile 3 iOS targets, A2 linkDebugFrameworkIosSimulatorArm64, A3 iosSimulatorArm64Test (33 tests, 0 failures), A4 assembleChartRealtimeXCFramework (ios-arm64 + ios-arm64_x86_64-simulator slices), A5 Android assembleRelease/publishToMavenLocal --dry-run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
73bbd9ddec
commit
d6f51cdef2
9 changed files with 228 additions and 29 deletions
|
|
@ -10,7 +10,8 @@ KMP real-time chart library. Six phases v0.1.0 MVP shipped; v0.2.0 polish shippe
|
||||||
|-----------|---------|--------|-----------|
|
|-----------|---------|--------|-----------|
|
||||||
| v0.1.0 MVP | 0.1.0 | ✓ Shipped | 2026-05-20 |
|
| v0.1.0 MVP | 0.1.0 | ✓ Shipped | 2026-05-20 |
|
||||||
| v0.2.0 Polish | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
| v0.2.0 Polish | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
||||||
| v0.3.0 (TBD) | - | Not started | - |
|
| v0.3.0 iOS Build Pipeline | 0.5.0-SNAPSHOT | ✓ Shipped | 2026-05-21 |
|
||||||
|
| v0.4.0 (TBD) | - | Not started | - |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -56,16 +57,37 @@ Summary: .paul/phases/v0.2.0-plan-SUMMARY.md
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## v0.3.0 — Potential Scope (TBD)
|
## v0.3.0 — iOS Build Pipeline (SHIPPED)
|
||||||
|
|
||||||
|
Status: ✓ Complete
|
||||||
|
Plan: .paul/phases/v0.3.0-ios-build-plan.md
|
||||||
|
|
||||||
|
### Delivered
|
||||||
|
|
||||||
|
- 3 iOS targets compile green (iosX64, iosArm64, iosSimulatorArm64)
|
||||||
|
- `applyDefaultHierarchyTemplate()` wires iosMain/iosTest across targets
|
||||||
|
- `linkDebugFrameworkIosSimulatorArm64` green
|
||||||
|
- `iosSimulatorArm64Test` green — 33 tests, 0 failures
|
||||||
|
- XCFramework `ChartRealtime` produced (debug + release): ios-arm64 + ios-arm64_x86_64-simulator slices
|
||||||
|
- Pre-existing compile bugs fixed:
|
||||||
|
- `@Volatile` → `kotlin.concurrent.Volatile` import
|
||||||
|
- `@JvmName` → `kotlin.jvm.JvmName` import (OptionalExpectation)
|
||||||
|
- `String.format` in AxisRenderer → manual `formatFixed`/`formatScientific`/`pow10` helpers
|
||||||
|
- `NSDate.date()` → `NSDate()` + `timeIntervalSinceReferenceDate` + 1970 epoch offset
|
||||||
|
- Android non-regression confirmed (`assembleRelease`, `publishToMavenLocal --dry-run`)
|
||||||
|
- Local Mac config: removed Linux JDK pin from `gradle.properties`, added `local.properties` for Android SDK
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.4.0 — Potential Scope (TBD)
|
||||||
|
|
||||||
| Item | Effort | Priority |
|
| Item | Effort | Priority |
|
||||||
|------|--------|----------|
|
|------|--------|----------|
|
||||||
| Dirty flag render (skip frame if no new data) | S | High — thermal |
|
| Publish to Maven Central / GitHub Packages | M | Medium |
|
||||||
| targetFps=30 default | S | High — thermal |
|
| iOS demo app (SwiftUI) | L | Medium |
|
||||||
| KDoc on public API surface | M | Medium |
|
| CI workflow GitHub Actions (Android + iOS) | M | Medium |
|
||||||
| README with integration example | S | Medium |
|
| Compose compiler stability annotation on SignalConfig | S | Low |
|
||||||
| Publish to Maven Central / GitHub Packages | M | Low |
|
| CocoaPods / SPM publication | M | Low |
|
||||||
| iOS expect/actual (beyond currentTimeMs) | L | Future |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
*Roadmap updated: 2026-05-21*
|
*Roadmap updated: 2026-05-21*
|
||||||
|
|
|
||||||
|
|
@ -5,25 +5,26 @@
|
||||||
See: .paul/PROJECT.md (updated 2026-05-21)
|
See: .paul/PROJECT.md (updated 2026-05-21)
|
||||||
|
|
||||||
**Core value:** Android devs plot 200Hz+ multi-signal sensor data without frame drops
|
**Core value:** Android devs plot 200Hz+ multi-signal sensor data without frame drops
|
||||||
**Current focus:** v0.3.0 IN PROGRESS — dirty flag render + targetFps=30 shipped
|
**Current focus:** v0.3.0 iOS build pipeline SHIPPED — 33 tests pass on iOS sim, XCFramework produced
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone: v0.2.0-polish
|
Milestone: v0.3.0-ios-build
|
||||||
Phase: 6 of 6 — Complete
|
Phase: 6 of 6 — Complete
|
||||||
Plan: .paul/phases/v0.2.0-plan.md
|
Plan: .paul/phases/v0.3.0-ios-build-plan.md
|
||||||
Status: Complete
|
Status: Complete
|
||||||
Last activity: 2026-05-21 — dirty flag render, targetFps=30, 17 unit tests, KDoc, README, iOS targets (iosX64/iosArm64/iosSimulatorArm64) + Platform.ios.kt actual.
|
Last activity: 2026-05-21 — iOS pipeline: compile 3 targets, link debug framework, iosSimulatorArm64Test green (33 tests, 0 failures), XCFramework "ChartRealtime" output (ios-arm64 + ios-arm64_x86_64-simulator slices). Android non-regression confirmed.
|
||||||
|
|
||||||
Progress:
|
Progress:
|
||||||
- v0.1.0 milestone: [██████████] 100% SHIPPED
|
- v0.1.0 milestone: [██████████] 100% SHIPPED
|
||||||
- v0.2.0 milestone: [██████████] 100% SHIPPED
|
- v0.2.0 milestone: [██████████] 100% SHIPPED
|
||||||
|
- v0.3.0 milestone: [██████████] 100% SHIPPED
|
||||||
|
|
||||||
## Loop Position
|
## Loop Position
|
||||||
|
|
||||||
```
|
```
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
✓ ✓ ✓ [v0.2.0 SHIPPED — v0.5.0-SNAPSHOT]
|
✓ ✓ ✓ [v0.3.0 iOS build SHIPPED — v0.5.0-SNAPSHOT]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
@ -41,6 +42,11 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
| Sensor cap 200Hz (5_000μs) | v0.2.0 | Thermal/battery optimization |
|
| Sensor cap 200Hz (5_000μs) | v0.2.0 | Thermal/battery optimization |
|
||||||
| Package dev.dtrentin | v0.2.0 | Personal project, not company |
|
| Package dev.dtrentin | v0.2.0 | Personal project, not company |
|
||||||
| iOS targets shipped | v0.3.0 | iosX64/iosArm64/iosSimulatorArm64 + Platform.ios.kt |
|
| iOS targets shipped | v0.3.0 | iosX64/iosArm64/iosSimulatorArm64 + Platform.ios.kt |
|
||||||
|
| iOS build pipeline green | v0.3.0 iOS | 33 tests pass on iOS sim, XCFramework "ChartRealtime" produced |
|
||||||
|
| timeIntervalSinceReferenceDate + epoch offset | v0.3.0 iOS | K/Native binding doesn't expose timeIntervalSince1970; use 978_307_200s constant |
|
||||||
|
| applyDefaultHierarchyTemplate() explicit | v0.3.0 iOS | iosMain/iosTest auto-wired across 3 iOS targets |
|
||||||
|
| Manual format helpers in AxisRenderer | v0.3.0 iOS | String.format JVM-only; private formatFixed/formatScientific/pow10 |
|
||||||
|
| kotlin.concurrent.Volatile + kotlin.jvm.JvmName imports | v0.3.0 iOS | Multiplatform replacements for JVM-default annotations |
|
||||||
|
|
||||||
### Deferred Issues
|
### Deferred Issues
|
||||||
|
|
||||||
|
|
@ -51,21 +57,27 @@ PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
| ~~iOS expect/actual beyond currentTimeMs~~ | Init | L | ✓ v0.3.0 |
|
| ~~iOS expect/actual beyond currentTimeMs~~ | Init | L | ✓ v0.3.0 |
|
||||||
| ~~KDoc on public API surface~~ | v0.2.0 | M | ✓ v0.3.0 |
|
| ~~KDoc on public API surface~~ | v0.2.0 | M | ✓ v0.3.0 |
|
||||||
| ~~README with integration example~~ | v0.2.0 | S | ✓ v0.3.0 |
|
| ~~README with integration example~~ | v0.2.0 | S | ✓ v0.3.0 |
|
||||||
| Publish to Maven Central / GitHub Packages | v0.2.0 | M | v0.3.0 |
|
| ~~iOS build/test pipeline (compile + iosTest + XCFramework)~~ | v0.3.0 plan | M | ✓ v0.3.0 iOS |
|
||||||
|
| Publish to Maven Central / GitHub Packages | v0.2.0 | M | v0.4.0 |
|
||||||
|
| iOS demo app (SwiftUI) | v0.3.0 iOS | L | v0.4.0 |
|
||||||
|
| CI workflow for iOS build (GitHub Actions) | v0.3.0 iOS | M | v0.4.0 |
|
||||||
|
| Compose compiler unstable warning on SignalConfig | v0.3.0 iOS | S | v0.4.0 |
|
||||||
|
|
||||||
### Blockers/Concerns
|
### Blockers/Concerns
|
||||||
None.
|
None.
|
||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-05-21
|
Last session: 2026-05-21 (iOS build pipeline)
|
||||||
Stopped at: v0.2.0 complete. All AC pass. Git pushed to remote.
|
Stopped at: v0.3.0 iOS shipped. All 5 acceptance items pass (A1-A5). 33 tests green on iOS sim. ChartRealtime.xcframework at chart-realtime/build/XCFrameworks/release/.
|
||||||
Next action: Define v0.3.0 scope (dirty flag render, targetFps=30, KDoc, publish)
|
Next action: Define v0.4.0 scope (Maven Central publish, iOS demo app, CI)
|
||||||
Resume context:
|
Resume context:
|
||||||
- Library: AndroidCharts/chart-realtime, published dev.dtrentin:chart-realtime:0.5.0-SNAPSHOT to mavenLocal
|
- Library: chart-realtime, published dev.dtrentin:chart-realtime:0.5.0-SNAPSHOT to mavenLocal
|
||||||
- App: AndroidChartsApp consuming library, builds clean
|
- App: AndroidChartsApp consuming library, builds clean
|
||||||
- Remote: ssh://git@3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git
|
- Remote: ssh://git@3nt-git.duckdns.org:222/davide.trentin/KMPCharts.git
|
||||||
- Package: dev.dtrentin.chart
|
- Package: dev.dtrentin.chart
|
||||||
|
- iOS: 3 targets compile, link, test (33 tests). XCFramework name `ChartRealtime`, baseName CamelCase.
|
||||||
|
- Local Mac setup: Xcode.app at /Applications/Xcode.app, iOS sim runtime installed (iPhone 17 family), local.properties points to ~/Library/Android/sdk, JDK 21 Temurin via JAVA_HOME.
|
||||||
|
|
||||||
---
|
---
|
||||||
*STATE.md — Updated after every significant action*
|
*STATE.md — Updated after every significant action*
|
||||||
|
|
|
||||||
109
.paul/phases/v0.3.0-ios-build-plan.md
Normal file
109
.paul/phases/v0.3.0-ios-build-plan.md
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
# v0.3.0 — iOS Build & Test Pipeline Plan
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Wire `chart-realtime` for full iOS pipeline: compile all 3 iOS targets, link debug framework on `iosSimulatorArm64`, run `commonTest` on iOS sim, produce release `XCFramework`. Keep Android green. No new features, no demo app, no SPM/CocoaPods/CI.
|
||||||
|
|
||||||
|
Acceptance:
|
||||||
|
- A1: `./gradlew :chart-realtime:compileKotlinIosX64 compileKotlinIosArm64 compileKotlinIosSimulatorArm64` green
|
||||||
|
- A2: `./gradlew :chart-realtime:linkDebugFrameworkIosSimulatorArm64` green
|
||||||
|
- A3: `./gradlew :chart-realtime:iosSimulatorArm64Test` green, executes existing common tests
|
||||||
|
- A4: `./gradlew :chart-realtime:assembleXCFramework` produces `chart-realtime/build/XCFrameworks/release/chart-realtime.xcframework`
|
||||||
|
- A5: `./gradlew :chart-realtime:assemble` green (Android non-regression)
|
||||||
|
|
||||||
|
## Locked decisions
|
||||||
|
- T0: remove `org.gradle.java.home` pin (rely on JAVA_HOME)
|
||||||
|
- XCFramework baseName: `ChartRealtime` (CamelCase iOS convention)
|
||||||
|
- Include `iosX64` target in XCFramework
|
||||||
|
- Local Mac dev only, no CI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### T0 — Mac JDK / gradle.properties sanity
|
||||||
|
- Goal: Unblock local Mac build. Current `gradle.properties` pins Linux JDK path. Pre-req for A1–A5.
|
||||||
|
- Files: `gradle.properties`
|
||||||
|
- Agent: `gradle-expert`
|
||||||
|
- Action: remove line `org.gradle.java.home=/home/dtrentin/.local/share/JetBrains/Toolbox/apps/android-studio/jbr`. Keep other lines unchanged.
|
||||||
|
- Deps: none
|
||||||
|
- Verify: `./gradlew :chart-realtime:help`
|
||||||
|
|
||||||
|
### T1+T2+T4 — Batched build.gradle.kts edit
|
||||||
|
- Goal: iOS hierarchy explicit + XCFramework task.
|
||||||
|
- Files: `chart-realtime/build.gradle.kts`
|
||||||
|
- Agent: `gradle-expert` (single edit, single agent)
|
||||||
|
- Actions:
|
||||||
|
- Apply default hierarchy template explicitly (`applyDefaultHierarchyTemplate()`)
|
||||||
|
- Configure each iOS target with `binaries.framework { baseName = "ChartRealtime"; xcf.add(this) }` using `XCFramework("ChartRealtime")` helper from `org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework`
|
||||||
|
- Keep `compose.material3` in `commonMain` (CMP 1.8.0 supports iOS native)
|
||||||
|
- Do not break Android target, publishing, or existing source-set deps
|
||||||
|
- Deps: T0
|
||||||
|
- Verify:
|
||||||
|
- `./gradlew :chart-realtime:compileKotlinIosX64`
|
||||||
|
- `./gradlew :chart-realtime:compileKotlinIosArm64`
|
||||||
|
- `./gradlew :chart-realtime:compileKotlinIosSimulatorArm64`
|
||||||
|
- `./gradlew :chart-realtime:linkDebugFrameworkIosSimulatorArm64`
|
||||||
|
- `./gradlew :chart-realtime:assembleXCFramework`
|
||||||
|
|
||||||
|
### T3 — iosTest wiring
|
||||||
|
- Goal: `iosTest` source set runs existing `commonTest` files on iOS sim.
|
||||||
|
- Files: `chart-realtime/build.gradle.kts` (only if hierarchy template doesn't auto-wire)
|
||||||
|
- Agent: `unit-test-writer`
|
||||||
|
- Inputs: existing `commonTest.dependencies { implementation(libs.kotlin.test) }`. Default hierarchy template wires `iosTest → commonTest` automatically. Verify; add explicit block only if test discovery fails.
|
||||||
|
- Deps: T1+T2+T4
|
||||||
|
- Verify: `./gradlew :chart-realtime:iosSimulatorArm64Test`
|
||||||
|
|
||||||
|
### T5 — Android non-regression guard
|
||||||
|
- Goal: Confirm Android target/build/publish untouched.
|
||||||
|
- Agent: `android-build-resolver`
|
||||||
|
- Deps: T1+T2+T4
|
||||||
|
- Verify: `./gradlew :chart-realtime:assembleRelease publishToMavenLocal --dry-run`
|
||||||
|
|
||||||
|
### T6 — Smoke acceptance run
|
||||||
|
- Goal: Sequential run of all 5 acceptance commands.
|
||||||
|
- Agent: `general-purpose`
|
||||||
|
- Deps: T3, T5
|
||||||
|
- Verify: all 5 commands green
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallelism
|
||||||
|
|
||||||
|
| Group | Tasks |
|
||||||
|
|-------|-------|
|
||||||
|
| P0 | T0 |
|
||||||
|
| P1 | T1+T2+T4 (one batched agent call) |
|
||||||
|
| P2 | T3, T5 (parallel) |
|
||||||
|
| P3 | T6 |
|
||||||
|
| P4 | Unify |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- R1: gradle.properties Linux JDK path — fixed in T0
|
||||||
|
- R2: `compose.material3` iOS native — supported in 1.8.0; fallback = isolate to non-iOS if compile fails
|
||||||
|
- R3: Hierarchy template silent inactivity — `applyDefaultHierarchyTemplate()` made explicit
|
||||||
|
- R4: `commonTest` not seen by `iosTest` — same hierarchy fix
|
||||||
|
- R5: XCFramework name collision — single `XCFramework("ChartRealtime")` registration
|
||||||
|
- R6: Compose link errors on iOS — defer to v0.4.0 if hit
|
||||||
|
- R7: Android regression — T5 final gate, revert if needed
|
||||||
|
- R8: Sim runtime requirement — confirmed installed (iPhone 17 family available)
|
||||||
|
|
||||||
|
## New Deps
|
||||||
|
None. KGP + CMP 1.8.0 + Kotlin 2.1.0 cover everything.
|
||||||
|
|
||||||
|
## Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :chart-realtime:help # T0
|
||||||
|
./gradlew :chart-realtime:compileKotlinIosX64 # A1
|
||||||
|
./gradlew :chart-realtime:compileKotlinIosArm64 # A1
|
||||||
|
./gradlew :chart-realtime:compileKotlinIosSimulatorArm64 # A1
|
||||||
|
./gradlew :chart-realtime:linkDebugFrameworkIosSimulatorArm64 # A2
|
||||||
|
./gradlew :chart-realtime:iosSimulatorArm64Test # A3
|
||||||
|
./gradlew :chart-realtime:assembleXCFramework # A4
|
||||||
|
ls chart-realtime/build/XCFrameworks/release/ChartRealtime.xcframework # A4
|
||||||
|
./gradlew :chart-realtime:assembleRelease # A5
|
||||||
|
./gradlew :chart-realtime:publishToMavenLocal --dry-run # T5
|
||||||
|
```
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlin.multiplatform)
|
alias(libs.plugins.kotlin.multiplatform)
|
||||||
alias(libs.plugins.android.library)
|
alias(libs.plugins.android.library)
|
||||||
|
|
@ -10,6 +12,8 @@ group = "dev.dtrentin"
|
||||||
version = "0.5.0-SNAPSHOT"
|
version = "0.5.0-SNAPSHOT"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
applyDefaultHierarchyTemplate()
|
||||||
|
|
||||||
androidTarget {
|
androidTarget {
|
||||||
compilations.all {
|
compilations.all {
|
||||||
compileTaskProvider.configure {
|
compileTaskProvider.configure {
|
||||||
|
|
@ -21,9 +25,17 @@ kotlin {
|
||||||
publishLibraryVariants("release")
|
publishLibraryVariants("release")
|
||||||
}
|
}
|
||||||
|
|
||||||
iosX64()
|
val xcf = XCFramework("ChartRealtime")
|
||||||
iosArm64()
|
listOf(
|
||||||
|
iosX64(),
|
||||||
|
iosArm64(),
|
||||||
iosSimulatorArm64()
|
iosSimulatorArm64()
|
||||||
|
).forEach { target ->
|
||||||
|
target.binaries.framework {
|
||||||
|
baseName = "ChartRealtime"
|
||||||
|
xcf.add(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import dev.dtrentin.chart.buffer.TieredBuffer
|
||||||
import dev.dtrentin.chart.model.ChartConfig
|
import dev.dtrentin.chart.model.ChartConfig
|
||||||
import dev.dtrentin.chart.model.SignalConfig
|
import dev.dtrentin.chart.model.SignalConfig
|
||||||
import dev.dtrentin.chart.model.T0
|
import dev.dtrentin.chart.model.T0
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
|
import kotlin.jvm.JvmName
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package dev.dtrentin.chart.buffer
|
package dev.dtrentin.chart.buffer
|
||||||
|
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock-free circular buffer for (timestampMs, value) sensor samples.
|
* Lock-free circular buffer for (timestampMs, value) sensor samples.
|
||||||
* Pre-allocated — zero allocations after construction.
|
* Pre-allocated — zero allocations after construction.
|
||||||
|
|
|
||||||
|
|
@ -164,15 +164,50 @@ internal object AxisRenderer {
|
||||||
|
|
||||||
private fun formatAxisValue(value: Double, decimals: Int): String {
|
private fun formatAxisValue(value: Double, decimals: Int): String {
|
||||||
val absVal = kotlin.math.abs(value)
|
val absVal = kotlin.math.abs(value)
|
||||||
return if (value == 0.0) {
|
return when {
|
||||||
"0"
|
value == 0.0 -> "0"
|
||||||
} else if (absVal < 0.01 || absVal >= 1e5) {
|
absVal < 0.01 || absVal >= 1e5 -> formatScientific(value, decimals)
|
||||||
"%.${decimals}e".format(value)
|
else -> formatFixed(value, decimals)
|
||||||
} else {
|
|
||||||
"%.${decimals}f".format(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatFixed(value: Double, decimals: Int): String {
|
||||||
|
val negative = value < 0.0
|
||||||
|
val absVal = kotlin.math.abs(value)
|
||||||
|
val factor = pow10(decimals)
|
||||||
|
val rounded = kotlin.math.round(absVal * factor)
|
||||||
|
val whole = (rounded / factor).toLong()
|
||||||
|
val frac = (rounded - whole * factor).toLong()
|
||||||
|
val sign = if (negative && (whole != 0L || frac != 0L)) "-" else ""
|
||||||
|
return if (decimals <= 0) {
|
||||||
|
"$sign$whole"
|
||||||
|
} else {
|
||||||
|
val fracStr = frac.toString().padStart(decimals, '0')
|
||||||
|
"$sign$whole.$fracStr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatScientific(value: Double, decimals: Int): String {
|
||||||
|
if (value == 0.0) return formatFixed(0.0, decimals) + "e+00"
|
||||||
|
val negative = value < 0.0
|
||||||
|
val absVal = kotlin.math.abs(value)
|
||||||
|
val exp = kotlin.math.floor(kotlin.math.log10(absVal)).toInt()
|
||||||
|
val mantissa = absVal / pow10(exp)
|
||||||
|
val mantStr = formatFixed(mantissa, decimals)
|
||||||
|
val sign = if (negative) "-" else ""
|
||||||
|
val expSign = if (exp >= 0) "+" else "-"
|
||||||
|
val expAbs = kotlin.math.abs(exp).toString().padStart(2, '0')
|
||||||
|
return "$sign${mantStr}e$expSign$expAbs"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pow10(n: Int): Double {
|
||||||
|
if (n == 0) return 1.0
|
||||||
|
var r = 1.0
|
||||||
|
val absN = kotlin.math.abs(n)
|
||||||
|
repeat(absN) { r *= 10.0 }
|
||||||
|
return if (n < 0) 1.0 / r else r
|
||||||
|
}
|
||||||
|
|
||||||
private fun niceInterval(range: Double, targetTickCount: Int): Double {
|
private fun niceInterval(range: Double, targetTickCount: Int): Double {
|
||||||
if (range <= 0) return 1.0
|
if (range <= 0) return 1.0
|
||||||
val rough = range / targetTickCount
|
val rough = range / targetTickCount
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,10 @@ package dev.dtrentin.chart
|
||||||
|
|
||||||
import platform.Foundation.NSDate
|
import platform.Foundation.NSDate
|
||||||
|
|
||||||
internal actual fun currentTimeMs(): Long = (NSDate.date().timeIntervalSince1970 * 1000).toLong()
|
private const val REFERENCE_DATE_OFFSET_SEC = 978_307_200.0
|
||||||
|
|
||||||
|
internal actual fun currentTimeMs(): Long {
|
||||||
|
val date = NSDate()
|
||||||
|
val secsSince1970 = date.timeIntervalSinceReferenceDate + REFERENCE_DATE_OFFSET_SEC
|
||||||
|
return (secsSince1970 * 1000).toLong()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
org.gradle.java.home=/home/dtrentin/.local/share/JetBrains/Toolbox/apps/android-studio/jbr
|
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
|
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue