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