Khojiakbar
Khojiakbar

Reputation: 579

Draw an arc with rounded corners on Canvas

I'm trying to implement a pie chart as shown in pictures below, where corners of the arc should be rounded.

Arc with rounded corners

Arc with rounded corners

I've tried to use CornerPathEffect(), but it seems to work only on the intersection of two lines (path.lineTo()). Nothing changes if I use this method for arc (path.arcTo()).

Upvotes: 1

Views: 1299

Answers (2)

Jitender Singh
Jitender Singh

Reputation: 1

I know it's too late for this answer but here is my solution.

PieSlice.kt

data class PieSlice(
    val name: String,
    var value: Double,
    var startAngle: Float,
    var sweepAngle: Float,
    var indicatorCircleLocation: PointF,
    val paint: Paint
)

Function to make round corner arc

private fun drawCurvedArc(canvas: Canvas?, pieItem: PieSlice) {
        val path = Path()

        path.moveTo(originX, originY)

        val angleStart = pieItem.startAngle

        val angleEnd = (pieItem.startAngle - pieItem.sweepAngle)

        val arcOffset = pieItem.sweepAngle.coerceAtMost(7f)
        val lineOffset = if (pieItem.sweepAngle < 7f) 0f else 25f

        // line from origin to top
        val line1x = getPointX(angleStart, lineOffset)
        val line1y = getPointY(angleStart, lineOffset)

        path.lineTo(line1x, line1y)


        //Curve corner from line top to arc start
        val arcStartx = getPointX(angleStart - arcOffset)
        val arcStarty = getPointY(angleStart - arcOffset)
        joinLineAndArc(path, line1x, line1y, arcStartx, arcStarty)


        //Arc
        path.arcTo(
            outerRect, (pieItem.startAngle - arcOffset),
            (-pieItem.sweepAngle + 2 * arcOffset), true
        )


        val line2x = getPointX(angleEnd, lineOffset)
        val line2y = getPointY(angleEnd, lineOffset)

        val arcEndx = getPointX(angleEnd + arcOffset)
        val arcEndy = getPointY(angleEnd + arcOffset)


        //Curve corner from arc end to bottom line
        joinLineAndArc(path, arcEndx, arcEndy, line2x, line2y)


        // Bottom line
        path.lineTo(originX, originY)


        val borderPaint = Paint()
        borderPaint.strokeJoin = Paint.Join.ROUND
        borderPaint.strokeCap = Paint.Cap.ROUND
        borderPaint.color = pieItem.paint.color
        borderPaint.style = Paint.Style.FILL
        borderPaint.strokeWidth = 0f


        canvas?.drawPath(path, borderPaint)

    }

    /**
     * Join line and arc with a curve
     *
     * vector = (x1-x2,y1-y2)
     *
     * pVector perpendicular to vector
     *   pVector = (y1-y2,x2-x1)
     *
     *  midX = (x1+x2)/2
     *  midY = (y1+y2)/2
     *
     * (px,py) = (midX,midY) ± (D/√((y1−y2)^2,(x2−x1)^2))*(y1-y2,x2-x1)
     */
    private fun joinLineAndArc(
        path: Path,
        x1: Float,
        y1: Float,
        x2: Float,
        y2: Float
    ) {


        val midX: Float = (x2 + x1) / 2f
        val midY: Float = (y2 + y1) / 2f


        val x2_x1 = (x2 - x1).toDouble()
        val y1_y2 = (y1 - y2).toDouble()

        val powY = y1_y2.pow(2.0)
        val powX = x2_x1.pow(2.0)
        val den = sqrt(powY + powX)

        val len = 20.0

        // perpendicular1
        val p1x = (midX + ((len * y1_y2) / den)).toFloat()
        val p1y = (midY + ((len * x2_x1) / den)).toFloat()

        // perpendicular2
        val p2x = (midX - ((len * y1_y2) / den)).toFloat()
        val p2y = (midY - ((len * x2_x1) / den)).toFloat()

        val len1 = Math.sqrt(
            Math.pow((originX - p1x).toDouble(), 2.0)
                    + Math.pow((originY - p1y).toDouble(), 2.0)
        )

        val len2 = Math.sqrt(
            Math.pow((originX - p2x).toDouble(), 2.0)
                    + Math.pow((originY - p2y).toDouble(), 2.0)
        )

        //Make a curve to the point which is far from origin
        if (len1 > len2) {
            path.cubicTo(x1, y1, p1x, p1y, x2, y2)
        } else {
            path.cubicTo(x1, y1, p2x, p2y, x2, y2)
        }

    }

    /**
     * Get the x coordinate on a circle
     * formula for x pos: (radius) * cos(angle) + (distance from left edge of screen)
     * @param angle angle of point from origin
     * @param offset to make shorter line than actual radius
     */
    private fun getPointX(angle: Float, offset: Float = 0f): Float {
        return ((radius - offset)
                * cos(Math.toRadians((angle.toDouble())))
                + originX).toFloat()
    }

    /**
     * Get the y coordinate on a circle
     * formula for y pos: (radius) * sin(angle) + (distance from top edge of screen)
     *
     * @param angle angle of point from origin
     * @param offset to make shorter line than actual radius
     */
    private fun getPointY(angle: Float, offset: Float = 0f): Float {
        return ((radius - offset)
                * sin(Math.toRadians((angle.toDouble())))
                + originY).toFloat()

    }

Upvotes: 0

aslam khan
aslam khan

Reputation: 85

Try to set Stroke Cap of paint.

mPaint.setStrokeCap(Paint.Cap.ROUND);

Upvotes: 2

Related Questions