在饼图(甜甜圈图)的中心绘制图例。

5
我需要在饼图中心为每个扇形实现一个图例的甜甜圈图。我使用compose,对于图表本身没有问题,但我不明白如何在每个扇形的中心绘制文本。
下面的图片展示了文本应该如何显示的示例。

enter image description here

同时,我也提供了绘制甜甜圈图表的代码

Box(modifier = modifier
       .size(middleLineRadius * 2)
       .drawBehind {
           var startAngle = 0f

           // draw each arc for each data entry in chart
           data.forEach { chartEntry ->
               drawArc(
                   color = chartEntry.color,
                   startAngle = startAngle,
                   sweepAngle = sweepAngle,
                   useCenter = false,
                   style = Stroke(width = chartBarWidth.toPx(), cap = StrokeCap.Butt)
               )
               startAngle += sweepAngle // increase sweep angle
           }
       }
   )
2个回答

10

结果

enter image description here

您可以使用TextMeasurer为每个值测量文本来实现此目标。

    val textMeasurer = rememberTextMeasurer()
    val textMeasureResults = remember(chartDataList) {
        chartDataList.map {
            textMeasurer.measure(
                text = "%${it.data.toInt()}",
                style = TextStyle(
                    fontSize = 18.sp
                )
            )
        }
    }

然后使用弧度制中的cos和sin绘制文本

drawText(
    textLayoutResult = textMeasureResult,
    color = Color.Gray,
    topLeft = Offset(
        -textCenter.x + center.x + (innerRadius + strokeWidth / 2) * cos(
            angleInRadians
        ),
        -textCenter.y + center.y + (innerRadius + strokeWidth / 2) * sin(
            angleInRadians
        )
    )
)

startAngle += sweepAngle

完整实现

@Preview
@Composable
private fun PieChartWithText() {

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp),
        contentAlignment = Alignment.Center
    ) {
        val chartDataList = listOf(
            ChartData(Pink400, 10f),
            ChartData(Orange400, 20f),
            ChartData(Yellow400, 15f),
            ChartData(Green400, 5f),
            ChartData(Blue400, 50f),
        )

        val textMeasurer = rememberTextMeasurer()
        val textMeasureResults = remember(chartDataList) {
            chartDataList.map {
                textMeasurer.measure(
                    text = "%${it.data.toInt()}",
                    style = TextStyle(
                        fontSize = 18.sp
                    )
                )
            }
        }

        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(1f)
        ) {
            val width = size.width
            val radius = width / 2f
            val strokeWidth = radius * .4f
            val innerRadius = radius - strokeWidth
            val lineStrokeWidth = 3.dp.toPx()

            var startAngle = -90f

            for (index in 0..chartDataList.lastIndex) {

                val chartData = chartDataList[index]
                val sweepAngle = chartData.data.asAngle
                val angleInRadians = (startAngle + sweepAngle / 2).degreeToAngle
                val textMeasureResult = textMeasureResults[index]
                val textSize = textMeasureResult.size

                drawArc(
                    color = chartData.color,
                    startAngle = startAngle,
                    sweepAngle = sweepAngle,
                    useCenter = false,
                    topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
                    size = Size(width - strokeWidth, width - strokeWidth),
                    style = Stroke(strokeWidth)
                )

                rotate(
                    90f + startAngle
                ) {
                    drawLine(
                        color = Color.White,
                        start = Offset(radius, strokeWidth),
                        end = Offset(radius, 0f),
                        strokeWidth = lineStrokeWidth
                    )
                }

                val textCenter = textSize.center

    drawText(
        textLayoutResult = textMeasureResult,
        color = Color.Gray,
        topLeft = Offset(
            -textCenter.x + center.x + (innerRadius + strokeWidth / 2) * cos(
                angleInRadians
            ),
            -textCenter.y + center.y + (innerRadius + strokeWidth / 2) * sin(
                angleInRadians
            )
        )
    )

    startAngle += sweepAngle
            }
        }
    }
}

private val Float.degreeToAngle
    get() = (this * Math.PI / 180f).toFloat()

private val Float.asAngle: Float
    get() = this * 360f / 100f

@Immutable
data class ChartData(val color: Color, val data: Float)

同时也很容易在构图中以微小的变化来制作动画图表

enter image description here

@Preview
@Composable
private fun AnimatedChart() {

    val animatable = remember {
        Animatable(-90f)
    }

    val finalValue = 270f

    LaunchedEffect(key1 = animatable) {
        animatable.animateTo(
            targetValue = finalValue,
            animationSpec = tween(
                delayMillis = 4000,
                durationMillis = 2000
            )
        )
    }
    val currentSweepAngle = animatable.value


    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(20.dp),
        contentAlignment = Alignment.Center
    ) {
        val chartDataList = listOf(
            ChartData(Pink400, 10f),
            ChartData(Orange400, 20f),
            ChartData(Yellow400, 15f),
            ChartData(Green400, 5f),
            ChartData(Blue400, 50f),
        )

        val textMeasurer = rememberTextMeasurer()
        val textMeasureResults = remember(chartDataList) {
            chartDataList.map {
                textMeasurer.measure(
                    text = "%${it.data.toInt()}",
                    style = TextStyle(
                        fontSize = 18.sp
                    )
                )
            }
        }

        Canvas(
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(1f)
        ) {
            val width = size.width
            val radius = width / 2f
            val strokeWidth = radius * .4f
            val innerRadius = radius - strokeWidth
            val lineStrokeWidth = 3.dp.toPx()

            var startAngle = -90f

            for (index in 0..chartDataList.lastIndex) {

                val chartData = chartDataList[index]
                val sweepAngle = chartData.data.asAngle
                val angleInRadians = (startAngle + sweepAngle / 2).degreeToAngle
                val textMeasureResult = textMeasureResults[index]
                val textSize = textMeasureResult.size

                if (startAngle <= currentSweepAngle) {
                    drawArc(
                        color = chartData.color,
                        startAngle = startAngle,
                        sweepAngle = sweepAngle.coerceAtMost(currentSweepAngle - startAngle),
                        useCenter = false,
                        topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
                        size = Size(width - strokeWidth, width - strokeWidth),
                        style = Stroke(strokeWidth)
                    )
                }

                rotate(
                    90f + startAngle
                ) {
                    drawLine(
                        color = Color.White,
                        start = Offset(radius, strokeWidth),
                        end = Offset(radius, 0f),
                        strokeWidth = lineStrokeWidth
                    )
                }

                val textCenter = textSize.center

                if (currentSweepAngle == finalValue) {
                    drawText(
                        textLayoutResult = textMeasureResult,
                        color = Brown400,
                        topLeft = Offset(
                            -textCenter.x + center.x + (innerRadius + strokeWidth / 2) * cos(
                                angleInRadians
                            ),
                            -textCenter.y + center.y + (innerRadius + strokeWidth / 2) * sin(
                                angleInRadians
                            )
                        )
                    )
                }

                startAngle += sweepAngle
            }
        }
    }
}

textMeasurer.measure( text = buildAnnotatedString { append("%${it.data.toInt()}")}, - Vsevolod

0

伪代码中:

midAngle = startAngle + sweepAngle/2
midPosx = (innerradius+outerradius)/2 * cos(midAngle)
midPosy = (innerradius+outerradius)/2 * sin(midAngle)
textWidth, textHeight = calculate_size(text)
textx = midPosx - textWidth/2
texty = midPosy - textHeight/2  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接