From 38eea4e8337a0d5d9541424ab6bb775078d4f7ce Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Thu, 16 Jan 2025 14:12:02 -0800 Subject: [PATCH 01/27] WIP --- .../toolkit/scalebarapp/screens/MainScreen.kt | 4 +- .../toolkit/scalebar/ScalebarTests.kt | 4 +- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 109 +++++++++++++++--- .../scalebar/internal/ScalebarViewModel.kt | 16 +++ 4 files changed, 112 insertions(+), 21 deletions(-) diff --git a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt index 6cd29e5be..ea4e15dcb 100644 --- a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt +++ b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt @@ -40,6 +40,7 @@ import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.geoviewcompose.MapView import com.arcgismaps.toolkit.scalebar.Scalebar +import com.arcgismaps.toolkit.scalebar.ScalebarStyle @Composable fun MainScreen(modifier: Modifier) { @@ -79,7 +80,8 @@ fun MainScreen(modifier: Modifier) { maxWidth = 300.0, unitsPerDip = unitsPerDip, viewpoint = viewpoint, - spatialReference = spatialReference + spatialReference = spatialReference, + style = ScalebarStyle.Line, ) } } diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt index 537a9e63a..7903c600c 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt @@ -53,7 +53,7 @@ class ScalebarTests { // Test the scalebar composeTestRule.setContent { LineScalebar( - scaleValue = "1000 km", + label = "1000 km", maxWidth = 300f, colorScheme = ScalebarDefaults.colors(), shapes = ScalebarDefaults.shapes() @@ -78,7 +78,7 @@ class ScalebarTests { contentAlignment = Alignment.BottomCenter ) { LineScalebar( - scaleValue = "1000 km", + label = "1000 km", maxWidth = 300f, colorScheme = ScalebarDefaults.colors(lineColor = Color.Red), shapes = ScalebarDefaults.shapes() diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 26945fcf4..d5a4dcae3 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -26,6 +26,11 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.graphics.Color import androidx.compose.ui.Modifier @@ -35,8 +40,12 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint +import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel +import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel +import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory import com.arcgismaps.toolkit.scalebar.internal.lineWidth import com.arcgismaps.toolkit.scalebar.theme.LabelTypography import com.arcgismaps.toolkit.scalebar.internal.TextAlignment @@ -76,22 +85,80 @@ public fun Scalebar( useGeodeticCalculations: Boolean = true, // `false` to compute scale without a geodesic curve, style: ScalebarStyle = ScalebarStyle.AlternatingBar, // TODO: determining the default ScalebarUnit is not tested - units: ScalebarUnits = if (isMetric()) { - ScalebarUnits.METRIC - } else { - ScalebarUnits.IMPERIAL - }, + units: ScalebarUnits = ScalebarUnits.IMPERIAL, +// if (isMetric()) { +// ScalebarUnits.METRIC +// } else { +// ScalebarUnits.IMPERIAL +// }, colorScheme: ScalebarColors = ScalebarDefaults.colors(), shapes: ScalebarShapes = ScalebarDefaults.shapes(), labelTypography: LabelTypography = ScalebarDefaults.typography() ) { - LineScalebar( - modifier = modifier, - scaleValue = "1,000 km", - maxWidth = maxWidth.toFloat(), - colorScheme = colorScheme, - shapes = shapes + val scalebarViewModel: ScalebarViewModel = viewModel( + factory = ScalebarViewModelFactory( + minScale, + style, + units, + labelTypography, + useGeodeticCalculations + ) ) + + key(unitsPerDip, viewpoint, spatialReference) { + val availableLineDisplayLength = + availableLineDisplayLength(maxWidth, labelTypography, style) + scalebarViewModel.updateScaleBar( + spatialReference, + viewpoint, + unitsPerDip, + availableLineDisplayLength + ) + } + + val isScaleBarUpdated by scalebarViewModel.isScaleBarUpdated + if (isScaleBarUpdated) { + ShowScalebar( + scalebarViewModel.displayLength, + scalebarViewModel.labels, + style, + colorScheme, + shapes, + modifier + ) + } + +// LineScalebar( +// modifier = modifier, +// label = "1,000 km", +// maxWidth = maxWidth.toFloat(), +// colorScheme = colorScheme, +// shapes = shapes +// ) +} + +@Composable +private fun ShowScalebar( + maxWidth: Double, + labels: List, + scalebarStyle: ScalebarStyle, + colorScheme: ScalebarColors, + shapes: ScalebarShapes, + modifier: Modifier = Modifier +) { + when (scalebarStyle) { + ScalebarStyle.AlternatingBar -> TODO() + ScalebarStyle.Bar -> TODO() + ScalebarStyle.DualUnitLine -> TODO() + ScalebarStyle.GraduatedLine -> TODO() + ScalebarStyle.Line -> LineScalebar( + modifier = modifier, + label = labels[0].text, + maxWidth = maxWidth.toFloat(), + colorScheme = colorScheme, + shapes = shapes + ) + } } @Preview @@ -109,7 +176,7 @@ internal fun ScalebarPreview() { * Displays a scalebar with single label and endpoint lines. * * @param modifier The modifier to apply to the layout. - * @param scaleValue The scale value to display. + * @param label The scale value to display. * @param maxWidth The width of the scale bar. * @param colorScheme The color scheme to use. * @param shapes The shape properties to use. @@ -119,7 +186,7 @@ internal fun ScalebarPreview() { @Composable internal fun LineScalebar( modifier: Modifier = Modifier.testTag("LineScalebar"), - scaleValue: String, + label: String, maxWidth: Float, colorScheme: ScalebarColors, shapes: ScalebarShapes @@ -129,11 +196,14 @@ internal fun LineScalebar( val textSizeInPx = with(density) { textSize.toPx() } val totalHeight = scalebarHeight + shadowOffset + textOffset + textSizeInPx - val totalWidth = maxWidth + shadowOffset + pixelAlignment +// val totalWidth = maxWidth + shadowOffset + pixelAlignment Canvas( modifier = modifier - .width(calculateSizeInDp(density, totalWidth)) +// .width(calculateSizeInDp(density, totalWidth)) +// .width(calculateSizeInDp(density, maxWidth)) +// .height(calculateSizeInDp(density, totalHeight)) + .width(maxWidth.dp) .height(calculateSizeInDp(density, totalHeight)) ) { // left line @@ -164,7 +234,7 @@ internal fun LineScalebar( ) // text label drawText( - text = scaleValue, + text = label, textMeasurer = textMeasurer, barEnd = maxWidth, scalebarHeight = scalebarHeight, @@ -178,10 +248,12 @@ internal fun LineScalebar( @Preview(showBackground = true, backgroundColor = 0xff91d2ff) @Composable internal fun LineScaleBarPreview() { - Box(modifier = Modifier.fillMaxSize().padding(4.dp), contentAlignment = Alignment.BottomStart) { + Box(modifier = Modifier + .fillMaxSize() + .padding(4.dp), contentAlignment = Alignment.BottomStart) { LineScalebar( modifier = Modifier, - scaleValue = "1,000 km", + label = "1,000 km", maxWidth = 300f, colorScheme = ScalebarDefaults.colors(lineColor = Color.Red), shapes = ScalebarDefaults.shapes() @@ -211,6 +283,7 @@ private fun availableLineDisplayLength( labelTypography: LabelTypography, style: ScalebarStyle ): Double { + val density = LocalDensity.current return when (style) { ScalebarStyle.AlternatingBar, ScalebarStyle.DualUnitLine, diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt index a2c80f1a7..bbb6cad2d 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import com.arcgismaps.geometry.AngularUnit import com.arcgismaps.geometry.GeodeticCurveType import com.arcgismaps.geometry.GeometryEngine @@ -218,6 +219,21 @@ internal class ScalebarViewModel( } } +internal class ScalebarViewModelFactory( + private val minScale: Double, + private val style: ScalebarStyle, + private val units: ScalebarUnits, + private val labelTypography: LabelTypography, + private val useGeodeticCalculations: Boolean +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(ScalebarViewModel::class.java)) { + return ScalebarViewModel(minScale, style, units, labelTypography, useGeodeticCalculations) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} + /** * Gets the abbreviation for the LinearUnit. * From c72fab4cc1ccc2c2c70341f950ba7a01f24bddf7 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 11:22:53 -0800 Subject: [PATCH 02/27] WIP --- .../toolkit/scalebarapp/screens/MainScreen.kt | 2 + .../arcgismaps/toolkit/scalebar/Scalebar.kt | 64 +++++++++++++------ .../scalebar/internal/ScalebarRenderer.kt | 1 + .../scalebar/internal/ScalebarViewModel.kt | 32 ++++------ 4 files changed, 60 insertions(+), 39 deletions(-) diff --git a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt index ea4e15dcb..8934afe49 100644 --- a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt +++ b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt @@ -41,6 +41,7 @@ import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.geoviewcompose.MapView import com.arcgismaps.toolkit.scalebar.Scalebar import com.arcgismaps.toolkit.scalebar.ScalebarStyle +import com.arcgismaps.toolkit.scalebar.ScalebarUnits @Composable fun MainScreen(modifier: Modifier) { @@ -82,6 +83,7 @@ fun MainScreen(modifier: Modifier) { viewpoint = viewpoint, spatialReference = spatialReference, style = ScalebarStyle.Line, + units = ScalebarUnits.IMPERIAL ) } } diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index d5a4dcae3..567c3510f 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -53,6 +53,7 @@ import com.arcgismaps.toolkit.scalebar.internal.calculateSizeInDp import com.arcgismaps.toolkit.scalebar.internal.drawHorizontalLine import com.arcgismaps.toolkit.scalebar.internal.drawText import com.arcgismaps.toolkit.scalebar.internal.drawVerticalLine +import com.arcgismaps.toolkit.scalebar.internal.labelXPadding import com.arcgismaps.toolkit.scalebar.internal.pixelAlignment import com.arcgismaps.toolkit.scalebar.internal.scalebarHeight import com.arcgismaps.toolkit.scalebar.internal.shadowOffset @@ -85,12 +86,11 @@ public fun Scalebar( useGeodeticCalculations: Boolean = true, // `false` to compute scale without a geodesic curve, style: ScalebarStyle = ScalebarStyle.AlternatingBar, // TODO: determining the default ScalebarUnit is not tested - units: ScalebarUnits = ScalebarUnits.IMPERIAL, -// if (isMetric()) { -// ScalebarUnits.METRIC -// } else { -// ScalebarUnits.IMPERIAL -// }, + units: ScalebarUnits = if (isMetric()) { + ScalebarUnits.METRIC + } else { + ScalebarUnits.IMPERIAL + }, colorScheme: ScalebarColors = ScalebarDefaults.colors(), shapes: ScalebarShapes = ScalebarDefaults.shapes(), labelTypography: LabelTypography = ScalebarDefaults.typography() @@ -116,6 +116,13 @@ public fun Scalebar( ) } + val isUpdateLabels by scalebarViewModel.isUpdateLabels + if (isUpdateLabels) { + scalebarViewModel.updateLabels( + minSegmentWidth(scalebarViewModel.lineMapLength, labelTypography) + ) + } + val isScaleBarUpdated by scalebarViewModel.isScaleBarUpdated if (isScaleBarUpdated) { ShowScalebar( @@ -127,14 +134,6 @@ public fun Scalebar( modifier ) } - -// LineScalebar( -// modifier = modifier, -// label = "1,000 km", -// maxWidth = maxWidth.toFloat(), -// colorScheme = colorScheme, -// shapes = shapes -// ) } @Composable @@ -196,14 +195,11 @@ internal fun LineScalebar( val textSizeInPx = with(density) { textSize.toPx() } val totalHeight = scalebarHeight + shadowOffset + textOffset + textSizeInPx -// val totalWidth = maxWidth + shadowOffset + pixelAlignment + val totalWidth = maxWidth + shadowOffset + pixelAlignment Canvas( modifier = modifier -// .width(calculateSizeInDp(density, totalWidth)) -// .width(calculateSizeInDp(density, maxWidth)) -// .height(calculateSizeInDp(density, totalHeight)) - .width(maxWidth.dp) + .width(calculateSizeInDp(density, totalWidth)) .height(calculateSizeInDp(density, totalHeight)) ) { // left line @@ -272,7 +268,6 @@ private fun isMetric(): Boolean { /** * Returns the display length of the Scalebar line. - * // TODO: The computed length from this function needs to be passed to the updateScalebar function in the ScalebarViewModel. * * @return maxLength to be passed to updateScalebar fun in ScalebarViewModel * @since 200.7.0 @@ -283,7 +278,6 @@ private fun availableLineDisplayLength( labelTypography: LabelTypography, style: ScalebarStyle ): Double { - val density = LocalDensity.current return when (style) { ScalebarStyle.AlternatingBar, ScalebarStyle.DualUnitLine, @@ -299,3 +293,31 @@ private fun availableLineDisplayLength( } } } + +/** + * Returns the minimum segment width required to display the labels without overlapping. + * + * @return minimum segment width + * @since 200.7.0 + */ +@Composable +private fun minSegmentWidth( + lineMapLength: Double, + labelTypography: LabelTypography +): Double { + // The constraining factor is the space required to draw the labels. Create a testString containing the longest + // label, which is usually the one for 'distance' because the other labels will be smaller numbers. + // But if 'distance' is small some of the other labels may use decimals, so allow for each label needing at least + // 3 characters + val minSegmentTestString: String = if (lineMapLength >= 100) { + lineMapLength.toInt().toString() + } else { + "9.9" + } + // Calculate the bounds of the testString to determine its length + val textMeasurer = rememberTextMeasurer() + val maxUnitDisplayWidth = textMeasurer.measure(minSegmentTestString, labelTypography.labelStyle).size.width + // Calculate the minimum segment length to ensure the labels don't overlap; multiply the testString length by 1.5 + // to allow for the right-most label being right-justified whereas the other labels are center-justified + return (maxUnitDisplayWidth * 1.5) + (labelXPadding * 2) +} diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt index d0c78ebcd..f79072bde 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt @@ -32,6 +32,7 @@ internal const val shadowOffset = 3f internal const val scalebarHeight = 20f // Height of the scalebar in pixels internal val textSize = 14.sp internal const val textOffset = 5f +internal const val labelXPadding = 4f // padding between scalebar labels. /** * Calculates the size in dp based on the density of the device. diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt index bbb6cad2d..c34ea4c5d 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt @@ -42,9 +42,6 @@ internal class ScalebarViewModel( private val useGeodeticCalculations: Boolean ) : ViewModel() { - private val labelPaddingX: Float = 4.0f - - private var lineMapLength: Double = 0.0 private var displayUnit: LinearUnit? = null private val geodeticCurveType: GeodeticCurveType = GeodeticCurveType.Geodesic @@ -52,10 +49,17 @@ internal class ScalebarViewModel( private var _isScaleBarUpdated: MutableState = mutableStateOf(false) val isScaleBarUpdated: State = _isScaleBarUpdated + private var _isUpdateLabels: MutableState = mutableStateOf(false) + val isUpdateLabels: State = _isUpdateLabels + private var _displayLength: Double = 0.0 val displayLength: Double get() = _displayLength + private var _lineMapLength: Double = 0.0 + val lineMapLength: Double + get() = _lineMapLength + private var _labels: MutableList = mutableListOf() val labels: List get() = _labels @@ -65,16 +69,8 @@ internal class ScalebarViewModel( * * @since 200.7.0 */ - private fun updateLabels() { + internal fun updateLabels(minSegmentWidth: Double) { val localLabels = mutableListOf() - val minSegmentTestString: String = if (lineMapLength >= 100) { - lineMapLength.toInt().toString() - } else { - "9.9" - } - // TODO: Do we need to calculate this using UnitsPerDip? - val minSegmentWidth = (minSegmentTestString.length * labelTypography.labelStyle.fontSize.value * 1.5) + - (labelPaddingX * 2) val suggestedNumSegments = (displayLength / minSegmentWidth).toInt() @@ -94,7 +90,7 @@ internal class ScalebarViewModel( index = -1, xOffset = 0.0 , yOffset = labelTypography.labelStyle.fontSize.value / 2.0, - text = "0" // TODO: localized this ? + text = "0" ) ) @@ -106,7 +102,7 @@ internal class ScalebarViewModel( val displayUnitAbbr = displayUnit?.getAbbreviation() "${segmentMapLength.toInt()} $displayUnitAbbr" } else { - segmentMapLength.toString() + segmentMapLength.toInt().toString() } val label = ScalebarLabel( @@ -133,11 +129,11 @@ internal class ScalebarViewModel( * * @since 200.7.0 */ - fun updateScaleBar( + internal fun updateScaleBar( spatialReference: SpatialReference?, viewpoint: Viewpoint?, unitsPerDip: Double?, - maxLength: Double + maxLength: Double, ) { if (spatialReference == null || unitsPerDip == null || viewpoint == null) { return @@ -212,10 +208,10 @@ internal class ScalebarViewModel( // update the scalebar with the new values _displayLength = localDisplayLength displayUnit = localDisplayUnit - lineMapLength = localLineMapLength + _lineMapLength = localLineMapLength // update the labels - updateLabels() + _isUpdateLabels.value = true } } From dad7ec52445b5b4864a96d40c0a2926ca03ca0af Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 12:04:48 -0800 Subject: [PATCH 03/27] Update tests --- .../scalebar/ScalebarViewModelTests.kt | 37 ++++++++++++++++--- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 2 +- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt index e85f97b8d..9569a9c14 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt @@ -16,6 +16,12 @@ package com.arcgismaps.toolkit.scalebar +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.sp import com.arcgismaps.geometry.Point @@ -23,8 +29,10 @@ import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.theme.LabelTypography +import com.arcgismaps.toolkit.scalebar.theme.ScalebarDefaults import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest +import org.junit.Rule import org.junit.Test import kotlin.math.roundToInt @@ -34,6 +42,8 @@ import kotlin.math.roundToInt * @since 200.7.0 */ class ScalebarViewModelTests { + @get:Rule + val composeTestRule = createComposeRule() private val esriRedlands = Point(-13046081.04434825, 4036489.208008117, SpatialReference.webMercator()) private val defaultLabelTypography = LabelTypography(labelStyle = TextStyle(fontSize = 11.sp)) @@ -123,12 +133,29 @@ class ScalebarViewModelTests { val lineWidth = 2.0f // this is the value being passed after the available line display length is calculated // in swift, it is calculated as maxWidth - lineWidth val availableLineDisplayLength = maxWidth - lineWidth - viewModel.updateScaleBar(spatialReference, viewpoint, unitsPerDip, availableLineDisplayLength) - assertThat(viewModel.displayLength.roundToInt()).isEqualTo(displayLength) - assertThat(viewModel.labels.size).isEqualTo(labels.size) - for (i in labels.indices) { - assertThat(viewModel.labels[i].text).isEqualTo(labels[i]) + composeTestRule.setContent { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.BottomCenter + ) { + viewModel.updateScaleBar( + spatialReference, + viewpoint, + unitsPerDip, + availableLineDisplayLength + ) + val isUpdateLabels by viewModel.isUpdateLabels + if (isUpdateLabels) { + viewModel.updateLabels(minSegmentWidth(viewModel.lineMapLength, ScalebarDefaults.typography())) + } + + assertThat(viewModel.displayLength.roundToInt()).isEqualTo(displayLength) + assertThat(viewModel.labels.size).isEqualTo(labels.size) + for (i in labels.indices) { + assertThat(viewModel.labels[i].text).isEqualTo(labels[i]) + } + } } } } diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 567c3510f..27df8d0d9 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -301,7 +301,7 @@ private fun availableLineDisplayLength( * @since 200.7.0 */ @Composable -private fun minSegmentWidth( +internal fun minSegmentWidth( lineMapLength: Double, labelTypography: LabelTypography ): Double { From 317352cf60bb9af69fc81999a1f02cad16a80bb1 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 13:00:19 -0800 Subject: [PATCH 04/27] update maxLength calc --- .../com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt | 2 +- .../src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt index 8934afe49..70865ae60 100644 --- a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt +++ b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt @@ -78,7 +78,7 @@ fun MainScreen(modifier: Modifier) { .align(Alignment.BottomStart) ) { Scalebar( - maxWidth = 300.0, + maxWidth = 175.0, unitsPerDip = unitsPerDip, viewpoint = viewpoint, spatialReference = spatialReference, diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 27df8d0d9..fb124565a 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -125,8 +125,9 @@ public fun Scalebar( val isScaleBarUpdated by scalebarViewModel.isScaleBarUpdated if (isScaleBarUpdated) { + val density = LocalDensity.current ShowScalebar( - scalebarViewModel.displayLength, + scalebarViewModel.displayLength * density.density, scalebarViewModel.labels, style, colorScheme, From fe2ec0bc6a02bc65a30c9d8e68ebc19f933aba30 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 13:36:45 -0800 Subject: [PATCH 05/27] update test --- .../java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt index 7903c600c..22fc36454 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt @@ -75,7 +75,6 @@ class ScalebarTests { composeTestRule.setContent { Box( modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.BottomCenter ) { LineScalebar( label = "1000 km", From 72de31b8c6fdfd4d8db75581f3721a12d792aa03 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 15:50:56 -0800 Subject: [PATCH 06/27] fix bug where the scalebar was not displaying fraction values --- .../toolkit/scalebar/internal/ScalebarUtils.kt | 16 ++++++++++++++++ .../scalebar/internal/ScalebarViewModel.kt | 10 +++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt index cf9bdc88b..019374750 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt @@ -113,4 +113,20 @@ internal object ScalebarUtils { val options = segmentOptions(multiplier) return options.lastOrNull { it <= maxNumSegments } ?: 1 } + + /** + * Formats a double to a string. + * If the double has no decimal part, it will be displayed as an int. + * If the second digit after the decimal is zero, it will be displayed with one decimal place. + * Otherwise, it will be displayed with two decimal places. + * + * @since 200.7.0 + */ + fun Double.format(): String { + return when { + this % 1.0 == 0.0 -> this.toInt().toString() // Display as int if no decimal part + (this * 10) % 1.0 == 0.0 -> "%.1f".format(this) // Display one decimal place if last digit is zero + else -> "%.2f".format(this) // Display two decimal places + } + } } diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt index c34ea4c5d..edfe27822 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt @@ -32,6 +32,7 @@ import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.ScalebarStyle import com.arcgismaps.toolkit.scalebar.ScalebarUnits +import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.format import com.arcgismaps.toolkit.scalebar.theme.LabelTypography internal class ScalebarViewModel( @@ -70,8 +71,6 @@ internal class ScalebarViewModel( * @since 200.7.0 */ internal fun updateLabels(minSegmentWidth: Double) { - val localLabels = mutableListOf() - val suggestedNumSegments = (displayLength / minSegmentWidth).toInt() // Cap segments at 4 @@ -84,6 +83,7 @@ internal class ScalebarViewModel( val segmentScreenLength = displayLength / numSegments var currSegmentX = 0.0 + val localLabels = mutableListOf() localLabels.add( ScalebarLabel( @@ -96,13 +96,13 @@ internal class ScalebarViewModel( for (index in 0 until numSegments) { currSegmentX += segmentScreenLength - val segmentMapLength = (segmentScreenLength * (index + 1) / displayLength) * lineMapLength + val segmentMapLength: Double = (segmentScreenLength * (index + 1) / displayLength) * lineMapLength val segmentText: String = if (index == numSegments - 1 && displayUnit != null) { val displayUnitAbbr = displayUnit?.getAbbreviation() - "${segmentMapLength.toInt()} $displayUnitAbbr" + "${segmentMapLength.format()} $displayUnitAbbr" } else { - segmentMapLength.toInt().toString() + segmentMapLength.format() } val label = ScalebarLabel( From 387b32e8e12b928647e1fde071b4cecd31dbb5e3 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 16:23:58 -0800 Subject: [PATCH 07/27] optimize imports --- .../java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index fb124565a..7e8540f73 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -26,14 +26,11 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.graphics.Color import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag @@ -46,19 +43,19 @@ import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory -import com.arcgismaps.toolkit.scalebar.internal.lineWidth -import com.arcgismaps.toolkit.scalebar.theme.LabelTypography import com.arcgismaps.toolkit.scalebar.internal.TextAlignment import com.arcgismaps.toolkit.scalebar.internal.calculateSizeInDp import com.arcgismaps.toolkit.scalebar.internal.drawHorizontalLine import com.arcgismaps.toolkit.scalebar.internal.drawText import com.arcgismaps.toolkit.scalebar.internal.drawVerticalLine import com.arcgismaps.toolkit.scalebar.internal.labelXPadding +import com.arcgismaps.toolkit.scalebar.internal.lineWidth import com.arcgismaps.toolkit.scalebar.internal.pixelAlignment import com.arcgismaps.toolkit.scalebar.internal.scalebarHeight import com.arcgismaps.toolkit.scalebar.internal.shadowOffset import com.arcgismaps.toolkit.scalebar.internal.textOffset import com.arcgismaps.toolkit.scalebar.internal.textSize +import com.arcgismaps.toolkit.scalebar.theme.LabelTypography import com.arcgismaps.toolkit.scalebar.theme.ScalebarColors import com.arcgismaps.toolkit.scalebar.theme.ScalebarDefaults import com.arcgismaps.toolkit.scalebar.theme.ScalebarShapes From fd887a78c80c793d57ad6cb021a6628279499b68 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 17:03:15 -0800 Subject: [PATCH 08/27] Refactor fun names --- .../toolkit/scalebar/ScalebarViewModelTests.kt | 4 ++-- .../com/arcgismaps/toolkit/scalebar/Scalebar.kt | 16 ++++++++-------- .../scalebar/internal/ScalebarRenderer.kt | 7 +++++++ .../scalebar/internal/ScalebarViewModel.kt | 5 +++-- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt index 9569a9c14..80614fe0c 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt @@ -139,7 +139,7 @@ class ScalebarViewModelTests { modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter ) { - viewModel.updateScaleBar( + viewModel.computeScalebarProperties( spatialReference, viewpoint, unitsPerDip, @@ -147,7 +147,7 @@ class ScalebarViewModelTests { ) val isUpdateLabels by viewModel.isUpdateLabels if (isUpdateLabels) { - viewModel.updateLabels(minSegmentWidth(viewModel.lineMapLength, ScalebarDefaults.typography())) + viewModel.updateLabels(measureMinSegmentWidth(viewModel.lineMapLength, ScalebarDefaults.typography())) } assertThat(viewModel.displayLength.roundToInt()).isEqualTo(displayLength) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 7e8540f73..acd87e2e0 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -45,6 +45,7 @@ import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory import com.arcgismaps.toolkit.scalebar.internal.TextAlignment import com.arcgismaps.toolkit.scalebar.internal.calculateSizeInDp +import com.arcgismaps.toolkit.scalebar.internal.calculateSizeInPixels import com.arcgismaps.toolkit.scalebar.internal.drawHorizontalLine import com.arcgismaps.toolkit.scalebar.internal.drawText import com.arcgismaps.toolkit.scalebar.internal.drawVerticalLine @@ -104,8 +105,8 @@ public fun Scalebar( key(unitsPerDip, viewpoint, spatialReference) { val availableLineDisplayLength = - availableLineDisplayLength(maxWidth, labelTypography, style) - scalebarViewModel.updateScaleBar( + measureAvailableLineDisplayLength(maxWidth, labelTypography, style) + scalebarViewModel.computeScalebarProperties( spatialReference, viewpoint, unitsPerDip, @@ -115,16 +116,15 @@ public fun Scalebar( val isUpdateLabels by scalebarViewModel.isUpdateLabels if (isUpdateLabels) { - scalebarViewModel.updateLabels( - minSegmentWidth(scalebarViewModel.lineMapLength, labelTypography) - ) + val minSegmentWidth = measureMinSegmentWidth(scalebarViewModel.lineMapLength, labelTypography) + scalebarViewModel.updateLabels(minSegmentWidth) } val isScaleBarUpdated by scalebarViewModel.isScaleBarUpdated if (isScaleBarUpdated) { val density = LocalDensity.current ShowScalebar( - scalebarViewModel.displayLength * density.density, + calculateSizeInPixels(density, scalebarViewModel.displayLength), scalebarViewModel.labels, style, colorScheme, @@ -271,7 +271,7 @@ private fun isMetric(): Boolean { * @since 200.7.0 */ @Composable -private fun availableLineDisplayLength( +private fun measureAvailableLineDisplayLength( maxWidth: Double, labelTypography: LabelTypography, style: ScalebarStyle @@ -299,7 +299,7 @@ private fun availableLineDisplayLength( * @since 200.7.0 */ @Composable -internal fun minSegmentWidth( +internal fun measureMinSegmentWidth( lineMapLength: Double, labelTypography: LabelTypography ): Double { diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt index f79072bde..e1f34cde4 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt @@ -43,6 +43,13 @@ internal fun calculateSizeInDp(density: Density, value: Float) = with(density) { value.toDp() } +/** + * Calculates the size in pixels based on the density of the device. + * + * @since 200.7.0 + */ +internal fun calculateSizeInPixels(density: Density, value: Double) = value * density.density + /** * Used to align the text relative to the scalebar. * diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt index edfe27822..e8b3ef6c7 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt @@ -125,11 +125,12 @@ internal class ScalebarViewModel( } /** - * Updates the Scalebar with the new values. + * Computes the Scalebar properties namely DisplayLength, DisplayUnit and LineMapLength + * with the new values of the given parameters. * * @since 200.7.0 */ - internal fun updateScaleBar( + internal fun computeScalebarProperties( spatialReference: SpatialReference?, viewpoint: Viewpoint?, unitsPerDip: Double?, From ff0c93cc28d8776b9ab399785c8ffd6e003e0b6c Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Fri, 17 Jan 2025 17:51:19 -0800 Subject: [PATCH 09/27] add doc --- .../main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index acd87e2e0..f9a22ec77 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -104,8 +104,10 @@ public fun Scalebar( ) key(unitsPerDip, viewpoint, spatialReference) { + // Measure the available line display length val availableLineDisplayLength = measureAvailableLineDisplayLength(maxWidth, labelTypography, style) + // compute the scalebar properties scalebarViewModel.computeScalebarProperties( spatialReference, viewpoint, @@ -115,12 +117,17 @@ public fun Scalebar( } val isUpdateLabels by scalebarViewModel.isUpdateLabels + // invoked after the scalebar properties are computed if (isUpdateLabels) { + // Measure the minimum segment width required to display the labels without overlapping val minSegmentWidth = measureMinSegmentWidth(scalebarViewModel.lineMapLength, labelTypography) + // update the label text and offsets scalebarViewModel.updateLabels(minSegmentWidth) } val isScaleBarUpdated by scalebarViewModel.isScaleBarUpdated + // invoked after the scalebar properties displayLength, displayUnit are computed + // and the labels are updated if (isScaleBarUpdated) { val density = LocalDensity.current ShowScalebar( From 7ed31a2011f484e5cb121b229e57d162c083680f Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 10:59:34 -0800 Subject: [PATCH 10/27] address code review comments --- .../java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 10 +++++----- .../toolkit/scalebar/internal/ScalebarRenderer.kt | 7 ------- .../toolkit/scalebar/internal/ScalebarUtils.kt | 8 ++++++++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index f9a22ec77..dd2a05cc0 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -41,11 +41,11 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel +import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory import com.arcgismaps.toolkit.scalebar.internal.TextAlignment import com.arcgismaps.toolkit.scalebar.internal.calculateSizeInDp -import com.arcgismaps.toolkit.scalebar.internal.calculateSizeInPixels import com.arcgismaps.toolkit.scalebar.internal.drawHorizontalLine import com.arcgismaps.toolkit.scalebar.internal.drawText import com.arcgismaps.toolkit.scalebar.internal.drawVerticalLine @@ -131,7 +131,7 @@ public fun Scalebar( if (isScaleBarUpdated) { val density = LocalDensity.current ShowScalebar( - calculateSizeInPixels(density, scalebarViewModel.displayLength), + scalebarViewModel.displayLength.toPx(density), scalebarViewModel.labels, style, colorScheme, @@ -181,7 +181,7 @@ internal fun ScalebarPreview() { * * @param modifier The modifier to apply to the layout. * @param label The scale value to display. - * @param maxWidth The width of the scale bar. + * @param maxWidth The width of the scalebar in pixels. * @param colorScheme The color scheme to use. * @param shapes The shape properties to use. * @@ -272,7 +272,7 @@ private fun isMetric(): Boolean { } /** - * Returns the display length of the Scalebar line. + * Returns the display length in pixels of the Scalebar line. * * @return maxLength to be passed to updateScalebar fun in ScalebarViewModel * @since 200.7.0 @@ -300,7 +300,7 @@ private fun measureAvailableLineDisplayLength( } /** - * Returns the minimum segment width required to display the labels without overlapping. + * Returns the minimum segment width in pixels required to display the labels without overlapping. * * @return minimum segment width * @since 200.7.0 diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt index e1f34cde4..f79072bde 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarRenderer.kt @@ -43,13 +43,6 @@ internal fun calculateSizeInDp(density: Density, value: Float) = with(density) { value.toDp() } -/** - * Calculates the size in pixels based on the density of the device. - * - * @since 200.7.0 - */ -internal fun calculateSizeInPixels(density: Density, value: Double) = value * density.density - /** * Used to align the text relative to the scalebar. * diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt index 019374750..c4766e382 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarUtils.kt @@ -17,6 +17,7 @@ */ package com.arcgismaps.toolkit.scalebar.internal +import androidx.compose.ui.unit.Density import kotlin.math.floor import kotlin.math.log10 import kotlin.math.pow @@ -129,4 +130,11 @@ internal object ScalebarUtils { else -> "%.2f".format(this) // Display two decimal places } } + + /** + * Calculates the size in pixels based on the density of the device. + * + * @since 200.7.0 + */ + fun Double.toPx(density: Density): Double = this * density.density } From ef93e8b1051ffcb793d12adf316d7dd4b9ff8d6f Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 11:15:13 -0800 Subject: [PATCH 11/27] make measureMinSegmentWidth private --- .../src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index dd2a05cc0..a0c46f82d 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -306,7 +306,7 @@ private fun measureAvailableLineDisplayLength( * @since 200.7.0 */ @Composable -internal fun measureMinSegmentWidth( +private fun measureMinSegmentWidth( lineMapLength: Double, labelTypography: LabelTypography ): Double { From a62a3d13dc9ffe57815ff3ac9572bc745b4b55c0 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 11:42:41 -0800 Subject: [PATCH 12/27] remove showScalebar fun --- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 45 ++++++------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index a0c46f82d..840690f60 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -130,38 +130,19 @@ public fun Scalebar( // and the labels are updated if (isScaleBarUpdated) { val density = LocalDensity.current - ShowScalebar( - scalebarViewModel.displayLength.toPx(density), - scalebarViewModel.labels, - style, - colorScheme, - shapes, - modifier - ) - } -} - -@Composable -private fun ShowScalebar( - maxWidth: Double, - labels: List, - scalebarStyle: ScalebarStyle, - colorScheme: ScalebarColors, - shapes: ScalebarShapes, - modifier: Modifier = Modifier -) { - when (scalebarStyle) { - ScalebarStyle.AlternatingBar -> TODO() - ScalebarStyle.Bar -> TODO() - ScalebarStyle.DualUnitLine -> TODO() - ScalebarStyle.GraduatedLine -> TODO() - ScalebarStyle.Line -> LineScalebar( - modifier = modifier, - label = labels[0].text, - maxWidth = maxWidth.toFloat(), - colorScheme = colorScheme, - shapes = shapes - ) + when (style) { + ScalebarStyle.AlternatingBar -> TODO() + ScalebarStyle.Bar -> TODO() + ScalebarStyle.DualUnitLine -> TODO() + ScalebarStyle.GraduatedLine -> TODO() + ScalebarStyle.Line -> LineScalebar( + modifier = modifier, + label = scalebarViewModel.labels[0].text, + maxWidth = scalebarViewModel.displayLength.toPx(density).toFloat(), + colorScheme = colorScheme, + shapes = shapes + ) + } } } From 824558e1c57572eb74048d861bfe6e91627abafe Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 11:44:07 -0800 Subject: [PATCH 13/27] optimize import --- .../src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 840690f60..6b0ec2ec9 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint -import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory From 698603c36fb289853a130fa995121aa9a66f30d6 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 13:00:15 -0800 Subject: [PATCH 14/27] Revert "optimize import" This reverts commit 824558e1c57572eb74048d861bfe6e91627abafe. --- .../src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 6b0ec2ec9..840690f60 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint +import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory From 4c61fae31e353acfbae2c2f0e4d1207e93056418 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 13:00:38 -0800 Subject: [PATCH 15/27] Revert "remove showScalebar fun" This reverts commit a62a3d13dc9ffe57815ff3ac9572bc745b4b55c0. --- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 840690f60..a0c46f82d 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -130,19 +130,38 @@ public fun Scalebar( // and the labels are updated if (isScaleBarUpdated) { val density = LocalDensity.current - when (style) { - ScalebarStyle.AlternatingBar -> TODO() - ScalebarStyle.Bar -> TODO() - ScalebarStyle.DualUnitLine -> TODO() - ScalebarStyle.GraduatedLine -> TODO() - ScalebarStyle.Line -> LineScalebar( - modifier = modifier, - label = scalebarViewModel.labels[0].text, - maxWidth = scalebarViewModel.displayLength.toPx(density).toFloat(), - colorScheme = colorScheme, - shapes = shapes - ) - } + ShowScalebar( + scalebarViewModel.displayLength.toPx(density), + scalebarViewModel.labels, + style, + colorScheme, + shapes, + modifier + ) + } +} + +@Composable +private fun ShowScalebar( + maxWidth: Double, + labels: List, + scalebarStyle: ScalebarStyle, + colorScheme: ScalebarColors, + shapes: ScalebarShapes, + modifier: Modifier = Modifier +) { + when (scalebarStyle) { + ScalebarStyle.AlternatingBar -> TODO() + ScalebarStyle.Bar -> TODO() + ScalebarStyle.DualUnitLine -> TODO() + ScalebarStyle.GraduatedLine -> TODO() + ScalebarStyle.Line -> LineScalebar( + modifier = modifier, + label = labels[0].text, + maxWidth = maxWidth.toFloat(), + colorScheme = colorScheme, + shapes = shapes + ) } } From b5898618181d62eae38247994659815cff279810 Mon Sep 17 00:00:00 2001 From: Puneet Prakash Date: Tue, 21 Jan 2025 13:01:12 -0800 Subject: [PATCH 16/27] Revert "make measureMinSegmentWidth private" This reverts commit ef93e8b1051ffcb793d12adf316d7dd4b9ff8d6f. --- .../src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index a0c46f82d..dd2a05cc0 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -306,7 +306,7 @@ private fun measureAvailableLineDisplayLength( * @since 200.7.0 */ @Composable -private fun measureMinSegmentWidth( +internal fun measureMinSegmentWidth( lineMapLength: Double, labelTypography: LabelTypography ): Double { From 2a7bd00818f824e48cca50a0f2ba2882d9c01c91 Mon Sep 17 00:00:00 2001 From: Erick Date: Tue, 21 Jan 2025 13:56:50 -0800 Subject: [PATCH 17/27] working proto --- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 68 +++++++------ .../scalebar/internal/ScalebarViewModel.kt | 95 ++++++++----------- 2 files changed, 74 insertions(+), 89 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index dd2a05cc0..f5015a042 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -27,7 +27,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -41,6 +43,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel +import com.arcgismaps.toolkit.scalebar.internal.ScalebarProperties import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory @@ -93,6 +96,7 @@ public fun Scalebar( shapes: ScalebarShapes = ScalebarDefaults.shapes(), labelTypography: LabelTypography = ScalebarDefaults.typography() ) { + var scalebarProperties: ScalebarProperties? by rememberSaveable { mutableStateOf(null) } val scalebarViewModel: ScalebarViewModel = viewModel( factory = ScalebarViewModelFactory( minScale, @@ -103,41 +107,30 @@ public fun Scalebar( ) ) - key(unitsPerDip, viewpoint, spatialReference) { - // Measure the available line display length - val availableLineDisplayLength = - measureAvailableLineDisplayLength(maxWidth, labelTypography, style) - // compute the scalebar properties - scalebarViewModel.computeScalebarProperties( - spatialReference, - viewpoint, - unitsPerDip, - availableLineDisplayLength - ) - } - - val isUpdateLabels by scalebarViewModel.isUpdateLabels - // invoked after the scalebar properties are computed - if (isUpdateLabels) { + val availableLineDisplayLength = measureAvailableLineDisplayLength(maxWidth, labelTypography, style) + scalebarProperties = scalebarViewModel.computeScalebarProperties( + spatialReference, + viewpoint, + unitsPerDip, + availableLineDisplayLength + ) + if (scalebarProperties != null) { + val localScalebarProperties = scalebarProperties!! // Measure the minimum segment width required to display the labels without overlapping - val minSegmentWidth = measureMinSegmentWidth(scalebarViewModel.lineMapLength, labelTypography) - // update the label text and offsets - scalebarViewModel.updateLabels(minSegmentWidth) - } + val minSegmentWidth = measureMinSegmentWidth(localScalebarProperties.displayLength, labelTypography) + val scalebarLabels = scalebarViewModel.updateLabels(minSegmentWidth) - val isScaleBarUpdated by scalebarViewModel.isScaleBarUpdated - // invoked after the scalebar properties displayLength, displayUnit are computed - // and the labels are updated - if (isScaleBarUpdated) { val density = LocalDensity.current - ShowScalebar( - scalebarViewModel.displayLength.toPx(density), - scalebarViewModel.labels, - style, - colorScheme, - shapes, - modifier - ) + if (scalebarLabels.isNotEmpty()) { + ShowScalebar( + localScalebarProperties.displayLength.toPx(density), + scalebarLabels, + style, + colorScheme, + shapes, + modifier + ) + } } } @@ -249,9 +242,11 @@ internal fun LineScalebar( @Preview(showBackground = true, backgroundColor = 0xff91d2ff) @Composable internal fun LineScaleBarPreview() { - Box(modifier = Modifier - .fillMaxSize() - .padding(4.dp), contentAlignment = Alignment.BottomStart) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(4.dp), contentAlignment = Alignment.BottomStart + ) { LineScalebar( modifier = Modifier, label = "1,000 km", @@ -292,6 +287,7 @@ private fun measureAvailableLineDisplayLength( val maxUnitDisplayWidth = textMeasurer.measure(" km", labelTypography.labelStyle).size.width maxWidth - (lineWidth / 2.0f) - maxUnitDisplayWidth } + ScalebarStyle.Bar, ScalebarStyle.Line -> { maxWidth - lineWidth diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt index e8b3ef6c7..8f092719b 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt @@ -17,9 +17,6 @@ */ package com.arcgismaps.toolkit.scalebar.internal -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.arcgismaps.geometry.AngularUnit @@ -43,52 +40,37 @@ internal class ScalebarViewModel( private val useGeodeticCalculations: Boolean ) : ViewModel() { - private var displayUnit: LinearUnit? = null - + private var _scalebarProperties: ScalebarProperties = ScalebarProperties( + displayLength = 0.0, + displayUnit = LinearUnit.meters, + lineMapLength = 0.0 + ) private val geodeticCurveType: GeodeticCurveType = GeodeticCurveType.Geodesic - private var _isScaleBarUpdated: MutableState = mutableStateOf(false) - val isScaleBarUpdated: State = _isScaleBarUpdated - - private var _isUpdateLabels: MutableState = mutableStateOf(false) - val isUpdateLabels: State = _isUpdateLabels - - private var _displayLength: Double = 0.0 - val displayLength: Double - get() = _displayLength - - private var _lineMapLength: Double = 0.0 - val lineMapLength: Double - get() = _lineMapLength - - private var _labels: MutableList = mutableListOf() - val labels: List - get() = _labels - /** * Updates the labels for the Scalebar. * * @since 200.7.0 */ - internal fun updateLabels(minSegmentWidth: Double) { - val suggestedNumSegments = (displayLength / minSegmentWidth).toInt() + internal fun updateLabels(minSegmentWidth: Double): List { + val suggestedNumSegments = (_scalebarProperties.displayLength / minSegmentWidth).toInt() // Cap segments at 4 val maxNumSegments = minOf(suggestedNumSegments, 4) val numSegments = ScalebarUtils.numSegments( - lineMapLength, + _scalebarProperties.lineMapLength, maxNumSegments ) - val segmentScreenLength = displayLength / numSegments + val segmentScreenLength = _scalebarProperties.displayLength / numSegments var currSegmentX = 0.0 val localLabels = mutableListOf() localLabels.add( ScalebarLabel( index = -1, - xOffset = 0.0 , + xOffset = 0.0, yOffset = labelTypography.labelStyle.fontSize.value / 2.0, text = "0" ) @@ -96,14 +78,16 @@ internal class ScalebarViewModel( for (index in 0 until numSegments) { currSegmentX += segmentScreenLength - val segmentMapLength: Double = (segmentScreenLength * (index + 1) / displayLength) * lineMapLength + val segmentMapLength: Double = + (segmentScreenLength * (index + 1) / _scalebarProperties.displayLength) * _scalebarProperties.lineMapLength - val segmentText: String = if (index == numSegments - 1 && displayUnit != null) { - val displayUnitAbbr = displayUnit?.getAbbreviation() - "${segmentMapLength.format()} $displayUnitAbbr" - } else { - segmentMapLength.format() - } + val segmentText: String = + if (index == numSegments - 1 /*&& _scalebarProperties.value.displayUnit != null*/) { + val displayUnitAbbr = _scalebarProperties.displayUnit.getAbbreviation() + "${segmentMapLength.format()} $displayUnitAbbr" + } else { + segmentMapLength.format() + } val label = ScalebarLabel( index = index, @@ -114,14 +98,15 @@ internal class ScalebarViewModel( localLabels.add(label) } - if (style == ScalebarStyle.Bar || style == ScalebarStyle.Line) { - localLabels.lastOrNull()?.let { - _labels = mutableListOf(it) + return if (style == ScalebarStyle.Bar || style == ScalebarStyle.Line) { + if (localLabels.isNotEmpty()) { + mutableListOf(localLabels.last()) + } else { + mutableListOf() } } else { - _labels = localLabels + localLabels } - _isScaleBarUpdated.value = true } /** @@ -135,13 +120,13 @@ internal class ScalebarViewModel( viewpoint: Viewpoint?, unitsPerDip: Double?, maxLength: Double, - ) { + ): ScalebarProperties? { if (spatialReference == null || unitsPerDip == null || viewpoint == null) { - return + return null } if (minScale > 0 && viewpoint.targetScale >= minScale || unitsPerDip.isNaN()) { - return + return null } val mapCenter = viewpoint.targetGeometry.extent.center @@ -181,7 +166,7 @@ internal class ScalebarViewModel( localDisplayUnit = units.linearUnitsForDistance(roundNumberDistance) localLineMapLength = baseUnits.convertTo(localDisplayUnit, roundNumberDistance) } else { - val srUnit = spatialReference.unit as? LinearUnit ?: return + val srUnit = spatialReference.unit as? LinearUnit ?: return null val baseUnits = units.baseLinearUnit val lenAvail = srUnit.convertTo( baseUnits, @@ -203,19 +188,23 @@ internal class ScalebarViewModel( } if (!localDisplayLength.isFinite() || localDisplayLength.isNaN()) { - return + return null } - - // update the scalebar with the new values - _displayLength = localDisplayLength - displayUnit = localDisplayUnit - _lineMapLength = localLineMapLength - - // update the labels - _isUpdateLabels.value = true + _scalebarProperties = ScalebarProperties( + displayLength = localDisplayLength, + displayUnit = localDisplayUnit, + lineMapLength = localLineMapLength + ) + return _scalebarProperties } } +internal data class ScalebarProperties( + val displayLength: Double, + val displayUnit: LinearUnit, + val lineMapLength: Double +) + internal class ScalebarViewModelFactory( private val minScale: Double, private val style: ScalebarStyle, From 34cfa11208c7891195383dc5720d9c778e6e6a8b Mon Sep 17 00:00:00 2001 From: Erick Date: Tue, 21 Jan 2025 15:39:33 -0800 Subject: [PATCH 18/27] Update Scalebar.kt --- .../main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 1aa3bc63c..3dbbd215a 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -21,7 +21,9 @@ package com.arcgismaps.toolkit.scalebar import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -32,6 +34,7 @@ import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.LineScalebar import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel +import com.arcgismaps.toolkit.scalebar.internal.ScalebarProperties import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory From cbbc8a54d13589fa6ddf34fa43d96d437cda5336 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 09:02:44 -0800 Subject: [PATCH 19/27] working protto 2 --- .../toolkit/scalebarapp/screens/MainScreen.kt | 6 +- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 61 +++++++++---------- ...{ScalebarViewModel.kt => ScalebarState.kt} | 48 +++++---------- 3 files changed, 47 insertions(+), 68 deletions(-) rename toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/{ScalebarViewModel.kt => ScalebarState.kt} (81%) diff --git a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt index 70865ae60..2d3d2a7e0 100644 --- a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt +++ b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt @@ -49,9 +49,9 @@ fun MainScreen(modifier: Modifier) { mutableStateOf( ArcGISMap(BasemapStyle.ArcGISTopographic).apply { initialViewpoint = Viewpoint( - latitude = 39.8, - longitude = -98.6, - scale = 10e7 + latitude = 33.751765, + longitude = -117.935518, + scale = 44016.65596653387 ) } ) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 3dbbd215a..fe4becfe3 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -20,24 +20,20 @@ package com.arcgismaps.toolkit.scalebar import android.content.Context import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.tooling.preview.Preview -import androidx.lifecycle.viewmodel.compose.viewModel import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.LineScalebar import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel -import com.arcgismaps.toolkit.scalebar.internal.ScalebarProperties +import com.arcgismaps.toolkit.scalebar.internal.ScalebarState import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx -import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel -import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModelFactory import com.arcgismaps.toolkit.scalebar.internal.labelXPadding import com.arcgismaps.toolkit.scalebar.internal.lineWidth import com.arcgismaps.toolkit.scalebar.theme.LabelTypography @@ -77,42 +73,44 @@ public fun Scalebar( shapes: ScalebarShapes = ScalebarDefaults.shapes(), labelTypography: LabelTypography = ScalebarDefaults.typography() ) { - var scalebarProperties: ScalebarProperties? by rememberSaveable { mutableStateOf(null) } - val scalebarViewModel: ScalebarViewModel = viewModel( - factory = ScalebarViewModelFactory( + val scalebarState = remember(minScale, style, units, labelTypography, useGeodeticCalculations) { + ScalebarState( minScale, style, units, labelTypography, useGeodeticCalculations ) - ) + } val availableLineDisplayLength = measureAvailableLineDisplayLength(maxWidth, labelTypography, style) - scalebarProperties = scalebarViewModel.computeScalebarProperties( - spatialReference, - viewpoint, - unitsPerDip, - availableLineDisplayLength - ) - if (scalebarProperties != null) { - val localScalebarProperties = scalebarProperties!! - // Measure the minimum segment width required to display the labels without overlapping - val minSegmentWidth = measureMinSegmentWidth(localScalebarProperties.displayLength, labelTypography) - val scalebarLabels = scalebarViewModel.updateLabels(minSegmentWidth) - val density = LocalDensity.current - if (scalebarLabels.isNotEmpty()) { - ShowScalebar( - localScalebarProperties.displayLength.toPx(density), - scalebarLabels, - style, - colorScheme, - shapes, - modifier + val scalebarProperties by remember(spatialReference, viewpoint, unitsPerDip, availableLineDisplayLength) { + derivedStateOf { + scalebarState.computeScalebarProperties( + spatialReference, + viewpoint, + unitsPerDip, + availableLineDisplayLength ) } } + + // Measure the minimum segment width required to display the labels without overlapping + val minSegmentWidth = measureMinSegmentWidth(scalebarProperties?.displayLength ?: 0.0, labelTypography) + val scalebarLabels = scalebarState.updateLabels(scalebarProperties, minSegmentWidth) + + if (scalebarLabels.isNotEmpty()) { + val density = LocalDensity.current + ShowScalebar( + scalebarProperties?.displayLength?.toPx(density) ?: 0.0, + scalebarLabels, + style, + colorScheme, + shapes, + modifier + ) + } } @Composable @@ -180,6 +178,7 @@ private fun measureAvailableLineDisplayLength( val maxUnitDisplayWidth = textMeasurer.measure(" km", labelTypography.labelStyle).size.width maxWidth - (lineWidth / 2.0f) - maxUnitDisplayWidth } + ScalebarStyle.Bar, ScalebarStyle.Line -> { maxWidth - lineWidth diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt similarity index 81% rename from toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt rename to toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt index 8f092719b..307ecd44f 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarViewModel.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt @@ -17,8 +17,6 @@ */ package com.arcgismaps.toolkit.scalebar.internal -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import com.arcgismaps.geometry.AngularUnit import com.arcgismaps.geometry.GeodeticCurveType import com.arcgismaps.geometry.GeometryEngine @@ -32,19 +30,14 @@ import com.arcgismaps.toolkit.scalebar.ScalebarUnits import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.format import com.arcgismaps.toolkit.scalebar.theme.LabelTypography -internal class ScalebarViewModel( + +internal class ScalebarState( private val minScale: Double, private val style: ScalebarStyle, private val units: ScalebarUnits, private val labelTypography: LabelTypography, private val useGeodeticCalculations: Boolean -) : ViewModel() { - - private var _scalebarProperties: ScalebarProperties = ScalebarProperties( - displayLength = 0.0, - displayUnit = LinearUnit.meters, - lineMapLength = 0.0 - ) +) { private val geodeticCurveType: GeodeticCurveType = GeodeticCurveType.Geodesic /** @@ -52,18 +45,21 @@ internal class ScalebarViewModel( * * @since 200.7.0 */ - internal fun updateLabels(minSegmentWidth: Double): List { - val suggestedNumSegments = (_scalebarProperties.displayLength / minSegmentWidth).toInt() + internal fun updateLabels(scalebarProperties: ScalebarProperties?, minSegmentWidth: Double): List { + if (scalebarProperties == null) { + return emptyList() + } + val suggestedNumSegments = (scalebarProperties.displayLength / minSegmentWidth).toInt() // Cap segments at 4 val maxNumSegments = minOf(suggestedNumSegments, 4) val numSegments = ScalebarUtils.numSegments( - _scalebarProperties.lineMapLength, + scalebarProperties.lineMapLength, maxNumSegments ) - val segmentScreenLength = _scalebarProperties.displayLength / numSegments + val segmentScreenLength = scalebarProperties.displayLength / numSegments var currSegmentX = 0.0 val localLabels = mutableListOf() @@ -79,11 +75,11 @@ internal class ScalebarViewModel( for (index in 0 until numSegments) { currSegmentX += segmentScreenLength val segmentMapLength: Double = - (segmentScreenLength * (index + 1) / _scalebarProperties.displayLength) * _scalebarProperties.lineMapLength + (segmentScreenLength * (index + 1) / scalebarProperties.displayLength) * scalebarProperties.lineMapLength val segmentText: String = if (index == numSegments - 1 /*&& _scalebarProperties.value.displayUnit != null*/) { - val displayUnitAbbr = _scalebarProperties.displayUnit.getAbbreviation() + val displayUnitAbbr = scalebarProperties.displayUnit.getAbbreviation() "${segmentMapLength.format()} $displayUnitAbbr" } else { segmentMapLength.format() @@ -190,36 +186,20 @@ internal class ScalebarViewModel( if (!localDisplayLength.isFinite() || localDisplayLength.isNaN()) { return null } - _scalebarProperties = ScalebarProperties( + return ScalebarProperties( displayLength = localDisplayLength, displayUnit = localDisplayUnit, lineMapLength = localLineMapLength ) - return _scalebarProperties } -} +} internal data class ScalebarProperties( val displayLength: Double, val displayUnit: LinearUnit, val lineMapLength: Double ) -internal class ScalebarViewModelFactory( - private val minScale: Double, - private val style: ScalebarStyle, - private val units: ScalebarUnits, - private val labelTypography: LabelTypography, - private val useGeodeticCalculations: Boolean -) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(ScalebarViewModel::class.java)) { - return ScalebarViewModel(minScale, style, units, labelTypography, useGeodeticCalculations) as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} - /** * Gets the abbreviation for the LinearUnit. * From 2c4b125f3ac88dfa8313ac41ac452447adc915e9 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 11:23:13 -0800 Subject: [PATCH 20/27] updates test --- .../scalebar/ScalebarComputationTest.kt | 138 +++++++++++ .../scalebar/ScalebarViewModelTests.kt | 161 ------------- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 215 ++++++++++++++++- .../scalebar/internal/ScalebarState.kt | 216 ------------------ 4 files changed, 341 insertions(+), 389 deletions(-) create mode 100644 toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt delete mode 100644 toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt delete mode 100644 toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt new file mode 100644 index 000000000..59916894e --- /dev/null +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt @@ -0,0 +1,138 @@ +package com.arcgismaps.toolkit.scalebar/* + * Copyright 2025 Esri + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.sp +import com.arcgismaps.geometry.Point +import com.arcgismaps.geometry.SpatialReference +import com.arcgismaps.mapping.Viewpoint +import com.arcgismaps.toolkit.scalebar.theme.LabelTypography +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class ScalebarCalculationsTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val esriRedlands = Point(-13046081.04434825, 4036489.208008117, SpatialReference.webMercator()) + private val defaultLabelTypography = LabelTypography(labelStyle = TextStyle(fontSize = 11.sp)) + + /** + * Given map properties and device properties + * When the properties of a line style scalebar are computed + * Then the display length and labels should be correct + * + * @since 200.7.0 + */ + @Test + fun testLineStyle() { + testScalebarCalculations( + point = esriRedlands, + style = ScalebarStyle.Line, + maxWidth = 175.0, + units = ScalebarUnits.METRIC, + scale = 10000000.0, + unitsPerDip = 2645.833333330476, + labelTypography = defaultLabelTypography, + expectedDisplayLength = 171.0, + expectedLabels = listOf("375 km") + ) + } + + /** + * Given map properties and device properties + * When the properties of a bar scalebar are calculated + * Then the display length and labels should be correct + * + * @since 200.7.0 + */ + @Test + fun testBarStyle() = runTest { + testScalebarCalculations( + point = esriRedlands, + style = ScalebarStyle.Bar, + maxWidth = 175.0, + units = ScalebarUnits.METRIC, + scale = 10000000.0, + unitsPerDip = 2645.833333330476, + labelTypography = defaultLabelTypography, + expectedDisplayLength = 171.0, + expectedLabels = listOf("375 km") + ) + } + + + /** + * Executes the scalebar calculations and verifies the results. + * + * @since 200.7.0 + */ + private fun testScalebarCalculations( + point: Point, + style: ScalebarStyle, + maxWidth: Double, + units: ScalebarUnits, + scale: Double, + unitsPerDip: Double, + labelTypography: LabelTypography, + expectedDisplayLength: Double, + expectedLabels: List, + spatialReference: SpatialReference = SpatialReference.webMercator(), + useGeodeticCalculations: Boolean = true, + ) { + composeTestRule.setContent { + + val viewpoint = Viewpoint( + center = Point( + x = point.x, + y = point.y, + spatialReference = spatialReference + ), + scale = scale + ) + val scalebarProperties = computeScalebarProperties( + minScale = 0.0, + useGeodeticCalculations = useGeodeticCalculations, + units = units, + spatialReference = spatialReference, + viewpoint = viewpoint, + unitsPerDip = unitsPerDip, + maxLength = maxWidth, + ) + val minSegmentWidth = measureMinSegmentWidth( + lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, + labelTypography = labelTypography, + ) + val scalebarLabels = updateLabels( + minSegmentWidth = minSegmentWidth, + displayLength = scalebarProperties?.displayLength ?: 0.0, + lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, + displayUnit = scalebarProperties?.displayUnit, + style = style, + labelTypography = labelTypography, + ) + + assertThat(scalebarProperties?.displayLength).isWithin(.5).of(expectedDisplayLength) + assertThat(scalebarLabels.size).isEqualTo(expectedLabels.size) + for (i in expectedLabels.indices) { + assertThat(scalebarLabels[i].text).isEqualTo(expectedLabels[i]) + } + } + } +} diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt deleted file mode 100644 index 80614fe0c..000000000 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarViewModelTests.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2025 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.arcgismaps.toolkit.scalebar - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.sp -import com.arcgismaps.geometry.Point -import com.arcgismaps.geometry.SpatialReference -import com.arcgismaps.mapping.Viewpoint -import com.arcgismaps.toolkit.scalebar.internal.ScalebarViewModel -import com.arcgismaps.toolkit.scalebar.theme.LabelTypography -import com.arcgismaps.toolkit.scalebar.theme.ScalebarDefaults -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest -import org.junit.Rule -import org.junit.Test -import kotlin.math.roundToInt - -/** - * Tests for ScalebarViewModel. - * - * @since 200.7.0 - */ -class ScalebarViewModelTests { - @get:Rule - val composeTestRule = createComposeRule() - - private val esriRedlands = Point(-13046081.04434825, 4036489.208008117, SpatialReference.webMercator()) - private val defaultLabelTypography = LabelTypography(labelStyle = TextStyle(fontSize = 11.sp)) - - /** - * Given a Scalebar view model - * When the Scalebar of line style is updated - * Then the display length and labels should be correct - * - * @since 200.7.0 - */ - @Test - fun testLineStyle() = runTest { - testScalebarViewModel( - x = esriRedlands.x, - y = esriRedlands.y, - style = ScalebarStyle.Line, - maxWidth = 175.0, - units = ScalebarUnits.METRIC, - scale = 10000000.0, - unitsPerDip = 2645.833333330476, - labelTypography = defaultLabelTypography, - displayLength = 171, - labels = listOf("375 km") - ) - } - - /** - * Given a Scalebar view model - * When the Scalebar of Bar style is updated - * Then the display length and labels should be correct - * - * @since 200.7.0 - */ - @Test - fun testBarStyle() = runTest { - testScalebarViewModel( - x = esriRedlands.x, - y = esriRedlands.y, - style = ScalebarStyle.Bar, - maxWidth = 175.0, - units = ScalebarUnits.METRIC, - scale = 10000000.0, - unitsPerDip = 2645.833333330476, - labelTypography = defaultLabelTypography, - displayLength = 171, - labels = listOf("375 km") - ) - } - - /** - * Executes a test for the ScalebarViewModel with the given parameters. - * - * @since 200.7.0 - */ - private fun testScalebarViewModel( - x: Double, - y: Double, - spatialReference: SpatialReference = SpatialReference.webMercator(), - style: ScalebarStyle, - maxWidth: Double, - units: ScalebarUnits, - scale: Double, - unitsPerDip: Double, - labelTypography: LabelTypography, - useGeodeticCalculations: Boolean = true, - displayLength: Int, - labels: List, - ) = runTest { - val viewpoint = Viewpoint( - center = Point( - x = x, - y = y, - spatialReference = spatialReference - ), - scale = scale - ) - - val viewModel = ScalebarViewModel( - 0.0, - style, - units, - labelTypography, - useGeodeticCalculations - ) - - val lineWidth = 2.0f // this is the value being passed after the available line display length is calculated - // in swift, it is calculated as maxWidth - lineWidth - val availableLineDisplayLength = maxWidth - lineWidth - - composeTestRule.setContent { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.BottomCenter - ) { - viewModel.computeScalebarProperties( - spatialReference, - viewpoint, - unitsPerDip, - availableLineDisplayLength - ) - val isUpdateLabels by viewModel.isUpdateLabels - if (isUpdateLabels) { - viewModel.updateLabels(measureMinSegmentWidth(viewModel.lineMapLength, ScalebarDefaults.typography())) - } - - assertThat(viewModel.displayLength.roundToInt()).isEqualTo(displayLength) - assertThat(viewModel.labels.size).isEqualTo(labels.size) - for (i in labels.indices) { - assertThat(viewModel.labels[i].text).isEqualTo(labels[i]) - } - } - } - } -} diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index fe4becfe3..de061aeaf 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -28,11 +28,18 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.tooling.preview.Preview +import com.arcgismaps.geometry.AngularUnit +import com.arcgismaps.geometry.GeodeticCurveType +import com.arcgismaps.geometry.GeometryEngine +import com.arcgismaps.geometry.LinearUnit +import com.arcgismaps.geometry.Point +import com.arcgismaps.geometry.Polyline import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.toolkit.scalebar.internal.LineScalebar import com.arcgismaps.toolkit.scalebar.internal.ScalebarLabel -import com.arcgismaps.toolkit.scalebar.internal.ScalebarState +import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils +import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.format import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.toPx import com.arcgismaps.toolkit.scalebar.internal.labelXPadding import com.arcgismaps.toolkit.scalebar.internal.lineWidth @@ -73,21 +80,15 @@ public fun Scalebar( shapes: ScalebarShapes = ScalebarDefaults.shapes(), labelTypography: LabelTypography = ScalebarDefaults.typography() ) { - val scalebarState = remember(minScale, style, units, labelTypography, useGeodeticCalculations) { - ScalebarState( - minScale, - style, - units, - labelTypography, - useGeodeticCalculations - ) - } val availableLineDisplayLength = measureAvailableLineDisplayLength(maxWidth, labelTypography, style) val scalebarProperties by remember(spatialReference, viewpoint, unitsPerDip, availableLineDisplayLength) { derivedStateOf { - scalebarState.computeScalebarProperties( + computeScalebarProperties( + minScale = minScale, + useGeodeticCalculations = useGeodeticCalculations, + units = units, spatialReference, viewpoint, unitsPerDip, @@ -98,7 +99,14 @@ public fun Scalebar( // Measure the minimum segment width required to display the labels without overlapping val minSegmentWidth = measureMinSegmentWidth(scalebarProperties?.displayLength ?: 0.0, labelTypography) - val scalebarLabels = scalebarState.updateLabels(scalebarProperties, minSegmentWidth) + val scalebarLabels = updateLabels( + minSegmentWidth, + scalebarProperties?.displayLength ?: 0.0, + scalebarProperties?.lineMapLength ?: 0.0, + scalebarProperties?.displayUnit, + style, + labelTypography + ) if (scalebarLabels.isNotEmpty()) { val density = LocalDensity.current @@ -213,3 +221,186 @@ internal fun measureMinSegmentWidth( // to allow for the right-most label being right-justified whereas the other labels are center-justified return (maxUnitDisplayWidth * 1.5) + (labelXPadding * 2) } + +/** + * Computes the Scalebar properties namely DisplayLength, DisplayUnit and LineMapLength + * with the new values of the given parameters. + * + * @since 200.7.0 + */ +internal fun computeScalebarProperties( + minScale: Double, + useGeodeticCalculations: Boolean, + units: ScalebarUnits, + spatialReference: SpatialReference?, + viewpoint: Viewpoint?, + unitsPerDip: Double?, + maxLength: Double, +): ScalebarProperties? { + if (spatialReference == null || unitsPerDip == null || viewpoint == null) { + return null + } + + if (minScale > 0 && viewpoint.targetScale >= minScale || unitsPerDip.isNaN()) { + return null + } + + val mapCenter = viewpoint.targetGeometry.extent.center + + val localDisplayLength: Double + val localDisplayUnit: LinearUnit + val localLineMapLength: Double + + if (useGeodeticCalculations || spatialReference.unit is AngularUnit) { + val maxLengthPlanar = unitsPerDip * maxLength + val p1 = Point( + x = mapCenter.x - (maxLengthPlanar * 0.5), + y = mapCenter.y, + spatialReference = spatialReference + ) + val p2 = Point( + x = mapCenter.x + (maxLengthPlanar * 0.5), + y = mapCenter.y, + spatialReference = spatialReference + ) + val polyline = Polyline( + points = listOf(p1, p2), + spatialReference = spatialReference + ) + val baseUnits = units.baseLinearUnit + val maxLengthGeodetic = GeometryEngine.lengthGeodetic( + polyline, + baseUnits, + GeodeticCurveType.Geodesic + ) + val roundNumberDistance = units.closestDistanceWithoutGoingOver( + maxLengthGeodetic, + baseUnits + ) + val planarToGeodeticFactor = maxLengthPlanar / maxLengthGeodetic + localDisplayLength = (roundNumberDistance * planarToGeodeticFactor) / unitsPerDip + localDisplayUnit = units.linearUnitsForDistance(roundNumberDistance) + localLineMapLength = baseUnits.convertTo(localDisplayUnit, roundNumberDistance) + } else { + val srUnit = spatialReference.unit as? LinearUnit ?: return null + val baseUnits = units.baseLinearUnit + val lenAvail = srUnit.convertTo( + baseUnits, + unitsPerDip * maxLength + ) + val closestLen = units.closestDistanceWithoutGoingOver( + lenAvail, + baseUnits + ) + localDisplayLength = baseUnits.convertTo( + srUnit, + closestLen + ) / unitsPerDip + localDisplayUnit = units.linearUnitsForDistance(closestLen) + localLineMapLength = baseUnits.convertTo( + localDisplayUnit, + closestLen + ) + } + + if (!localDisplayLength.isFinite() || localDisplayLength.isNaN()) { + return null + } + return ScalebarProperties( + displayLength = localDisplayLength, + displayUnit = localDisplayUnit, + lineMapLength = localLineMapLength + ) +} + +/** + * Updates the labels for the Scalebar. + * + * @since 200.7.0 + */ +internal fun updateLabels( + minSegmentWidth: Double, + displayLength: Double, + lineMapLength: Double, + displayUnit: LinearUnit?, + style: ScalebarStyle, + labelTypography: LabelTypography, +): List { + val suggestedNumSegments = (displayLength / minSegmentWidth).toInt() + + // Cap segments at 4 + val maxNumSegments = minOf(suggestedNumSegments, 4) + + val numSegments = ScalebarUtils.numSegments( + lineMapLength, + maxNumSegments + ) + + val segmentScreenLength = displayLength / numSegments + var currSegmentX = 0.0 + val localLabels = mutableListOf() + + localLabels.add( + ScalebarLabel( + index = -1, + xOffset = 0.0, + yOffset = labelTypography.labelStyle.fontSize.value / 2.0, + text = "0" + ) + ) + + for (index in 0 until numSegments) { + currSegmentX += segmentScreenLength + val segmentMapLength: Double = + (segmentScreenLength * (index + 1) / displayLength) * lineMapLength + + val segmentText: String = + if (index == numSegments - 1 && displayUnit != null) { + val displayUnitAbbr = displayUnit.getAbbreviation() + "${segmentMapLength.format()} $displayUnitAbbr" + } else { + segmentMapLength.format() + } + + val label = ScalebarLabel( + index = index, + xOffset = currSegmentX, + yOffset = labelTypography.labelStyle.fontSize.value / 2.0, + text = segmentText + ) + localLabels.add(label) + } + + return if (style == ScalebarStyle.Bar || style == ScalebarStyle.Line) { + if (localLabels.isNotEmpty()) { + mutableListOf(localLabels.last()) + } else { + mutableListOf() + } + } else { + localLabels + } +} + +internal data class ScalebarProperties( + val displayLength: Double, + val displayUnit: LinearUnit, + val lineMapLength: Double +) + +/** + * Gets the abbreviation for the LinearUnit. + * + * @since 200.7.0 + */ +private fun LinearUnit.getAbbreviation(): String { + return when (this) { + LinearUnit.meters -> "m" + LinearUnit.kilometers -> "km" + LinearUnit.feet -> "ft" + LinearUnit.miles -> "mi" + else -> "" + } +} + + diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt deleted file mode 100644 index 307ecd44f..000000000 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/internal/ScalebarState.kt +++ /dev/null @@ -1,216 +0,0 @@ -/* - * - * Copyright 2025 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package com.arcgismaps.toolkit.scalebar.internal - -import com.arcgismaps.geometry.AngularUnit -import com.arcgismaps.geometry.GeodeticCurveType -import com.arcgismaps.geometry.GeometryEngine -import com.arcgismaps.geometry.LinearUnit -import com.arcgismaps.geometry.Point -import com.arcgismaps.geometry.Polyline -import com.arcgismaps.geometry.SpatialReference -import com.arcgismaps.mapping.Viewpoint -import com.arcgismaps.toolkit.scalebar.ScalebarStyle -import com.arcgismaps.toolkit.scalebar.ScalebarUnits -import com.arcgismaps.toolkit.scalebar.internal.ScalebarUtils.format -import com.arcgismaps.toolkit.scalebar.theme.LabelTypography - - -internal class ScalebarState( - private val minScale: Double, - private val style: ScalebarStyle, - private val units: ScalebarUnits, - private val labelTypography: LabelTypography, - private val useGeodeticCalculations: Boolean -) { - private val geodeticCurveType: GeodeticCurveType = GeodeticCurveType.Geodesic - - /** - * Updates the labels for the Scalebar. - * - * @since 200.7.0 - */ - internal fun updateLabels(scalebarProperties: ScalebarProperties?, minSegmentWidth: Double): List { - if (scalebarProperties == null) { - return emptyList() - } - val suggestedNumSegments = (scalebarProperties.displayLength / minSegmentWidth).toInt() - - // Cap segments at 4 - val maxNumSegments = minOf(suggestedNumSegments, 4) - - val numSegments = ScalebarUtils.numSegments( - scalebarProperties.lineMapLength, - maxNumSegments - ) - - val segmentScreenLength = scalebarProperties.displayLength / numSegments - var currSegmentX = 0.0 - val localLabels = mutableListOf() - - localLabels.add( - ScalebarLabel( - index = -1, - xOffset = 0.0, - yOffset = labelTypography.labelStyle.fontSize.value / 2.0, - text = "0" - ) - ) - - for (index in 0 until numSegments) { - currSegmentX += segmentScreenLength - val segmentMapLength: Double = - (segmentScreenLength * (index + 1) / scalebarProperties.displayLength) * scalebarProperties.lineMapLength - - val segmentText: String = - if (index == numSegments - 1 /*&& _scalebarProperties.value.displayUnit != null*/) { - val displayUnitAbbr = scalebarProperties.displayUnit.getAbbreviation() - "${segmentMapLength.format()} $displayUnitAbbr" - } else { - segmentMapLength.format() - } - - val label = ScalebarLabel( - index = index, - xOffset = currSegmentX, - yOffset = labelTypography.labelStyle.fontSize.value / 2.0, - text = segmentText - ) - localLabels.add(label) - } - - return if (style == ScalebarStyle.Bar || style == ScalebarStyle.Line) { - if (localLabels.isNotEmpty()) { - mutableListOf(localLabels.last()) - } else { - mutableListOf() - } - } else { - localLabels - } - } - - /** - * Computes the Scalebar properties namely DisplayLength, DisplayUnit and LineMapLength - * with the new values of the given parameters. - * - * @since 200.7.0 - */ - internal fun computeScalebarProperties( - spatialReference: SpatialReference?, - viewpoint: Viewpoint?, - unitsPerDip: Double?, - maxLength: Double, - ): ScalebarProperties? { - if (spatialReference == null || unitsPerDip == null || viewpoint == null) { - return null - } - - if (minScale > 0 && viewpoint.targetScale >= minScale || unitsPerDip.isNaN()) { - return null - } - - val mapCenter = viewpoint.targetGeometry.extent.center - - val localDisplayLength: Double - val localDisplayUnit: LinearUnit - val localLineMapLength: Double - - if (useGeodeticCalculations || spatialReference.unit is AngularUnit) { - val maxLengthPlanar = unitsPerDip * maxLength - val p1 = Point( - x = mapCenter.x - (maxLengthPlanar * 0.5), - y = mapCenter.y, - spatialReference = spatialReference - ) - val p2 = Point( - x = mapCenter.x + (maxLengthPlanar * 0.5), - y = mapCenter.y, - spatialReference = spatialReference - ) - val polyline = Polyline( - points = listOf(p1, p2), - spatialReference = spatialReference - ) - val baseUnits = units.baseLinearUnit - val maxLengthGeodetic = GeometryEngine.lengthGeodetic( - polyline, - baseUnits, - geodeticCurveType - ) - val roundNumberDistance = units.closestDistanceWithoutGoingOver( - maxLengthGeodetic, - baseUnits - ) - val planarToGeodeticFactor = maxLengthPlanar / maxLengthGeodetic - localDisplayLength = (roundNumberDistance * planarToGeodeticFactor) / unitsPerDip - localDisplayUnit = units.linearUnitsForDistance(roundNumberDistance) - localLineMapLength = baseUnits.convertTo(localDisplayUnit, roundNumberDistance) - } else { - val srUnit = spatialReference.unit as? LinearUnit ?: return null - val baseUnits = units.baseLinearUnit - val lenAvail = srUnit.convertTo( - baseUnits, - unitsPerDip * maxLength - ) - val closestLen = units.closestDistanceWithoutGoingOver( - lenAvail, - baseUnits - ) - localDisplayLength = baseUnits.convertTo( - srUnit, - closestLen - ) / unitsPerDip - localDisplayUnit = units.linearUnitsForDistance(closestLen) - localLineMapLength = baseUnits.convertTo( - localDisplayUnit, - closestLen - ) - } - - if (!localDisplayLength.isFinite() || localDisplayLength.isNaN()) { - return null - } - return ScalebarProperties( - displayLength = localDisplayLength, - displayUnit = localDisplayUnit, - lineMapLength = localLineMapLength - ) - } - -} -internal data class ScalebarProperties( - val displayLength: Double, - val displayUnit: LinearUnit, - val lineMapLength: Double -) - -/** - * Gets the abbreviation for the LinearUnit. - * - * @since 200.7.0 - */ -private fun LinearUnit.getAbbreviation(): String { - return when (this) { - LinearUnit.meters -> "m" - LinearUnit.kilometers -> "km" - LinearUnit.feet -> "ft" - LinearUnit.miles -> "mi" - else -> "" - } -} From 6858d1d2278ae6341c09db6bbb7b9780dff0b8be Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 14:04:25 -0800 Subject: [PATCH 21/27] rename a few properties --- .../scalebar/ScalebarComputationTest.kt | 9 ++++++--- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 18 +++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt index 59916894e..79b404c0a 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt @@ -1,4 +1,4 @@ -package com.arcgismaps.toolkit.scalebar/* +/* * Copyright 2025 Esri * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +14,8 @@ package com.arcgismaps.toolkit.scalebar/* * limitations under the License. */ +package com.arcgismaps.toolkit.scalebar + import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.sp @@ -106,10 +108,11 @@ class ScalebarCalculationsTest { ), scale = scale ) + val scalebarProperties = computeScalebarProperties( minScale = 0.0, useGeodeticCalculations = useGeodeticCalculations, - units = units, + scalebarUnits = units, spatialReference = spatialReference, viewpoint = viewpoint, unitsPerDip = unitsPerDip, @@ -119,7 +122,7 @@ class ScalebarCalculationsTest { lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, labelTypography = labelTypography, ) - val scalebarLabels = updateLabels( + val scalebarLabels = computeScalebarLabels( minSegmentWidth = minSegmentWidth, displayLength = scalebarProperties?.displayLength ?: 0.0, lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index de061aeaf..1ce8456fb 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -99,7 +99,7 @@ public fun Scalebar( // Measure the minimum segment width required to display the labels without overlapping val minSegmentWidth = measureMinSegmentWidth(scalebarProperties?.displayLength ?: 0.0, labelTypography) - val scalebarLabels = updateLabels( + val scalebarLabels = computeScalebarLabels( minSegmentWidth, scalebarProperties?.displayLength ?: 0.0, scalebarProperties?.lineMapLength ?: 0.0, @@ -231,7 +231,7 @@ internal fun measureMinSegmentWidth( internal fun computeScalebarProperties( minScale: Double, useGeodeticCalculations: Boolean, - units: ScalebarUnits, + scalebarUnits: ScalebarUnits, spatialReference: SpatialReference?, viewpoint: Viewpoint?, unitsPerDip: Double?, @@ -267,28 +267,28 @@ internal fun computeScalebarProperties( points = listOf(p1, p2), spatialReference = spatialReference ) - val baseUnits = units.baseLinearUnit + val baseUnits = scalebarUnits.baseLinearUnit val maxLengthGeodetic = GeometryEngine.lengthGeodetic( polyline, baseUnits, GeodeticCurveType.Geodesic ) - val roundNumberDistance = units.closestDistanceWithoutGoingOver( + val roundNumberDistance = scalebarUnits.closestDistanceWithoutGoingOver( maxLengthGeodetic, baseUnits ) val planarToGeodeticFactor = maxLengthPlanar / maxLengthGeodetic localDisplayLength = (roundNumberDistance * planarToGeodeticFactor) / unitsPerDip - localDisplayUnit = units.linearUnitsForDistance(roundNumberDistance) + localDisplayUnit = scalebarUnits.linearUnitsForDistance(roundNumberDistance) localLineMapLength = baseUnits.convertTo(localDisplayUnit, roundNumberDistance) } else { val srUnit = spatialReference.unit as? LinearUnit ?: return null - val baseUnits = units.baseLinearUnit + val baseUnits = scalebarUnits.baseLinearUnit val lenAvail = srUnit.convertTo( baseUnits, unitsPerDip * maxLength ) - val closestLen = units.closestDistanceWithoutGoingOver( + val closestLen = scalebarUnits.closestDistanceWithoutGoingOver( lenAvail, baseUnits ) @@ -296,7 +296,7 @@ internal fun computeScalebarProperties( srUnit, closestLen ) / unitsPerDip - localDisplayUnit = units.linearUnitsForDistance(closestLen) + localDisplayUnit = scalebarUnits.linearUnitsForDistance(closestLen) localLineMapLength = baseUnits.convertTo( localDisplayUnit, closestLen @@ -318,7 +318,7 @@ internal fun computeScalebarProperties( * * @since 200.7.0 */ -internal fun updateLabels( +internal fun computeScalebarLabels( minSegmentWidth: Double, displayLength: Double, lineMapLength: Double, From 5d6dd452945be20c776ed840a505b0b9698a5afa Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 14:08:34 -0800 Subject: [PATCH 22/27] update method signature --- .../java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 1ce8456fb..0f44dc571 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -87,12 +87,12 @@ public fun Scalebar( derivedStateOf { computeScalebarProperties( minScale = minScale, + maxLength = availableLineDisplayLength, useGeodeticCalculations = useGeodeticCalculations, - units = units, - spatialReference, - viewpoint, - unitsPerDip, - availableLineDisplayLength + spatialReference = spatialReference, + viewpoint = viewpoint, + unitsPerDip = unitsPerDip, + scalebarUnits = units, ) } } From f2317b69e4acc9ae444d32f5a9fc84f1e80c071f Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 14:17:59 -0800 Subject: [PATCH 23/27] update names --- .../toolkit/scalebar/ScalebarComputationTest.kt | 4 ++-- .../main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt index 79b404c0a..bd88346e3 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt @@ -109,7 +109,7 @@ class ScalebarCalculationsTest { scale = scale ) - val scalebarProperties = computeScalebarProperties( + val scalebarProperties = calculateScalebarProperties( minScale = 0.0, useGeodeticCalculations = useGeodeticCalculations, scalebarUnits = units, @@ -122,7 +122,7 @@ class ScalebarCalculationsTest { lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, labelTypography = labelTypography, ) - val scalebarLabels = computeScalebarLabels( + val scalebarLabels = generateScalebarLabels( minSegmentWidth = minSegmentWidth, displayLength = scalebarProperties?.displayLength ?: 0.0, lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 0f44dc571..b9173345a 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -85,7 +85,7 @@ public fun Scalebar( val scalebarProperties by remember(spatialReference, viewpoint, unitsPerDip, availableLineDisplayLength) { derivedStateOf { - computeScalebarProperties( + calculateScalebarProperties( minScale = minScale, maxLength = availableLineDisplayLength, useGeodeticCalculations = useGeodeticCalculations, @@ -99,7 +99,7 @@ public fun Scalebar( // Measure the minimum segment width required to display the labels without overlapping val minSegmentWidth = measureMinSegmentWidth(scalebarProperties?.displayLength ?: 0.0, labelTypography) - val scalebarLabels = computeScalebarLabels( + val scalebarLabels = generateScalebarLabels( minSegmentWidth, scalebarProperties?.displayLength ?: 0.0, scalebarProperties?.lineMapLength ?: 0.0, @@ -228,7 +228,7 @@ internal fun measureMinSegmentWidth( * * @since 200.7.0 */ -internal fun computeScalebarProperties( +internal fun calculateScalebarProperties( minScale: Double, useGeodeticCalculations: Boolean, scalebarUnits: ScalebarUnits, @@ -318,7 +318,7 @@ internal fun computeScalebarProperties( * * @since 200.7.0 */ -internal fun computeScalebarLabels( +internal fun generateScalebarLabels( minSegmentWidth: Double, displayLength: Double, lineMapLength: Double, From adc6cd51754cfff9940b034c4cf47e5738f3a457 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 16:34:54 -0800 Subject: [PATCH 24/27] rename composable to match guidelines --- .../src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index b9173345a..1f4a30ef8 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -110,7 +110,7 @@ public fun Scalebar( if (scalebarLabels.isNotEmpty()) { val density = LocalDensity.current - ShowScalebar( + Scalebar( scalebarProperties?.displayLength?.toPx(density) ?: 0.0, scalebarLabels, style, @@ -122,7 +122,7 @@ public fun Scalebar( } @Composable -private fun ShowScalebar( +private fun Scalebar( maxWidth: Double, labels: List, scalebarStyle: ScalebarStyle, From f6cac205868f896dd15520176017c7a8339d6481 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 17:07:33 -0800 Subject: [PATCH 25/27] update retrieval of last label or empty if there's none --- .../main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 1f4a30ef8..b78937e22 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -372,11 +372,8 @@ internal fun generateScalebarLabels( } return if (style == ScalebarStyle.Bar || style == ScalebarStyle.Line) { - if (localLabels.isNotEmpty()) { - mutableListOf(localLabels.last()) - } else { - mutableListOf() - } + // return a list with the last label or an empty list if there are no labels + localLabels.takeLast(1) } else { localLabels } From 783b76ea2f58e2898479afbc22cd7b33347ec6dc Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 17:51:57 -0800 Subject: [PATCH 26/27] refactor names --- .../toolkit/scalebarapp/screens/MainScreen.kt | 2 +- .../scalebar/ScalebarComputationTest.kt | 6 +-- .../arcgismaps/toolkit/scalebar/Scalebar.kt | 50 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt index 2d3d2a7e0..5c293ed5d 100644 --- a/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt +++ b/microapps/ScalebarApp/app/src/main/java/com/arcgismaps/toolkit/scalebarapp/screens/MainScreen.kt @@ -78,7 +78,7 @@ fun MainScreen(modifier: Modifier) { .align(Alignment.BottomStart) ) { Scalebar( - maxWidth = 175.0, + maxDisplayWidth = 175.0, unitsPerDip = unitsPerDip, viewpoint = viewpoint, spatialReference = spatialReference, diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt index bd88346e3..b194641b3 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt @@ -116,16 +116,16 @@ class ScalebarCalculationsTest { spatialReference = spatialReference, viewpoint = viewpoint, unitsPerDip = unitsPerDip, - maxLength = maxWidth, + maxDisplayLength = maxWidth, ) val minSegmentWidth = measureMinSegmentWidth( - lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, + scalebarLineLength = scalebarProperties?.lineLength ?: 0.0, labelTypography = labelTypography, ) val scalebarLabels = generateScalebarLabels( minSegmentWidth = minSegmentWidth, displayLength = scalebarProperties?.displayLength ?: 0.0, - lineMapLength = scalebarProperties?.lineMapLength ?: 0.0, + scalebarLineLength = scalebarProperties?.lineLength ?: 0.0, displayUnit = scalebarProperties?.displayUnit, style = style, labelTypography = labelTypography, diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index b78937e22..bf2341afe 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -61,7 +61,7 @@ import kotlin.time.Duration.Companion.seconds */ @Composable public fun Scalebar( - maxWidth: Double, // maximum screen width allotted to the scalebar + maxDisplayWidth: Double, // maximum screen width allotted to the scalebar unitsPerDip: Double, viewpoint: Viewpoint?, spatialReference: SpatialReference?, @@ -81,13 +81,13 @@ public fun Scalebar( labelTypography: LabelTypography = ScalebarDefaults.typography() ) { - val availableLineDisplayLength = measureAvailableLineDisplayLength(maxWidth, labelTypography, style) + val availableDisplayLength = measureAvailableDisplayLength(maxDisplayWidth, labelTypography, style) - val scalebarProperties by remember(spatialReference, viewpoint, unitsPerDip, availableLineDisplayLength) { + val scalebarProperties by remember(spatialReference, viewpoint, unitsPerDip, availableDisplayLength) { derivedStateOf { calculateScalebarProperties( minScale = minScale, - maxLength = availableLineDisplayLength, + maxDisplayLength = availableDisplayLength, useGeodeticCalculations = useGeodeticCalculations, spatialReference = spatialReference, viewpoint = viewpoint, @@ -102,7 +102,7 @@ public fun Scalebar( val scalebarLabels = generateScalebarLabels( minSegmentWidth, scalebarProperties?.displayLength ?: 0.0, - scalebarProperties?.lineMapLength ?: 0.0, + scalebarProperties?.lineLength ?: 0.0, scalebarProperties?.displayUnit, style, labelTypography @@ -149,7 +149,7 @@ private fun Scalebar( @Composable internal fun ScalebarPreview() { Scalebar( - maxWidth = 200.0, + maxDisplayWidth = 200.0, spatialReference = null, unitsPerDip = 1.0, viewpoint = Viewpoint(0.0, 0.0, 0.0), @@ -172,7 +172,7 @@ private fun isMetric(): Boolean { * @since 200.7.0 */ @Composable -private fun measureAvailableLineDisplayLength( +private fun measureAvailableDisplayLength( maxWidth: Double, labelTypography: LabelTypography, style: ScalebarStyle @@ -202,15 +202,15 @@ private fun measureAvailableLineDisplayLength( */ @Composable internal fun measureMinSegmentWidth( - lineMapLength: Double, + scalebarLineLength: Double, labelTypography: LabelTypography ): Double { // The constraining factor is the space required to draw the labels. Create a testString containing the longest // label, which is usually the one for 'distance' because the other labels will be smaller numbers. // But if 'distance' is small some of the other labels may use decimals, so allow for each label needing at least // 3 characters - val minSegmentTestString: String = if (lineMapLength >= 100) { - lineMapLength.toInt().toString() + val minSegmentTestString: String = if (scalebarLineLength >= 100) { + scalebarLineLength.toInt().toString() } else { "9.9" } @@ -230,12 +230,12 @@ internal fun measureMinSegmentWidth( */ internal fun calculateScalebarProperties( minScale: Double, + maxDisplayLength: Double, useGeodeticCalculations: Boolean, scalebarUnits: ScalebarUnits, spatialReference: SpatialReference?, viewpoint: Viewpoint?, unitsPerDip: Double?, - maxLength: Double, ): ScalebarProperties? { if (spatialReference == null || unitsPerDip == null || viewpoint == null) { return null @@ -249,10 +249,10 @@ internal fun calculateScalebarProperties( val localDisplayLength: Double val localDisplayUnit: LinearUnit - val localLineMapLength: Double + val localScalebarLineLength: Double if (useGeodeticCalculations || spatialReference.unit is AngularUnit) { - val maxLengthPlanar = unitsPerDip * maxLength + val maxLengthPlanar = unitsPerDip * maxDisplayLength val p1 = Point( x = mapCenter.x - (maxLengthPlanar * 0.5), y = mapCenter.y, @@ -280,26 +280,26 @@ internal fun calculateScalebarProperties( val planarToGeodeticFactor = maxLengthPlanar / maxLengthGeodetic localDisplayLength = (roundNumberDistance * planarToGeodeticFactor) / unitsPerDip localDisplayUnit = scalebarUnits.linearUnitsForDistance(roundNumberDistance) - localLineMapLength = baseUnits.convertTo(localDisplayUnit, roundNumberDistance) + localScalebarLineLength = baseUnits.convertTo(localDisplayUnit, roundNumberDistance) } else { val srUnit = spatialReference.unit as? LinearUnit ?: return null val baseUnits = scalebarUnits.baseLinearUnit val lenAvail = srUnit.convertTo( baseUnits, - unitsPerDip * maxLength + unitsPerDip * maxDisplayLength ) - val closestLen = scalebarUnits.closestDistanceWithoutGoingOver( + val closestLength = scalebarUnits.closestDistanceWithoutGoingOver( lenAvail, baseUnits ) localDisplayLength = baseUnits.convertTo( srUnit, - closestLen + closestLength ) / unitsPerDip - localDisplayUnit = scalebarUnits.linearUnitsForDistance(closestLen) - localLineMapLength = baseUnits.convertTo( + localDisplayUnit = scalebarUnits.linearUnitsForDistance(closestLength) + localScalebarLineLength = baseUnits.convertTo( localDisplayUnit, - closestLen + closestLength ) } @@ -309,7 +309,7 @@ internal fun calculateScalebarProperties( return ScalebarProperties( displayLength = localDisplayLength, displayUnit = localDisplayUnit, - lineMapLength = localLineMapLength + lineLength = localScalebarLineLength ) } @@ -321,7 +321,7 @@ internal fun calculateScalebarProperties( internal fun generateScalebarLabels( minSegmentWidth: Double, displayLength: Double, - lineMapLength: Double, + scalebarLineLength: Double, displayUnit: LinearUnit?, style: ScalebarStyle, labelTypography: LabelTypography, @@ -332,7 +332,7 @@ internal fun generateScalebarLabels( val maxNumSegments = minOf(suggestedNumSegments, 4) val numSegments = ScalebarUtils.numSegments( - lineMapLength, + scalebarLineLength, maxNumSegments ) @@ -352,7 +352,7 @@ internal fun generateScalebarLabels( for (index in 0 until numSegments) { currSegmentX += segmentScreenLength val segmentMapLength: Double = - (segmentScreenLength * (index + 1) / displayLength) * lineMapLength + (segmentScreenLength * (index + 1) / displayLength) * scalebarLineLength val segmentText: String = if (index == numSegments - 1 && displayUnit != null) { @@ -382,7 +382,7 @@ internal fun generateScalebarLabels( internal data class ScalebarProperties( val displayLength: Double, val displayUnit: LinearUnit, - val lineMapLength: Double + val lineLength: Double ) /** From 46767d86338cd466d194f322e8c84de13397ef28 Mon Sep 17 00:00:00 2001 From: Erick Date: Wed, 22 Jan 2025 17:57:50 -0800 Subject: [PATCH 27/27] additional merge fixes --- .../toolkit/scalebar/ScalebarComputationTest.kt | 2 +- .../arcgismaps/toolkit/scalebar/ScalebarTests.kt | 2 +- .../com/arcgismaps/toolkit/scalebar/Scalebar.kt | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt index b194641b3..f52d03a3d 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarComputationTest.kt @@ -134,7 +134,7 @@ class ScalebarCalculationsTest { assertThat(scalebarProperties?.displayLength).isWithin(.5).of(expectedDisplayLength) assertThat(scalebarLabels.size).isEqualTo(expectedLabels.size) for (i in expectedLabels.indices) { - assertThat(scalebarLabels[i].text).isEqualTo(expectedLabels[i]) + assertThat(scalebarLabels[i].label).isEqualTo(expectedLabels[i]) } } } diff --git a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt index 10c92c7b9..820e2e53d 100644 --- a/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt +++ b/toolkit/scalebar/src/androidTest/java/com/arcgismaps/toolkit/scalebar/ScalebarTests.kt @@ -102,7 +102,7 @@ class ScalebarTests { * @since 200.7.0 */ @Test - fun testLineScaleBarColorChange() { + fun testLineScalebarColorChange() { composeTestRule.setContent { Box( modifier = Modifier.fillMaxSize(), diff --git a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt index 1724f9539..920b1c00b 100644 --- a/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt +++ b/toolkit/scalebar/src/main/java/com/arcgismaps/toolkit/scalebar/Scalebar.kt @@ -323,7 +323,7 @@ internal fun generateScalebarLabels( displayUnit: LinearUnit?, style: ScalebarStyle, labelTypography: LabelTypography, -): List { +): List { val suggestedNumSegments = (displayLength / minSegmentWidth).toInt() // Cap segments at 4 @@ -336,14 +336,14 @@ internal fun generateScalebarLabels( val segmentScreenLength = displayLength / numSegments var currSegmentX = 0.0 - val localLabels = mutableListOf() + val localLabels = mutableListOf() localLabels.add( - ScalebarLabel( + ScalebarDivision( index = -1, xOffset = 0.0, - yOffset = labelTypography.labelStyle.fontSize.value / 2.0, - text = "0" + labelYOffset = labelTypography.labelStyle.fontSize.value / 2.0, + label = "0" ) ) @@ -360,11 +360,11 @@ internal fun generateScalebarLabels( segmentMapLength.format() } - val label = ScalebarLabel( + val label = ScalebarDivision( index = index, xOffset = currSegmentX, - yOffset = labelTypography.labelStyle.fontSize.value / 2.0, - text = segmentText + labelYOffset = labelTypography.labelStyle.fontSize.value / 2.0, + label = segmentText ) localLabels.add(label) }