1 /*
<lambda>null2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.settingslib.spa.widget.chart
18
19 import android.view.ViewGroup
20 import android.widget.LinearLayout
21 import androidx.compose.animation.Crossfade
22 import androidx.compose.foundation.layout.Arrangement
23 import androidx.compose.foundation.layout.Column
24 import androidx.compose.foundation.layout.fillMaxWidth
25 import androidx.compose.foundation.layout.height
26 import androidx.compose.foundation.layout.padding
27 import androidx.compose.foundation.layout.wrapContentHeight
28 import androidx.compose.foundation.layout.wrapContentSize
29 import androidx.compose.material3.ColorScheme
30 import androidx.compose.material3.MaterialTheme
31 import androidx.compose.runtime.Composable
32 import androidx.compose.ui.Alignment
33 import androidx.compose.ui.Modifier
34 import androidx.compose.ui.graphics.Color
35 import androidx.compose.ui.graphics.toArgb
36 import androidx.compose.ui.unit.dp
37 import androidx.compose.ui.viewinterop.AndroidView
38 import com.android.settingslib.spa.framework.theme.divider
39 import com.github.mikephil.charting.charts.BarChart
40 import com.github.mikephil.charting.components.XAxis
41 import com.github.mikephil.charting.components.YAxis
42 import com.github.mikephil.charting.components.YAxis.AxisDependency
43 import com.github.mikephil.charting.data.BarData
44 import com.github.mikephil.charting.data.BarDataSet
45 import com.github.mikephil.charting.data.BarEntry
46 import com.github.mikephil.charting.formatter.IAxisValueFormatter
47
48 /**
49 * The chart settings model for [BarChart].
50 */
51 interface BarChartModel {
52 /**
53 * The chart data list for [BarChart].
54 */
55 val chartDataList: List<BarChartData>
56
57 /**
58 * The color list for [BarChart].
59 */
60 val colors: List<Color>
61
62 /**
63 * The label text formatter for x value.
64 */
65 val xValueFormatter: IAxisValueFormatter?
66 get() = null
67
68 /**
69 * The label text formatter for y value.
70 */
71 val yValueFormatter: IAxisValueFormatter?
72 get() = null
73
74 /**
75 * The minimum value for y-axis.
76 */
77 val yAxisMinValue: Float
78 get() = 0f
79
80 /**
81 * The maximum value for y-axis.
82 */
83 val yAxisMaxValue: Float
84 get() = 1f
85
86 /**
87 * The label count for y-axis.
88 */
89 val yAxisLabelCount: Int
90 get() = 3
91
92 /** If set to true, touch gestures are enabled on the [BarChart]. */
93 val enableBarchartTouch: Boolean
94 get() = true
95
96 /** The renderer provider for x-axis. */
97 val xAxisRendererProvider: XAxisRendererProvider?
98 get() = null
99 }
100
101 data class BarChartData(
102 var x: Float,
103 var y: List<Float>,
104 )
105
106 @Composable
BarChartnull107 fun BarChart(barChartModel: BarChartModel) {
108 Column(
109 modifier = Modifier
110 .fillMaxWidth()
111 .wrapContentHeight(),
112 horizontalAlignment = Alignment.CenterHorizontally,
113 verticalArrangement = Arrangement.Center
114 ) {
115 Column(
116 modifier = Modifier
117 .padding(16.dp)
118 .height(170.dp),
119 horizontalAlignment = Alignment.CenterHorizontally,
120 verticalArrangement = Arrangement.Center
121 ) {
122 val colorScheme = MaterialTheme.colorScheme
123 val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
124 val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
125 Crossfade(
126 targetState = barChartModel.chartDataList,
127 label = "chartDataList",
128 ) { barChartData ->
129 AndroidView(
130 factory = { context ->
131 BarChart(context).apply {
132 layoutParams = LinearLayout.LayoutParams(
133 ViewGroup.LayoutParams.MATCH_PARENT,
134 ViewGroup.LayoutParams.MATCH_PARENT,
135 )
136 description.isEnabled = false
137 legend.isEnabled = false
138 extraBottomOffset = 4f
139 setScaleEnabled(false)
140 setTouchEnabled(barChartModel.enableBarchartTouch)
141
142 xAxis.apply {
143 position = XAxis.XAxisPosition.BOTTOM
144 setDrawAxisLine(false)
145 setDrawGridLines(false)
146 textColor = labelTextColor
147 textSize = labelTextSize
148 valueFormatter = barChartModel.xValueFormatter
149 yOffset = 10f
150 }
151
152 barChartModel.xAxisRendererProvider?.let {
153 setXAxisRenderer(
154 it.provideXAxisRenderer(
155 getViewPortHandler(),
156 getXAxis(),
157 getTransformer(YAxis.AxisDependency.LEFT)
158 )
159 )
160 }
161
162 axisLeft.apply {
163 axisMaximum = barChartModel.yAxisMaxValue
164 axisMinimum = barChartModel.yAxisMinValue
165 isEnabled = false
166 }
167
168 axisRight.apply {
169 axisMaximum = barChartModel.yAxisMaxValue
170 axisMinimum = barChartModel.yAxisMinValue
171 gridColor = colorScheme.divider.toArgb()
172 setDrawAxisLine(false)
173 setLabelCount(barChartModel.yAxisLabelCount, true)
174 textColor = labelTextColor
175 textSize = labelTextSize
176 valueFormatter = barChartModel.yValueFormatter
177 xOffset = 10f
178 }
179 }
180 },
181 modifier = Modifier
182 .wrapContentSize()
183 .padding(4.dp),
184 update = { barChart ->
185 updateBarChartWithData(
186 chart = barChart,
187 data = barChartData,
188 colorList = barChartModel.colors,
189 colorScheme = colorScheme,
190 )
191 }
192 )
193 }
194 }
195 }
196 }
197
updateBarChartWithDatanull198 private fun updateBarChartWithData(
199 chart: BarChart,
200 data: List<BarChartData>,
201 colorList: List<Color>,
202 colorScheme: ColorScheme
203 ) {
204 val entries = data.map { item ->
205 BarEntry(item.x, item.y.toFloatArray())
206 }
207
208 val ds = BarDataSet(entries, "").apply {
209 colors = colorList.map(Color::toArgb)
210 setDrawValues(false)
211 isHighlightEnabled = true
212 highLightColor = colorScheme.primary.toArgb()
213 highLightAlpha = 255
214 }
215 // TODO: Sets round corners for bars.
216
217 chart.data = BarData(ds)
218 chart.invalidate()
219 }
220