Skip to content

Commit aba320e

Browse files
Added even more customization for donut chart by controlling label color (slice or specified label), label type (percentage or slice value), refacto percentageProperties to labelProperties, controlling display of unit (is empty)
1 parent 7b3e320 commit aba320e

File tree

9 files changed

+180
-100
lines changed

9 files changed

+180
-100
lines changed

YChartsLib/src/androidTest/java/co/yml/charts/piechart/DonutPieChartTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class DonutPieChartTest {
2323
val composeTestRule = createComposeRule()
2424

2525
private val pieChartConfig = PieChartConfig(
26-
percentVisible = false,
26+
labelVisible = false,
2727
strokeWidth = 120f,
28-
percentColor = Color.Black
28+
labelColor = Color.Black
2929
)
3030

3131
private val pieChartData = PieChartData(
@@ -70,4 +70,3 @@ class DonutPieChartTest {
7070
composeTestRule.onNodeWithText("C").assertDoesNotExist()
7171
}
7272
}
73-

YChartsLib/src/androidTest/java/co/yml/charts/piechart/PieChartTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ class PieChartTest {
2121
val composeTestRule = createComposeRule()
2222

2323
private val pieChartConfig = PieChartConfig(
24-
percentVisible = false,
24+
labelVisible = false,
2525
strokeWidth = 120f,
26-
percentColor = Color.Black
26+
labelColor = Color.Black
2727
)
2828
private val pieChartData = PieChartData(
2929
slices = listOf(

YChartsLib/src/main/java/co/yml/charts/ui/piechart/charts/DonutPieChart.kt

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
1818
import androidx.compose.ui.Modifier
1919
import androidx.compose.ui.geometry.Size
2020
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.graphics.NativeCanvas
2122
import androidx.compose.ui.graphics.nativeCanvas
2223
import androidx.compose.ui.graphics.toArgb
2324
import androidx.compose.ui.input.pointer.pointerInput
@@ -69,8 +70,8 @@ fun DonutPieChart(
6970
progressSize.add(sweepAngles[x] + progressSize[x - 1])
7071
}
7172

72-
var activePie by rememberSaveable {
73-
mutableStateOf(-1)
73+
var activePie: PieChartData.Slice? by rememberSaveable {
74+
mutableStateOf(null)
7475
}
7576
val accessibilitySheetState =
7677
rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
@@ -142,11 +143,12 @@ fun DonutPieChart(
142143
)
143144
progressSize.forEachIndexed { index, item ->
144145
if (clickedAngle <= item) {
145-
activePie = if (activePie != index)
146-
index
146+
val selectedSlice = pieChartData.slices[index]
147+
activePie = if (activePie != selectedSlice)
148+
selectedSlice
147149
else
148-
-1
149-
onSliceClick(pieChartData.slices[index])
150+
null
151+
onSliceClick(selectedSlice)
150152
return@detectTapGestures
151153
}
152154
}
@@ -174,70 +176,56 @@ fun DonutPieChart(
174176
padding = padding,
175177
isDonut = pieChartData.plotType == PlotType.Donut,
176178
strokeWidth = pieChartConfig.strokeWidth,
177-
isActive = activePie == index,
179+
isActive = activePie == pieChartData.slices[index],
178180
pieChartConfig = pieChartConfig
179181
)
180182
sAngle += arcProgress
181183
}
182-
183-
if (activePie != -1 && pieChartConfig.percentVisible)
184-
drawContext.canvas.nativeCanvas.apply {
185-
val fontSize = pieChartConfig.percentageFontSize.toPx()
186-
drawText(
187-
"${proportions[activePie].roundToInt()}%",
188-
(sideSize / 2) + fontSize / 4, (sideSize / 2) + fontSize / 3,
189-
Paint().apply {
190-
color = pieChartConfig.percentColor.toArgb()
191-
textSize = fontSize
192-
textAlign = Paint.Align.CENTER
193-
typeface = pieChartConfig.percentageTypeface
194-
195-
}
196-
)
197-
}
198-
199184
when {
200-
activePie != -1 && pieChartConfig.percentVisible -> {
185+
activePie != null && pieChartConfig.labelVisible -> {
201186
drawContext.canvas.nativeCanvas.apply {
202-
val fontSize = pieChartConfig.percentageFontSize.toPx()
203-
this.drawText(
204-
"${proportions[activePie].roundToInt()}%",
205-
(sideSize / 2) + fontSize / 4, (sideSize / 2) + fontSize / 3,
206-
Paint().apply {
207-
color = pieChartConfig.percentColor.toArgb()
208-
textSize = fontSize
209-
textAlign = Paint.Align.CENTER
210-
typeface = pieChartConfig.percentageTypeface
187+
val fontSize = pieChartConfig.labelFontSize.toPx()
188+
var isValue = false
189+
val textToDraw = when (pieChartConfig.labelType) {
190+
PieChartConfig.LabelType.PERCENTAGE -> "${
191+
proportions[pieChartData.slices.indexOf(
192+
activePie
193+
)].roundToInt()
194+
}%"
195+
PieChartConfig.LabelType.VALUE -> {
196+
isValue = true
197+
// We have already checked that activePie is not null so !! is safe here
198+
activePie!!.value.toString()
211199
}
200+
}
201+
val labelColor = when (pieChartConfig.labelColorType) {
202+
PieChartConfig.LabelColorType.SPECIFIED_COLOR -> pieChartConfig.labelColor
203+
PieChartConfig.LabelColorType.SLICE_COLOR -> activePie!!.color
204+
}
205+
val shouldShowUnit = isValue && pieChartConfig.sumUnit.isNotEmpty()
206+
drawLabel(
207+
canvas = this,
208+
pieChartConfig = pieChartConfig,
209+
labelColor = labelColor,
210+
shouldShowUnit = shouldShowUnit,
211+
fontSize = fontSize,
212+
textToDraw = textToDraw,
213+
sideSize = sideSize
212214
)
213215
}
214216
}
215-
activePie == -1 && pieChartConfig.isSumVisible -> {
217+
activePie == null && pieChartConfig.isSumVisible -> {
216218
drawContext.canvas.nativeCanvas.apply {
217-
val fontSize = pieChartConfig.percentageFontSize.toPx()
218-
val paint = Paint().apply {
219-
color = pieChartConfig.percentColor.toArgb()
220-
textSize = fontSize
221-
textAlign = Paint.Align.CENTER
222-
typeface = pieChartConfig.percentageTypeface
223-
}
224-
val x: Float = (sideSize / 2).toFloat()
225-
var y: Float = (sideSize / 2).toFloat() + fontSize / 3
226-
if (pieChartConfig.sumUnit.isNotEmpty()){
227-
y -= (paint.fontSpacing / 4)
228-
}
229-
this.drawText(
230-
"$sumOfValues",
231-
x,
232-
y,
233-
paint
234-
)
235-
y += paint.fontSpacing
236-
this.drawText(
237-
pieChartConfig.sumUnit,
238-
x,
239-
y,
240-
paint
219+
val fontSize = pieChartConfig.labelFontSize.toPx()
220+
val textToDraw = "$sumOfValues"
221+
drawLabel(
222+
canvas = this,
223+
pieChartConfig = pieChartConfig,
224+
labelColor = pieChartConfig.labelColor,
225+
shouldShowUnit = pieChartConfig.sumUnit.isNotEmpty(),
226+
fontSize = fontSize,
227+
textToDraw = textToDraw,
228+
sideSize = sideSize
241229
)
242230
}
243231
}
@@ -268,3 +256,36 @@ fun DonutPieChart(
268256
}
269257
}
270258
}
259+
260+
private fun drawLabel(
261+
canvas: NativeCanvas,
262+
pieChartConfig: PieChartConfig,
263+
labelColor: Color,
264+
shouldShowUnit: Boolean,
265+
fontSize: Float,
266+
textToDraw: String,
267+
sideSize: Int
268+
) {
269+
val paint = Paint().apply {
270+
color = labelColor.toArgb()
271+
textSize = fontSize
272+
textAlign = Paint.Align.CENTER
273+
typeface = pieChartConfig.labelTypeface
274+
}
275+
val x = (sideSize / 2).toFloat()
276+
var y: Float = (sideSize / 2).toFloat() + fontSize / 3
277+
if (shouldShowUnit)
278+
y -= (paint.fontSpacing / 4)
279+
canvas.drawText(
280+
textToDraw,
281+
x, y,
282+
paint
283+
)
284+
y += paint.fontSpacing
285+
canvas.drawText(
286+
pieChartConfig.sumUnit,
287+
x,
288+
y,
289+
paint
290+
)
291+
}

YChartsLib/src/main/java/co/yml/charts/ui/piechart/charts/PieChart.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ fun PieChart(
209209
it.nativeCanvas.withRotation(
210210
arcCenter, x, y
211211
) {
212-
if (pieChartConfig.percentVisible) {
212+
if (pieChartConfig.labelVisible) {
213213
label = "$label ${proportions[index].roundToInt()}%"
214214
}
215215
it.nativeCanvas.drawText(

YChartsLib/src/main/java/co/yml/charts/ui/piechart/models/PieChartConfig.kt

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,22 @@ import co.yml.charts.ui.piechart.PieChartConstants.DEFAULT_STROKE_WIDTH
2525
* @param isAnimationEnable: Boolean Flag for enabling animation
2626
* @param animationDuration: Duration of animation
2727
* @param strokeWidth: Stroke width of Donut Chart
28-
* @param percentageFontSize: Percentage text font size
29-
* @param percentageTypeface: Percentage text typeface
30-
* @param percentVisible: Percentage text visibility
31-
* @param percentColor: Percentage text color
28+
* @param labelFontSize: Label text font size
29+
* @param labelTypeface: Label text typeface
30+
* @param labelVisible: Label text visibility
31+
* @param labelType: Type of label (percentage or value of slice), value available only for Donut Chart
32+
* @param labelColor: Label text color
33+
* @param labelColorType: Label text color according to the specified in color in labelColor or the slice color, only for Donut Chart
3234
* @param activeSliceAlpha: Opacity of the active slice
3335
* @param inActiveSliceAlpha: Opacity of the inactive slice
3436
* @param isEllipsizeEnabled: Boolean flag for enabling ellipsize
3537
* @param sliceMinTextWidthToEllipsize: Minimum width of the label post which label will be ellipsized
3638
* @param sliceLabelEllipsizeAt: Position at which the label will be truncated or ellipsized
3739
* @param chartPadding: Padding for the Pie chart/Donut Chart
3840
* @param accessibilityConfig: Configs related to accessibility service defined here in [AccessibilityConfig]
39-
* @param isSumVisible: When no slice is selected show the sum of values
41+
* @param isSumVisible: When no slice is selected show the sum of values, only used for Donut Chart
4042
* @param isClickOnSliceEnabled: Enable/Disable the click on slice
41-
* @param sumUnit: The unit of the sum of values
43+
* @param sumUnit: The unit of the sum of values, only used for Donut Chart
4244
*/
4345
data class PieChartConfig(
4446
val startAngle: Float = DEFAULT_START_ANGLE,
@@ -49,10 +51,12 @@ data class PieChartConfig(
4951
val isAnimationEnable: Boolean = false,
5052
@IntRange(from = 1) val animationDuration: Int = 500,
5153
val strokeWidth: Float = DEFAULT_STROKE_WIDTH,
52-
val percentageFontSize: TextUnit = 24.sp,
53-
val percentageTypeface: Typeface = Typeface.DEFAULT,
54-
val percentVisible: Boolean = false,
55-
val percentColor: Color = Color.White,
54+
val labelFontSize: TextUnit = 24.sp,
55+
val labelTypeface: Typeface = Typeface.DEFAULT,
56+
val labelVisible: Boolean = false,
57+
val labelType: LabelType = LabelType.PERCENTAGE,
58+
val labelColor: Color = Color.White,
59+
val labelColorType: LabelColorType = LabelColorType.SPECIFIED_COLOR,
5660
val activeSliceAlpha: Float = .8f,
5761
val inActiveSliceAlpha: Float = 1f,
5862
val isEllipsizeEnabled: Boolean = false,
@@ -65,4 +69,14 @@ data class PieChartConfig(
6569
val isSumVisible: Boolean = false,
6670
val sumUnit: String = "",
6771
val isClickOnSliceEnabled: Boolean = true
68-
)
72+
){
73+
enum class LabelType {
74+
PERCENTAGE,
75+
VALUE
76+
}
77+
78+
enum class LabelColorType {
79+
SPECIFIED_COLOR,
80+
SLICE_COLOR
81+
}
82+
}

app/src/main/java/co/yml/ycharts/app/presentation/DonutChartActivity.kt

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.widget.Toast
77
import androidx.activity.ComponentActivity
88
import androidx.activity.compose.setContent
99
import androidx.compose.foundation.layout.*
10+
import androidx.compose.foundation.lazy.LazyColumn
1011
import androidx.compose.material.*
1112
import androidx.compose.runtime.Composable
1213
import androidx.compose.runtime.rememberCoroutineScope
@@ -45,11 +46,17 @@ class DonutChartActivity : ComponentActivity() {
4546
val context = LocalContext.current
4647
Box(
4748
modifier = Modifier
48-
.padding(it)
4949
.fillMaxSize()
50+
.padding(it)
5051
) {
51-
Spacer(modifier = Modifier.height(20.dp))
52-
DonutChart1(context)
52+
LazyColumn{
53+
items(count = 3){ item ->
54+
when (item){
55+
0 -> DonutChart1(context)
56+
1 -> DonutChart2(context)
57+
}
58+
}
59+
}
5360
}
5461
}
5562
}
@@ -71,24 +78,64 @@ private fun DonutChart1(context: Context) {
7178
val proportions = data.slices.proportion(sumOfValues)
7279
val pieChartConfig =
7380
PieChartConfig(
74-
percentVisible = true,
81+
labelVisible = true,
82+
strokeWidth = 120f,
83+
labelColor = Color.Black,
84+
activeSliceAlpha = .9f,
85+
isEllipsizeEnabled = true,
86+
labelTypeface = Typeface.defaultFromStyle(Typeface.BOLD),
87+
isAnimationEnable = true,
88+
chartPadding = 25,
89+
labelFontSize = 42.sp,
90+
)
91+
Column(modifier = Modifier.height(500.dp)) {
92+
Legends(legendsConfig = DataUtils.getLegendsConfigFromPieChartData(pieChartData = data, 3))
93+
DonutPieChart(
94+
modifier = Modifier
95+
.fillMaxWidth()
96+
.height(400.dp),
97+
data,
98+
pieChartConfig
99+
) { slice ->
100+
Toast.makeText(context, slice.label, Toast.LENGTH_SHORT).show()
101+
}
102+
}
103+
}
104+
105+
@ExperimentalMaterialApi
106+
@Composable
107+
private fun DonutChart2(context: Context) {
108+
val accessibilitySheetState =
109+
rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
110+
val scope = rememberCoroutineScope()
111+
val data = DataUtils.getDonutChartData()
112+
// Sum of all the values
113+
val sumOfValues = data.totalLength
114+
115+
// Calculate each proportion value
116+
val proportions = data.slices.proportion(sumOfValues)
117+
val pieChartConfig =
118+
PieChartConfig(
119+
labelVisible = true,
75120
strokeWidth = 120f,
76-
percentColor = Color.Black,
121+
labelColor = Color.Black,
77122
activeSliceAlpha = .9f,
78123
isEllipsizeEnabled = true,
79-
percentageTypeface = Typeface.defaultFromStyle(Typeface.BOLD),
124+
labelTypeface = Typeface.defaultFromStyle(Typeface.BOLD),
80125
isAnimationEnable = true,
81126
chartPadding = 25,
82-
percentageFontSize = 42.sp,
127+
labelFontSize = 42.sp,
83128
isSumVisible = true,
84-
sumUnit = "Unit"
129+
sumUnit = "",
130+
labelColorType = PieChartConfig.LabelColorType.SLICE_COLOR,
131+
labelType = PieChartConfig.LabelType.VALUE
85132
)
86-
Column {
133+
Column(modifier = Modifier.height(500.dp)) {
87134
Legends(legendsConfig = DataUtils.getLegendsConfigFromPieChartData(pieChartData = data, 3))
88135
DonutPieChart(
89136
modifier = Modifier
90137
.fillMaxWidth()
91-
.height(500.dp),
138+
.height(400.dp),
92139
data,
93140
pieChartConfig
94141
) { slice ->

0 commit comments

Comments
 (0)