|
1 | 1 | package com.tien.piholeconnect.ui.component
|
2 | 2 |
|
3 |
| -import android.graphics.Color |
4 | 3 | import androidx.compose.foundation.layout.fillMaxSize
|
5 |
| -import androidx.compose.material3.LocalContentColor |
| 4 | +import androidx.compose.material3.MaterialTheme |
6 | 5 | import androidx.compose.runtime.Composable
|
| 6 | +import androidx.compose.runtime.LaunchedEffect |
7 | 7 | import androidx.compose.runtime.remember
|
8 | 8 | import androidx.compose.ui.Modifier
|
9 | 9 | import androidx.compose.ui.tooling.preview.Preview
|
10 |
| -import androidx.compose.ui.viewinterop.AndroidView |
11 |
| -import com.github.mikephil.charting.animation.Easing.EaseOutBounce |
12 |
| -import com.github.mikephil.charting.data.Entry |
13 |
| -import com.github.mikephil.charting.data.LineData |
14 |
| -import com.github.mikephil.charting.data.LineDataSet |
15 |
| -import com.github.mikephil.charting.data.LineDataSet.Mode.CUBIC_BEZIER |
16 |
| -import com.github.mikephil.charting.highlight.Highlight |
17 |
| -import com.github.mikephil.charting.listener.OnChartValueSelectedListener |
18 |
| -import com.tien.piholeconnect.model.Coordinate |
19 |
| -import com.tien.piholeconnect.ui.theme.toColorInt |
20 |
| - |
21 |
| -data class LineChartData( |
22 |
| - val label: String, |
23 |
| - val data: Iterable<Coordinate>, |
24 |
| - val configure: (LineDataSet.() -> Unit) = {} |
25 |
| -) |
26 |
| - |
27 |
| -data class SelectedValue(val label: String, val value: Coordinate?) |
| 10 | +import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis |
| 11 | +import com.patrykandpatrick.vico.compose.axis.vertical.endAxis |
| 12 | +import com.patrykandpatrick.vico.compose.chart.Chart |
| 13 | +import com.patrykandpatrick.vico.compose.chart.line.lineChart |
| 14 | +import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec |
| 15 | +import com.patrykandpatrick.vico.compose.m3.style.m3ChartStyle |
| 16 | +import com.patrykandpatrick.vico.compose.style.ProvideChartStyle |
| 17 | +import com.patrykandpatrick.vico.core.axis.horizontal.HorizontalAxis |
| 18 | +import com.patrykandpatrick.vico.core.axis.vertical.VerticalAxis |
| 19 | +import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer |
| 20 | +import com.tien.piholeconnect.model.Entry |
| 21 | +import com.tien.piholeconnect.model.LineChartData |
| 22 | +import com.tien.piholeconnect.ui.theme.PiHoleConnectTheme |
| 23 | +import com.tien.piholeconnect.ui.theme.success |
| 24 | +import java.text.DateFormat |
| 25 | +import java.text.DecimalFormat |
28 | 26 |
|
29 | 27 | @Composable
|
30 | 28 | fun LineChart(
|
31 | 29 | modifier: Modifier = Modifier,
|
32 |
| - lineData: LineChartData, |
33 |
| - onValueSelected: (Iterable<SelectedValue>) -> Unit = {}, |
34 |
| - configure: com.github.mikephil.charting.charts.LineChart.() -> Unit = {} |
35 |
| -) = LineChart(modifier, listOf(lineData), onValueSelected, configure) |
| 30 | + data: LineChartData, |
| 31 | + xAxisFormatter: ((y: Number) -> String)? = null |
| 32 | +) = LineChart(modifier, listOf(data), xAxisFormatter) |
36 | 33 |
|
37 | 34 | @Composable
|
38 | 35 | fun LineChart(
|
39 | 36 | modifier: Modifier = Modifier,
|
40 |
| - lineData: Iterable<LineChartData>, |
41 |
| - onValueSelected: (Iterable<SelectedValue>) -> Unit = {}, |
42 |
| - configure: com.github.mikephil.charting.charts.LineChart.() -> Unit = {} |
| 37 | + data: Iterable<LineChartData>, |
| 38 | + xAxisFormatter: ((y: Number) -> String)? = null |
| 39 | +) = ProvideChartStyle( |
| 40 | + m3ChartStyle(entityColors = data.map { it.color ?: MaterialTheme.colorScheme.primary }) |
43 | 41 | ) {
|
44 |
| - val contentColor = LocalContentColor.current.toColorInt() |
45 |
| - |
46 |
| - val parsedData = remember(lineData) { |
47 |
| - LineData(lineData.map { |
48 |
| - val lineDataSet = LineDataSet( |
49 |
| - it.data.map { pair -> Entry(pair.first, pair.second) }, |
50 |
| - it.label |
51 |
| - ) |
52 |
| - |
53 |
| - lineDataSet.configure(contentColor) |
54 |
| - it.configure(lineDataSet) |
55 |
| - |
56 |
| - lineDataSet |
57 |
| - }) |
58 |
| - } |
59 |
| - |
60 |
| - val listener = remember(lineData) { |
61 |
| - object : OnChartValueSelectedListener { |
62 |
| - override fun onValueSelected(e: Entry, h: Highlight) { |
63 |
| - onValueSelected(lineData.map { |
64 |
| - SelectedValue( |
65 |
| - it.label, |
66 |
| - it.data.firstOrNull { value -> value.first == e.x }) |
67 |
| - }) |
68 |
| - } |
69 |
| - |
70 |
| - override fun onNothingSelected() { |
71 |
| - onValueSelected(lineData.map { SelectedValue(it.label, null) }) |
| 42 | + val entries = remember(data) { |
| 43 | + data.map { lineData -> |
| 44 | + lineData.data.mapIndexed { index, coordinate -> |
| 45 | + Entry( |
| 46 | + if (xAxisFormatter == null) coordinate.first.toFloat() else index.toFloat(), |
| 47 | + coordinate.second.toFloat(), |
| 48 | + xDisplayValue = xAxisFormatter?.invoke(coordinate.first), |
| 49 | + yLabel = lineData.label |
| 50 | + ) |
72 | 51 | }
|
73 | 52 | }
|
74 | 53 | }
|
75 | 54 |
|
76 |
| - AndroidView( |
77 |
| - factory = { |
78 |
| - val chart = com.github.mikephil.charting.charts.LineChart(it) |
| 55 | + val chartModelProducer = remember { ChartEntryModelProducer(entries) } |
79 | 56 |
|
80 |
| - chart.axisLeft.textColor = contentColor |
81 |
| - chart.axisRight.setDrawLabels(false) |
82 |
| - chart.xAxis.textColor = contentColor |
83 |
| - chart.description.isEnabled = false |
84 |
| - chart.legend.isEnabled = false |
85 |
| - |
86 |
| - chart.setTouchEnabled(true) |
87 |
| - chart.isDragEnabled = true |
88 |
| - chart.setPinchZoom(false) |
89 |
| - chart.isScaleXEnabled = true |
90 |
| - chart.isScaleYEnabled = false |
91 |
| - chart.animateXY(0, 1000, EaseOutBounce) |
92 |
| - |
93 |
| - chart.setOnChartValueSelectedListener(listener) |
94 |
| - |
95 |
| - configure(chart) |
| 57 | + LaunchedEffect(entries) { |
| 58 | + chartModelProducer.setEntries(entries) |
| 59 | + } |
96 | 60 |
|
97 |
| - chart.data = parsedData |
98 |
| - chart.invalidate() |
99 |
| - chart |
100 |
| - }, |
101 |
| - update = { |
102 |
| - it.data = parsedData |
103 |
| - it.setOnChartValueSelectedListener(listener) |
104 |
| - it.invalidate() |
105 |
| - }, |
106 |
| - modifier = modifier |
| 61 | + Chart(modifier = modifier, |
| 62 | + chart = lineChart(), |
| 63 | + chartModelProducer = chartModelProducer, |
| 64 | + bottomAxis = bottomAxis( |
| 65 | + axis = null, |
| 66 | + tickPosition = maxOf(data.maxOf { it.data.count() / 4 }, 1).let { |
| 67 | + HorizontalAxis.TickPosition.Center(it, it) |
| 68 | + }, |
| 69 | + guideline = null, |
| 70 | + valueFormatter = { value, chartValues -> |
| 71 | + (chartValues.chartEntryModel.entries.firstOrNull()?.getOrNull(value.toInt()) as Entry?)?.xDisplayValue |
| 72 | + ?: value.toString() |
| 73 | + }, |
| 74 | + ), |
| 75 | + endAxis = endAxis( |
| 76 | + axis = null, |
| 77 | + tick = null, |
| 78 | + guideline = null, |
| 79 | + valueFormatter = { value, _ -> |
| 80 | + if (value == 0f) "" else DecimalFormat("#.##;−#.##").format(value) |
| 81 | + }, |
| 82 | + horizontalLabelPosition = VerticalAxis.HorizontalLabelPosition.Inside, |
| 83 | + maxLabelCount = 3 |
| 84 | + ), |
| 85 | + chartScrollSpec = rememberChartScrollSpec(isScrollEnabled = false), |
| 86 | + marker = rememberMarker() |
107 | 87 | )
|
108 | 88 | }
|
109 | 89 |
|
110 |
| -private fun LineDataSet.configure(contentColor: Int? = null) { |
111 |
| - contentColor?.let { this.valueTextColor = it } |
112 |
| - this.mode = CUBIC_BEZIER |
113 |
| - this.cubicIntensity = 0.2f |
114 |
| - this.setDrawFilled(true) |
115 |
| - this.setDrawCircles(false) |
116 |
| - this.lineWidth = 1.8f |
117 |
| - this.highLightColor = Color.RED |
118 |
| - this.color = Color.WHITE |
119 |
| - this.fillColor = Color.WHITE |
120 |
| - this.fillAlpha = 100 |
121 |
| - this.setDrawHorizontalHighlightIndicator(false) |
122 |
| -} |
123 |
| - |
124 |
| -@Preview |
| 90 | +@Preview(showBackground = true) |
125 | 91 | @Composable
|
126 | 92 | fun LineChartPreview() {
|
127 |
| - LineChart( |
128 |
| - Modifier.fillMaxSize(), |
129 |
| - lineData = LineChartData(label = "label", data = listOf(Pair(0f, 0f), Pair(3f, 6f))), |
130 |
| - ) |
| 93 | + val formatter = DateFormat.getDateInstance() |
| 94 | + PiHoleConnectTheme { |
| 95 | + LineChart(Modifier.fillMaxSize(), data = listOf( |
| 96 | + LineChartData( |
| 97 | + label = "label", |
| 98 | + data = listOf(1525546500 to 163, 1525547100 to 154, 1525547700 to 164), |
| 99 | + color = MaterialTheme.colorScheme.success |
| 100 | + ), LineChartData( |
| 101 | + label = "label", |
| 102 | + data = listOf(1525546500 to 30, 1525547100 to 64, 1525547700 to 10), |
| 103 | + color = MaterialTheme.colorScheme.error |
| 104 | + ) |
| 105 | + ), xAxisFormatter = { formatter.format(it) }) |
| 106 | + } |
131 | 107 | }
|
0 commit comments