mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat: dual-station radar track plot (satlover/satmatch style)
- MutualPass now carries TrackSample (azimuth/elevation for both stations) - New MutualRadarView: polar plot with elevation rings (30/60/90), cardinal spokes, solid A-track vs dashed B-track, AOS/LOS markers - Shown in expanded mutual pass card and radar page mutual card
This commit is contained in:
1 parent
4baf3821e5
commit
bd42e643ea
6 files changed
+276
-4
No files matched your search
+9
@@ -46,6 +46,7 @@ interface IMainContainer {
|
|||||||
|
|
||||||
data class MutualPassData(
|
data class MutualPassData(
|
||||||
val samples: List<Pair<Long, Pair<Double, Double>>> = emptyList(),
|
val samples: List<Pair<Long, Pair<Double, Double>>> = emptyList(),
|
||||||
|
val trackSamples: List<TrackSampleData> = emptyList(),
|
||||||
val startTime: Long = 0L,
|
val startTime: Long = 0L,
|
||||||
val endTime: Long = 0L,
|
val endTime: Long = 0L,
|
||||||
val maxElev: Double = 10.0,
|
val maxElev: Double = 10.0,
|
||||||
@@ -53,6 +54,14 @@ data class MutualPassData(
|
|||||||
val labelB: String = "友台"
|
val labelB: String = "友台"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** Minimal track sample for cross-module sharing (angles in degrees). */
|
||||||
|
data class TrackSampleData(
|
||||||
|
val azimuthA: Double,
|
||||||
|
val elevationA: Double,
|
||||||
|
val azimuthB: Double,
|
||||||
|
val elevationB: Double
|
||||||
|
)
|
||||||
|
|
||||||
interface IContainerProvider {
|
interface IContainerProvider {
|
||||||
fun getMainContainer(): IMainContainer
|
fun getMainContainer(): IMainContainer
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ package com.rtbishop.look4sat.feature.mutual
|
|||||||
* @param maxElevationA Peak elevation for station A
|
* @param maxElevationA Peak elevation for station A
|
||||||
* @param maxElevationB Peak elevation for station B
|
* @param maxElevationB Peak elevation for station B
|
||||||
* @param elevationSamples List of (time, (elevationA, elevationB)) pairs
|
* @param elevationSamples List of (time, (elevationA, elevationB)) pairs
|
||||||
|
* @param trackSamples List of radar-track samples (azimuth/elevation for both stations, degrees)
|
||||||
*/
|
*/
|
||||||
data class MutualPass(
|
data class MutualPass(
|
||||||
val catNum: Int,
|
val catNum: Int,
|
||||||
@@ -35,5 +36,18 @@ data class MutualPass(
|
|||||||
val endTime: Long,
|
val endTime: Long,
|
||||||
val maxElevationA: Double,
|
val maxElevationA: Double,
|
||||||
val maxElevationB: Double,
|
val maxElevationB: Double,
|
||||||
val elevationSamples: List<Pair<Long, Pair<Double, Double>>>
|
val elevationSamples: List<Pair<Long, Pair<Double, Double>>>,
|
||||||
|
val trackSamples: List<TrackSample> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One radar-track sample: satellite position as seen from both stations.
|
||||||
|
* All angles are in degrees.
|
||||||
|
*/
|
||||||
|
data class TrackSample(
|
||||||
|
val time: Long,
|
||||||
|
val azimuthA: Double,
|
||||||
|
val elevationA: Double,
|
||||||
|
val azimuthB: Double,
|
||||||
|
val elevationB: Double
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.rtbishop.look4sat.feature.mutual
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
|
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.text.TextMeasurer
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.drawText
|
||||||
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
|
private const val CIRCLES = 3 // 30°, 60°, 90° rings
|
||||||
|
private const val STROKE_WIDTH = 2.5f
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polar radar chart showing the satellite track as seen from both stations
|
||||||
|
* on one plot. Station A is drawn solid, station B dashed.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MutualRadarView(
|
||||||
|
trackSamples: List<TrackSample>,
|
||||||
|
labelA: String = "站点A",
|
||||||
|
labelB: String = "站点B",
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
if (trackSamples.isEmpty()) return
|
||||||
|
|
||||||
|
val colorA = MaterialTheme.colorScheme.primary
|
||||||
|
val colorB = MaterialTheme.colorScheme.tertiary
|
||||||
|
val gridColor = MaterialTheme.colorScheme.outlineVariant
|
||||||
|
val textColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
val measurer = rememberTextMeasurer()
|
||||||
|
|
||||||
|
Column(modifier = modifier.fillMaxWidth()) {
|
||||||
|
Canvas(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
val radius = size.minDimension / 2f * 0.92f
|
||||||
|
val center = Offset(size.width / 2f, size.height / 2f)
|
||||||
|
|
||||||
|
// Grid: elevation rings + azimuth spokes + cardinal labels
|
||||||
|
drawRadarGrid(center, radius, gridColor, textColor, measurer)
|
||||||
|
|
||||||
|
// Track paths (only the above-horizon arc)
|
||||||
|
val pathA = Path()
|
||||||
|
val pathB = Path()
|
||||||
|
var firstVisibleA = true
|
||||||
|
var firstVisibleB = true
|
||||||
|
trackSamples.forEach { sample ->
|
||||||
|
if (sample.elevationA > 0f) {
|
||||||
|
val p = sph2Cart(center, sample.azimuthA, sample.elevationA, radius)
|
||||||
|
if (firstVisibleA) {
|
||||||
|
pathA.moveTo(p.x, p.y)
|
||||||
|
firstVisibleA = false
|
||||||
|
} else {
|
||||||
|
pathA.lineTo(p.x, p.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sample.elevationB > 0f) {
|
||||||
|
val p = sph2Cart(center, sample.azimuthB, sample.elevationB, radius)
|
||||||
|
if (firstVisibleB) {
|
||||||
|
pathB.moveTo(p.x, p.y)
|
||||||
|
firstVisibleB = false
|
||||||
|
} else {
|
||||||
|
pathB.lineTo(p.x, p.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solid line for station A, dashed for station B
|
||||||
|
drawPath(pathA, colorA, style = Stroke(STROKE_WIDTH))
|
||||||
|
drawPath(
|
||||||
|
pathB, colorB,
|
||||||
|
style = Stroke(STROKE_WIDTH, pathEffect = PathEffect.dashPathEffect(floatArrayOf(14f, 10f)))
|
||||||
|
)
|
||||||
|
|
||||||
|
// AOS / LOS markers on both tracks
|
||||||
|
val first = trackSamples.first()
|
||||||
|
val last = trackSamples.last()
|
||||||
|
val aosA = sph2Cart(center, first.azimuthA, first.elevationA, radius)
|
||||||
|
val aosB = sph2Cart(center, first.azimuthB, first.elevationB, radius)
|
||||||
|
val losA = sph2Cart(center, last.azimuthA, last.elevationA, radius)
|
||||||
|
val losB = sph2Cart(center, last.azimuthB, last.elevationB, radius)
|
||||||
|
// AOS: hollow circle, LOS: filled circle
|
||||||
|
if (first.elevationA > 0f) drawCircle(colorA, 7f, aosA, style = Stroke(2.5f))
|
||||||
|
if (first.elevationB > 0f) drawCircle(colorB, 7f, aosB, style = Stroke(2.5f))
|
||||||
|
if (last.elevationA > 0f) drawCircle(colorA, 5f, losA)
|
||||||
|
if (last.elevationB > 0f) drawCircle(colorB, 5f, losB)
|
||||||
|
|
||||||
|
// Center dot
|
||||||
|
drawCircle(gridColor, 4f, center)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legend
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
|
) {
|
||||||
|
Text("● $labelA", color = colorA, fontSize = 12.sp)
|
||||||
|
Text("- - $labelB", color = colorB, fontSize = 12.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DrawScope.drawRadarGrid(
|
||||||
|
center: Offset,
|
||||||
|
radius: Float,
|
||||||
|
color: Color,
|
||||||
|
textColor: Color,
|
||||||
|
measurer: TextMeasurer
|
||||||
|
) {
|
||||||
|
// Elevation rings: 30°, 60°, 90°(center) — outer edge is horizon (0°)
|
||||||
|
val step = radius / CIRCLES
|
||||||
|
for (i in 0 until CIRCLES) {
|
||||||
|
drawCircle(color, radius - step * i, center, style = Stroke(1.5f))
|
||||||
|
}
|
||||||
|
// Cardinal spokes
|
||||||
|
drawLine(color, Offset(center.x - radius, center.y), Offset(center.x + radius, center.y), 1.5f)
|
||||||
|
drawLine(color, Offset(center.x, center.y - radius), Offset(center.x, center.y + radius), 1.5f)
|
||||||
|
// Diagonal spokes (lighter)
|
||||||
|
val diag = radius * 0.7071f
|
||||||
|
drawLine(
|
||||||
|
color.copy(alpha = 0.5f),
|
||||||
|
Offset(center.x - diag, center.y - diag), Offset(center.x + diag, center.y + diag), 1f
|
||||||
|
)
|
||||||
|
drawLine(
|
||||||
|
color.copy(alpha = 0.5f),
|
||||||
|
Offset(center.x - diag, center.y + diag), Offset(center.x + diag, center.y - diag), 1f
|
||||||
|
)
|
||||||
|
|
||||||
|
// Elevation ring labels
|
||||||
|
val style = TextStyle(color = textColor, fontSize = 11.sp)
|
||||||
|
for (i in 0 until CIRCLES) {
|
||||||
|
val deg = 30 * (CIRCLES - i)
|
||||||
|
val y = (center.y - (radius - step * i)) - 24f
|
||||||
|
drawText(measurer, "$deg°", Offset(center.x + 6f, y), style = style)
|
||||||
|
}
|
||||||
|
// Cardinal labels
|
||||||
|
drawText(measurer, "N", Offset(center.x - 8f, center.y - radius - 20f), style = style)
|
||||||
|
drawText(measurer, "E", Offset(center.x + radius + 4f, center.y - 10f), style = style)
|
||||||
|
drawText(measurer, "S", Offset(center.x - 6f, center.y + radius + 2f), style = style)
|
||||||
|
drawText(measurer, "W", Offset(center.x - radius - 24f, center.y - 10f), style = style)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert azimuth (deg, 0=N, clockwise) and elevation (deg) to canvas offset. */
|
||||||
|
private fun sph2Cart(center: Offset, azimDeg: Double, elevDeg: Double, r: Float): Offset {
|
||||||
|
val azimRad = azimDeg * PI / 180.0
|
||||||
|
val elevRad = elevDeg * PI / 180.0
|
||||||
|
val radius = r * (PI / 2 - elevRad) / (PI / 2)
|
||||||
|
return Offset(
|
||||||
|
x = center.x + (radius * cos(PI / 2 - azimRad)).toFloat(),
|
||||||
|
y = center.y - (radius * sin(PI / 2 - azimRad)).toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -58,6 +58,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.rtbishop.look4sat.core.domain.repository.MutualPassData
|
import com.rtbishop.look4sat.core.domain.repository.MutualPassData
|
||||||
|
import com.rtbishop.look4sat.core.domain.repository.TrackSampleData
|
||||||
import com.rtbishop.look4sat.core.presentation.IconCard
|
import com.rtbishop.look4sat.core.presentation.IconCard
|
||||||
import com.rtbishop.look4sat.core.presentation.R
|
import com.rtbishop.look4sat.core.presentation.R
|
||||||
import com.rtbishop.look4sat.core.presentation.ScreenColumn
|
import com.rtbishop.look4sat.core.presentation.ScreenColumn
|
||||||
@@ -326,6 +327,14 @@ private fun MutualContent(
|
|||||||
itemsIndexed(state.mutualPasses) { index, pass ->
|
itemsIndexed(state.mutualPasses) { index, pass ->
|
||||||
val mutualData = MutualPassData(
|
val mutualData = MutualPassData(
|
||||||
samples = pass.elevationSamples,
|
samples = pass.elevationSamples,
|
||||||
|
trackSamples = pass.trackSamples.map {
|
||||||
|
TrackSampleData(
|
||||||
|
azimuthA = it.azimuthA,
|
||||||
|
elevationA = it.elevationA,
|
||||||
|
azimuthB = it.azimuthB,
|
||||||
|
elevationB = it.elevationB
|
||||||
|
)
|
||||||
|
},
|
||||||
startTime = pass.startTime,
|
startTime = pass.startTime,
|
||||||
endTime = pass.endTime,
|
endTime = pass.endTime,
|
||||||
maxElev = maxOf(pass.maxElevationA, pass.maxElevationB, 10.0),
|
maxElev = maxOf(pass.maxElevationA, pass.maxElevationB, 10.0),
|
||||||
@@ -399,6 +408,21 @@ private fun MutualPassCard(
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||||
|
// Dual-station radar track (polar plot)
|
||||||
|
if (pass.trackSamples.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = "双方轨迹(雷达图)",
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
MutualRadarView(
|
||||||
|
trackSamples = pass.trackSamples,
|
||||||
|
labelA = "你",
|
||||||
|
labelB = "友台"
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
}
|
||||||
if (pass.elevationSamples.isNotEmpty()) {
|
if (pass.elevationSamples.isNotEmpty()) {
|
||||||
ElevationCurveChart(
|
ElevationCurveChart(
|
||||||
samples = pass.elevationSamples,
|
samples = pass.elevationSamples,
|
||||||
|
|||||||
+16
-3
@@ -224,16 +224,28 @@ class MutualViewModel(
|
|||||||
val refinedLos = refineEdge(sat, posA, posB, minElevADeg, minElevBDeg, los, step = 1_000L, goingUp = false)
|
val refinedLos = refineEdge(sat, posA, posB, minElevADeg, minElevBDeg, los, step = 1_000L, goingUp = false)
|
||||||
|
|
||||||
val samples = mutableListOf<Pair<Long, Pair<Double, Double>>>()
|
val samples = mutableListOf<Pair<Long, Pair<Double, Double>>>()
|
||||||
|
val tracks = mutableListOf<TrackSample>()
|
||||||
var maxElevA = 0.0
|
var maxElevA = 0.0
|
||||||
var maxElevB = 0.0
|
var maxElevB = 0.0
|
||||||
|
|
||||||
var tSample = refinedAos
|
var tSample = refinedAos
|
||||||
while (tSample <= refinedLos) {
|
while (tSample <= refinedLos) {
|
||||||
val elevA = elevationDeg(sat, posA, tSample)
|
val fullA = sat.getFullPosition(posA, tSample)
|
||||||
val elevB = elevationDeg(sat, posB, tSample)
|
val fullB = sat.getFullPosition(posB, tSample)
|
||||||
|
val elevA = fullA.elevation * 180.0 / PI
|
||||||
|
val elevB = fullB.elevation * 180.0 / PI
|
||||||
if (elevA > maxElevA) maxElevA = elevA
|
if (elevA > maxElevA) maxElevA = elevA
|
||||||
if (elevB > maxElevB) maxElevB = elevB
|
if (elevB > maxElevB) maxElevB = elevB
|
||||||
samples.add(tSample to (elevA to elevB))
|
samples.add(tSample to (elevA to elevB))
|
||||||
|
tracks.add(
|
||||||
|
TrackSample(
|
||||||
|
time = tSample,
|
||||||
|
azimuthA = fullA.azimuth * 180.0 / PI,
|
||||||
|
elevationA = elevA,
|
||||||
|
azimuthB = fullB.azimuth * 180.0 / PI,
|
||||||
|
elevationB = elevB
|
||||||
|
)
|
||||||
|
)
|
||||||
tSample += sampleInterval
|
tSample += sampleInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +258,8 @@ class MutualViewModel(
|
|||||||
endTime = refinedLos,
|
endTime = refinedLos,
|
||||||
maxElevationA = (maxElevA * 10).roundToInt() / 10.0,
|
maxElevationA = (maxElevA * 10).roundToInt() / 10.0,
|
||||||
maxElevationB = (maxElevB * 10).roundToInt() / 10.0,
|
maxElevationB = (maxElevB * 10).roundToInt() / 10.0,
|
||||||
elevationSamples = samples
|
elevationSamples = samples,
|
||||||
|
trackSamples = tracks
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
@@ -72,6 +74,8 @@ import com.rtbishop.look4sat.core.presentation.getDefaultPass
|
|||||||
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
|
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
|
||||||
import com.rtbishop.look4sat.core.presentation.layoutPadding
|
import com.rtbishop.look4sat.core.presentation.layoutPadding
|
||||||
import com.rtbishop.look4sat.feature.mutual.ElevationCurveChart
|
import com.rtbishop.look4sat.feature.mutual.ElevationCurveChart
|
||||||
|
import com.rtbishop.look4sat.feature.mutual.MutualRadarView
|
||||||
|
import com.rtbishop.look4sat.feature.mutual.TrackSample
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private enum class RadarPage(val title: String) {
|
private enum class RadarPage(val title: String) {
|
||||||
@@ -319,6 +323,22 @@ private fun RadarLabel(
|
|||||||
private fun MutualElevationCard(mutualData: MutualPassData, modifier: Modifier = Modifier) {
|
private fun MutualElevationCard(mutualData: MutualPassData, modifier: Modifier = Modifier) {
|
||||||
ElevatedCard(modifier = modifier) {
|
ElevatedCard(modifier = modifier) {
|
||||||
Column(modifier = Modifier.fillMaxSize().padding(8.dp)) {
|
Column(modifier = Modifier.fillMaxSize().padding(8.dp)) {
|
||||||
|
if (mutualData.trackSamples.isNotEmpty()) {
|
||||||
|
MutualRadarView(
|
||||||
|
trackSamples = mutualData.trackSamples.map {
|
||||||
|
TrackSample(
|
||||||
|
time = 0L,
|
||||||
|
azimuthA = it.azimuthA,
|
||||||
|
elevationA = it.elevationA,
|
||||||
|
azimuthB = it.azimuthB,
|
||||||
|
elevationB = it.elevationB
|
||||||
|
)
|
||||||
|
},
|
||||||
|
labelA = mutualData.labelA,
|
||||||
|
labelB = mutualData.labelB
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
}
|
||||||
if (mutualData.samples.isNotEmpty()) {
|
if (mutualData.samples.isNotEmpty()) {
|
||||||
ElevationCurveChart(
|
ElevationCurveChart(
|
||||||
samples = mutualData.samples,
|
samples = mutualData.samples,
|
||||||
|
|||||||
Reference in new issue
Block a user