draw round corners in canvas with drawArc

651 Views Asked by At

I'm trying to create a pie chart in Jetpack Compose. I'm trying to make corners round for each Pie in the Chart. But, I'm having issues making corner rounds. I tried using cap = StrokeCap.Round in drawArc in canvas, but did not get any luck making just the corners rounds.

This is what I have so far, and the result looks like this. As you can see the corners of each pies are rectangle. Is there a way to make them round?

enter image description here

@Composable
fun Chart() {
    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1f)
    ) {
        drawIntoCanvas {
            val width = size.width
            val radius = width / 2f
            val strokeWidth = radius * .3f
            var startAngle = 0f

            val items = listOf(25f, 25f, 25f, 25f, 25f, 25f, 25f, 25f)

            items.forEach {
                val sweepAngle = it.toAngle

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

                startAngle += sweepAngle
            }
        }
    }
}

private val Float.toAngle: Float
    get() = this * 180 / 100

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MyApplicationTheme {
        Chart()
    }
}

I’m trying to make it so the arc looks like this

enter image description here

2

There are 2 best solutions below

0
On BEST ANSWER
  • I found a workaround by drawing two arcs one with style = Fill and one with style = Stroke. Hope this helps someone.

  • Another way to do it without using stroke is to do it using complex trig. That includes converting between polar and cartesian.

  • Using these formulas and given outer/inner radius and angle you can calculate all 4 corners of an arc.

x1 = r1 cos ()
y1 = r1 sin ()
x2 = r2 cos ()
y3 = r2 sin ()
where radius = r1 = outerRadius, r2 = innerRadius and  = Math.toRadians(sweepAngle)

using the math above you should be able to find x and y points of an arc. then using Path you can draw

Path().apply{
   moveTo(x, y) // start of arc in top left - rounded edge
   arcTo(Rect(..), startAngle, sweepAngle, false) // or you can use `quadTo` or `curveTo` but you'll have to find control points
   quadTo(..) // to draw the rounded corner - top right
   lineTo(..) // line to next point - bottom right
   quadTo(..) // draw rounded corner in bottom right
   arcTo() // inner arc to bottom left corner - rounded edge
   quadTo() // bottom left corner
   lineTo() // line to top left corner
   quadTo() // top left corner
   close()
}

// Example with stroke..

@Composable
fun Chart() {
    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1f)
    ) {
        drawIntoCanvas {
            val width = size.width
            val radius = width / 2f
            val innerRadius = radius - 40f
         
            var startAngle = 0f
            val center = Offset(size.width / 2f, size.height / 2f)

            val items = listOf(25f, 25f, 25f, 25f, 25f, 25f, 25f, 25f)

            items.forEach {
                val sweepAngle = it.toAngle

                val path = Path().apply{
                   drawArc(
                      Rect(
                        center.x - radius,
                        center.y - radius,
                        center.x + radius,
                        center.y + radius
                      ),
                      startAngle, 
                      sweepAngle,
                      false
                   )
                    drawArc(
                      Rect(
                        center.x - innerRadius,
                        center.y - innerRadius,
                        center.x + innerRadius,
                        center.y + innerRadius
                      ),
                      startAngle + sweepAngle, 
                      -sweepAngle,
                      false
                   )
                }
                
                drawPath(path, Color.Red, style = Fill)
                drawPath(path, Color.Red, style = Stroke(30f, StrokeCap.Round, StrokeJoin.Round))

                startAngle += sweepAngle
            }
        }
    }
}
2
On

The issue lies with how you structure the gap between arcs that's why you don't see rounded edges with StrokeCap.Round. You need to do it as

startAngle = startAngle + gap,

and sweepAngle as

sweepAngle = sweepAngle - gap * 2,

and when you have enough gap round

enter image description here

@Composable
fun Chart() {
    Canvas(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(1f)
    ) {
        drawIntoCanvas {
            val width = size.width
            val radius = width / 2f
            val strokeWidth = radius * .3f
            var startAngle = 0f

            val items = listOf(25f, 25f, 25f, 25f, 25f, 25f, 25f, 25f)

            items.forEach {
                val sweepAngle = it.toAngle
                val gap = 25f/2

                drawArc(
                    color = Color.Gray,
                    startAngle = startAngle + gap,
                    sweepAngle = sweepAngle - gap * 2,
                    useCenter = false,
                    topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
                    size = Size(width - strokeWidth, width - strokeWidth),
                    style = Stroke(strokeWidth, cap = StrokeCap.Round)
                )

                startAngle += sweepAngle
            }
        }
    }
}

I explained the logic behind in detail in this answer

https://stackoverflow.com/a/76135243/5457853