1
+ import androidx.compose.foundation.background
2
+ import androidx.compose.foundation.border
3
+ import androidx.compose.foundation.clickable
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.Column
6
+ import androidx.compose.foundation.layout.padding
7
+ import androidx.compose.material3.MaterialTheme
8
+ import androidx.compose.material3.Text
1
9
import androidx.compose.runtime.Composable
10
+ import androidx.compose.runtime.LaunchedEffect
11
+ import androidx.compose.runtime.derivedStateOf
12
+ import androidx.compose.runtime.getValue
13
+ import androidx.compose.runtime.mutableStateOf
14
+ import androidx.compose.runtime.remember
15
+ import androidx.compose.runtime.setValue
16
+ import androidx.compose.ui.Alignment
17
+ import androidx.compose.ui.Modifier
18
+ import androidx.compose.ui.draw.drawWithContent
19
+ import androidx.compose.ui.graphics.Color
20
+ import androidx.compose.ui.text.TextStyle
21
+ import androidx.compose.ui.text.style.TextAlign
22
+ import androidx.compose.ui.unit.TextUnit
23
+ import androidx.compose.ui.unit.dp
24
+ import androidx.compose.ui.unit.isUnspecified
25
+ import androidx.compose.ui.unit.sp
26
+ import by.overpass.treemapchart.compose.TreemapChart
27
+ import by.overpass.treemapchart.core.tree.Tree
28
+ import by.overpass.treemapchart.core.tree.tree
2
29
import dev.johnoreilly.climatetrace.remote.CountryAssetEmissionsInfo
30
+ import io.github.koalaplot.core.util.generateHueColorPalette
31
+ import io.github.koalaplot.core.util.toString
32
+ import kotlinx.coroutines.Dispatchers
33
+ import kotlinx.coroutines.withContext
34
+
3
35
4
36
@Composable
5
37
actual fun CountryAssetEmissionsInfoTreeMapChart (countryAssetEmissions : List <CountryAssetEmissionsInfo >) {
38
+ var tree by remember { mutableStateOf<Tree <ChartNode >? > (null ) }
39
+
40
+ LaunchedEffect (countryAssetEmissions) {
41
+ tree = buildAssetTree(countryAssetEmissions ? : emptyList())
42
+ }
43
+
44
+ tree?.let {
45
+ TreemapChart (
46
+ data = it,
47
+ evaluateItem = ChartNode ::value
48
+ ) { node, groupContent ->
49
+ val export = node.data
50
+ if (node.children.isEmpty() && export is ChartNode .Leaf ) {
51
+ LeafItem (item = export, onClick = { })
52
+ } else if (export is ChartNode .Section ) {
53
+ SectionItem (export.color) {
54
+ groupContent(node)
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+
62
+ @Composable
63
+ fun LeafItem (
64
+ item : ChartNode .Leaf ,
65
+ modifier : Modifier = Modifier ,
66
+ onClick : (ChartNode .Leaf ) -> Unit ,
67
+ ) {
68
+ Box (
69
+ contentAlignment = Alignment .Center ,
70
+ modifier = modifier
71
+ .border(0.5 .dp, Color .White )
72
+ .background(item.color)
73
+ .clickable { onClick(item) }
74
+ .padding(4 .dp),
75
+ ) {
76
+ ShrinkableHidableText (
77
+ text = " ${item.name} \n ${item.percentage.toPercent(2 )} " ,
78
+ minSize = 6 .sp,
79
+ )
80
+ }
81
+ }
82
+
83
+ fun Double.toPercent (precision : Int ): String = " ${(this * 100.0f ).toString(precision)} %"
84
+
85
+
86
+ @Composable
87
+ fun SectionItem (
88
+ sectionColor : Color ? ,
89
+ modifier : Modifier = Modifier ,
90
+ content : @Composable () -> Unit ,
91
+ ) {
92
+ if (sectionColor != null ) {
93
+ Box (
94
+ modifier = modifier
95
+ .background(sectionColor)
96
+ ) {
97
+ content()
98
+ }
99
+ } else {
100
+ content()
101
+ }
102
+ }
6
103
104
+
105
+
106
+
107
+ suspend fun buildAssetTree (assetEmissionInfoList : List <CountryAssetEmissionsInfo >): Tree <ChartNode > = withContext(
108
+ Dispatchers .Default ) {
109
+ val filteredList = assetEmissionInfoList
110
+ .filter { it.emissions > 0 }
111
+ .sortedByDescending(CountryAssetEmissionsInfo ::emissions)
112
+ .take(10 )
113
+
114
+ val colors = generateHueColorPalette(filteredList.size)
115
+
116
+ val total = filteredList.sumOf { it.emissions.toDouble() } // .sumOf(CountryAssetEmissionsInfo::emissions)
117
+ tree(
118
+ ChartNode .Section (
119
+ name = " Total" ,
120
+ value = total,
121
+ percentage = 1.0 ,
122
+ color = null ,
123
+ ),
124
+ ) {
125
+ assetEmissionInfoList
126
+ .filter { it.emissions > 0 }
127
+ .sortedByDescending(CountryAssetEmissionsInfo ::emissions)
128
+ .take(10 )
129
+ .forEachIndexed { index, assetEmissionInfo ->
130
+ val productPercentage = assetEmissionInfo.emissions / total
131
+ node(
132
+ ChartNode .Leaf (
133
+ name = assetEmissionInfo.sector,
134
+ value = assetEmissionInfo.emissions.toDouble(),
135
+ percentage = productPercentage,
136
+ color = colors[index]
137
+ ),
138
+ )
139
+ }
140
+
141
+ }
142
+ }
143
+
144
+
145
+
146
+ @Suppress(" LongParameterList" )
147
+ @Composable
148
+ fun ShrinkableHidableText (
149
+ text : String ,
150
+ minSize : TextUnit ,
151
+ modifier : Modifier = Modifier ,
152
+ shrinkSizeFactor : Float = 0.9F,
153
+ textAlign : TextAlign = TextAlign .Center ,
154
+ style : TextStyle = MaterialTheme .typography.bodyLarge,
155
+ ) {
156
+ var fontStyle by remember { mutableStateOf(style) }
157
+ var shouldDraw by remember { mutableStateOf(false ) }
158
+ val show by remember { derivedStateOf { fontStyle.fontSize >= minSize } }
159
+ if (show) {
160
+ Text (
161
+ text = text,
162
+ modifier = modifier.drawWithContent {
163
+ if (shouldDraw) {
164
+ drawContent()
165
+ }
166
+ },
167
+ textAlign = textAlign,
168
+ onTextLayout = { result ->
169
+ if (result.hasVisualOverflow) {
170
+ fontStyle = fontStyle.copy(
171
+ fontSize = fontStyle.fontSize * shrinkSizeFactor,
172
+ letterSpacing = if (fontStyle.letterSpacing.isUnspecified) {
173
+ fontStyle.letterSpacing
174
+ } else {
175
+ fontStyle.letterSpacing * shrinkSizeFactor
176
+ },
177
+ )
178
+ } else {
179
+ shouldDraw = true
180
+ }
181
+ },
182
+ style = fontStyle,
183
+ )
184
+ }
7
185
}
0 commit comments