diff options
author | 2023-08-08 14:41:46 +0800 | |
---|---|---|
committer | 2023-09-11 11:52:06 +0800 | |
commit | c09b8cfa1a9064ddac119e8229f36ea68459e3b4 (patch) | |
tree | 1bebe6cb10ffb3d63ce7edc51b2412ecf766895a | |
parent | 4565a43c59d5952836729742e385cbab9a8141db (diff) |
[Spa] Support stack bar chart
Fix: 294951338
Test: unit test
Test: visual - with gallery
Change-Id: Icd1c14d79490f10d595ebc40679bffb6f015c4d7
-rw-r--r-- | packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt | 2 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt | 55 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt) | 38 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt | 2 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt | 17 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt | 150 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt | 31 | ||||
-rw-r--r-- | packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt | 42 |
8 files changed, 202 insertions, 135 deletions
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 4a252a91b12f..471f3b93b8aa 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -22,6 +22,7 @@ import com.android.settingslib.spa.framework.common.SettingsPageProviderReposito import com.android.settingslib.spa.framework.common.SpaEnvironment import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider +import com.android.settingslib.spa.gallery.chart.ChartPageProvider import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider @@ -32,7 +33,6 @@ import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageProvider -import com.android.settingslib.spa.gallery.page.ChartPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt new file mode 100644 index 000000000000..bf7a8e14514b --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * 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.android.settingslib.spa.gallery.chart + +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.widget.chart.BarChart +import com.android.settingslib.spa.widget.chart.BarChartData +import com.android.settingslib.spa.widget.chart.BarChartModel +import com.android.settingslib.spa.widget.chart.ColorPalette +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.github.mikephil.charting.formatter.IAxisValueFormatter + +fun createBarChartEntry(owner: SettingsPage) = SettingsEntryBuilder.create("Bar Chart", owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = "Bar Chart" + }) + BarChart( + barChartModel = object : BarChartModel { + override val chartDataList = listOf( + BarChartData(x = 0f, y = listOf(12f, 2f)), + BarChartData(x = 1f, y = listOf(5f, 1f)), + BarChartData(x = 2f, y = listOf(21f, 2f)), + BarChartData(x = 3f, y = listOf(5f, 1f)), + BarChartData(x = 4f, y = listOf(10f, 0f)), + BarChartData(x = 5f, y = listOf(9f, 1f)), + BarChartData(x = 6f, y = listOf(1f, 1f)), + ) + override val colors = listOf(ColorPalette.green, ColorPalette.yellow) + override val xValueFormatter = IAxisValueFormatter { value, _ -> + "4/${value.toInt() + 1}" + } + override val yValueFormatter = IAxisValueFormatter { value, _ -> + "${value.toInt()}m" + } + override val yAxisMaxValue = 30f + } + ) + }.build() diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt index 69c47050e684..7a6ae2cee6ad 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.spa.gallery.page +package com.android.settingslib.spa.gallery.chart import android.os.Bundle import androidx.compose.runtime.Composable @@ -25,9 +25,6 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.framework.theme.SettingsTheme -import com.android.settingslib.spa.widget.chart.BarChart -import com.android.settingslib.spa.widget.chart.BarChartData -import com.android.settingslib.spa.widget.chart.BarChartModel import com.android.settingslib.spa.widget.chart.LineChart import com.android.settingslib.spa.widget.chart.LineChartData import com.android.settingslib.spa.widget.chart.LineChartModel @@ -83,36 +80,7 @@ object ChartPageProvider : SettingsPageProvider { ) }.build() ) - entryList.add( - SettingsEntryBuilder.create("Bar Chart", owner) - .setUiLayoutFn { - Preference(object : PreferenceModel { - override val title = "Bar Chart" - }) - BarChart( - barChartModel = object : BarChartModel { - override val chartDataList = listOf( - BarChartData(x = 0f, y = 12f), - BarChartData(x = 1f, y = 5f), - BarChartData(x = 2f, y = 21f), - BarChartData(x = 3f, y = 5f), - BarChartData(x = 4f, y = 10f), - BarChartData(x = 5f, y = 9f), - BarChartData(x = 6f, y = 1f), - ) - override val xValueFormatter = - IAxisValueFormatter { value, _ -> - "${WeekDay.values()[value.toInt()]}" - } - override val yValueFormatter = - IAxisValueFormatter { value, _ -> - "${value.toInt()}m" - } - override val yAxisMaxValue = 30f - } - ) - }.build() - ) + entryList.add(createBarChartEntry(owner)) entryList.add( SettingsEntryBuilder.create("Pie Chart", owner) .setUiLayoutFn { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index bb311a52052a..6cac2202742a 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -28,12 +28,12 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider +import com.android.settingslib.spa.gallery.chart.ChartPageProvider import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageModel import com.android.settingslib.spa.gallery.page.ArgumentPageProvider -import com.android.settingslib.spa.gallery.page.ChartPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt index 27d270c5d58e..e6decb13c2b9 100644 --- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt +++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt @@ -16,6 +16,7 @@ package com.android.settingslib.spa.screenshot +import androidx.compose.material3.MaterialTheme import com.android.settingslib.spa.widget.chart.BarChart import com.android.settingslib.spa.widget.chart.BarChartData import com.android.settingslib.spa.widget.chart.BarChartModel @@ -45,17 +46,19 @@ class BarChartScreenshotTest(emulationSpec: DeviceEmulationSpec) { @Test fun test() { screenshotRule.screenshotTest("barChart") { + val color = MaterialTheme.colorScheme.surfaceVariant BarChart( barChartModel = object : BarChartModel { override val chartDataList = listOf( - BarChartData(x = 0f, y = 12f), - BarChartData(x = 1f, y = 5f), - BarChartData(x = 2f, y = 21f), - BarChartData(x = 3f, y = 5f), - BarChartData(x = 4f, y = 10f), - BarChartData(x = 5f, y = 9f), - BarChartData(x = 6f, y = 1f), + BarChartData(x = 0f, y = listOf(12f)), + BarChartData(x = 1f, y = listOf(5f)), + BarChartData(x = 2f, y = listOf(21f)), + BarChartData(x = 3f, y = listOf(5f)), + BarChartData(x = 4f, y = listOf(10f)), + BarChartData(x = 5f, y = listOf(9f)), + BarChartData(x = 6f, y = listOf(1f)), ) + override val colors = listOf(color) override val xValueFormatter = IAxisValueFormatter { value, _ -> "${WeekDay.values()[value.toInt()]}" diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt index 0b0f07e670ee..7ca15d95cee1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView @@ -52,6 +53,11 @@ interface BarChartModel { val chartDataList: List<BarChartData> /** + * The color list for [BarChart]. + */ + val colors: List<Color> + + /** * The label text formatter for x value. */ val xValueFormatter: IAxisValueFormatter? @@ -83,34 +89,14 @@ interface BarChartModel { } data class BarChartData( - var x: Float?, - var y: Float?, + var x: Float, + var y: List<Float>, ) @Composable fun BarChart(barChartModel: BarChartModel) { - BarChart( - chartDataList = barChartModel.chartDataList, - xValueFormatter = barChartModel.xValueFormatter, - yValueFormatter = barChartModel.yValueFormatter, - yAxisMinValue = barChartModel.yAxisMinValue, - yAxisMaxValue = barChartModel.yAxisMaxValue, - yAxisLabelCount = barChartModel.yAxisLabelCount, - ) -} - -@Composable -fun BarChart( - chartDataList: List<BarChartData>, - modifier: Modifier = Modifier, - xValueFormatter: IAxisValueFormatter? = null, - yValueFormatter: IAxisValueFormatter? = null, - yAxisMinValue: Float = 0f, - yAxisMaxValue: Float = 30f, - yAxisLabelCount: Int = 4, -) { Column( - modifier = modifier + modifier = Modifier .fillMaxWidth() .wrapContentHeight(), horizontalAlignment = Alignment.CenterHorizontally, @@ -126,50 +112,61 @@ fun BarChart( val colorScheme = MaterialTheme.colorScheme val labelTextColor = colorScheme.onSurfaceVariant.toArgb() val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value - Crossfade(targetState = chartDataList) { barChartData -> - AndroidView(factory = { context -> - BarChart(context).apply { - // Fixed Settings. - layoutParams = LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ) - this.description.isEnabled = false - this.legend.isEnabled = false - this.extraBottomOffset = 4f - this.setScaleEnabled(false) - - this.xAxis.position = XAxis.XAxisPosition.BOTTOM - this.xAxis.setDrawGridLines(false) - this.xAxis.setDrawAxisLine(false) - this.xAxis.textColor = labelTextColor - this.xAxis.textSize = labelTextSize - this.xAxis.yOffset = 10f - - this.axisLeft.isEnabled = false - this.axisRight.setDrawAxisLine(false) - this.axisRight.textSize = labelTextSize - this.axisRight.textColor = labelTextColor - this.axisRight.gridColor = colorScheme.divider.toArgb() - this.axisRight.xOffset = 10f - - // Customizable Settings. - this.xAxis.valueFormatter = xValueFormatter - this.axisRight.valueFormatter = yValueFormatter - - this.axisLeft.axisMinimum = yAxisMinValue - this.axisLeft.axisMaximum = yAxisMaxValue - this.axisRight.axisMinimum = yAxisMinValue - this.axisRight.axisMaximum = yAxisMaxValue - - this.axisRight.setLabelCount(yAxisLabelCount, true) - } - }, + Crossfade( + targetState = barChartModel.chartDataList, + label = "chartDataList", + ) { barChartData -> + AndroidView( + factory = { context -> + BarChart(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + description.isEnabled = false + legend.isEnabled = false + extraBottomOffset = 4f + setScaleEnabled(false) + + xAxis.apply { + position = XAxis.XAxisPosition.BOTTOM + setDrawAxisLine(false) + setDrawGridLines(false) + textColor = labelTextColor + textSize = labelTextSize + valueFormatter = barChartModel.xValueFormatter + yOffset = 10f + } + + axisLeft.apply { + axisMaximum = barChartModel.yAxisMaxValue + axisMinimum = barChartModel.yAxisMinValue + isEnabled = false + } + + axisRight.apply { + axisMaximum = barChartModel.yAxisMaxValue + axisMinimum = barChartModel.yAxisMinValue + gridColor = colorScheme.divider.toArgb() + setDrawAxisLine(false) + setLabelCount(barChartModel.yAxisLabelCount, true) + textColor = labelTextColor + textSize = labelTextSize + valueFormatter = barChartModel.yValueFormatter + xOffset = 10f + } + } + }, modifier = Modifier .wrapContentSize() .padding(4.dp), - update = { - updateBarChartWithData(it, barChartData, colorScheme) + update = { barChart -> + updateBarChartWithData( + chart = barChart, + data = barChartData, + colorList = barChartModel.colors, + colorScheme = colorScheme, + ) } ) } @@ -177,26 +174,25 @@ fun BarChart( } } -fun updateBarChartWithData( +private fun updateBarChartWithData( chart: BarChart, data: List<BarChartData>, + colorList: List<Color>, colorScheme: ColorScheme ) { - val entries = ArrayList<BarEntry>() - for (i in data.indices) { - val item = data[i] - entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat())) + val entries = data.map { item -> + BarEntry(item.x, item.y.toFloatArray()) } - val ds = BarDataSet(entries, "") - ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb()) - ds.setDrawValues(false) - ds.isHighlightEnabled = true - ds.highLightColor = colorScheme.primary.toArgb() - ds.highLightAlpha = 255 + val ds = BarDataSet(entries, "").apply { + colors = colorList.map(Color::toArgb) + setDrawValues(false) + isHighlightEnabled = true + highLightColor = colorScheme.primary.toArgb() + highLightAlpha = 255 + } // TODO: Sets round corners for bars. - val d = BarData(ds) - chart.data = d + chart.data = BarData(ds) chart.invalidate() } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt index 2230d6c96bbb..0fe755f09b66 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt @@ -17,13 +17,17 @@ package com.android.settingslib.spa.widget.chart import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.SemanticsPropertyKey import androidx.compose.ui.semantics.SemanticsPropertyReceiver import androidx.compose.ui.semantics.semantics import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.assertContainsColor import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -61,22 +65,21 @@ class ChartTest { @Test fun bar_chart_displayed() { composeTestRule.setContent { - BarChart( - chartDataList = listOf( - BarChartData(x = 0f, y = 12f), - BarChartData(x = 1f, y = 5f), - BarChartData(x = 2f, y = 21f), - BarChartData(x = 3f, y = 5f), - BarChartData(x = 4f, y = 10f), - BarChartData(x = 5f, y = 9f), - BarChartData(x = 6f, y = 1f), - ), - yAxisMaxValue = 30f, - modifier = Modifier.semantics { chart = "bar" } - ) + BarChart(object : BarChartModel { + override val chartDataList = listOf( + BarChartData(x = 0f, y = listOf(12f)), + BarChartData(x = 1f, y = listOf(5f)), + BarChartData(x = 2f, y = listOf(21f)), + BarChartData(x = 3f, y = listOf(5f)), + BarChartData(x = 4f, y = listOf(10f)), + BarChartData(x = 5f, y = listOf(9f)), + BarChartData(x = 6f, y = listOf(1f)), + ) + override val colors = listOf(Color.Blue) + }) } - composeTestRule.onNode(hasChart("bar")).assertIsDisplayed() + composeTestRule.onRoot().captureToImage().assertContainsColor(Color.Blue) } @Test diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt new file mode 100644 index 000000000000..0190989ef628 --- /dev/null +++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * 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.android.settingslib.spa.testutils + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.toPixelMap + +/** + * Asserts that the expected color is present in this bitmap. + * + * @throws AssertionError if the expected color is not present. + */ +fun ImageBitmap.assertContainsColor(expectedColor: Color) { + assert(containsColor(expectedColor)) { + "The given color $expectedColor was not found in the bitmap." + } +} + +private fun ImageBitmap.containsColor(expectedColor: Color): Boolean { + val pixels = toPixelMap() + for (x in 0 until width) { + for (y in 0 until height) { + if (pixels[x, y] == expectedColor) return true + } + } + return false +} |