Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rounded corners in each pie chart slice #4540

Open
kelvinofula opened this issue Dec 22, 2020 · 7 comments
Open

Rounded corners in each pie chart slice #4540

kelvinofula opened this issue Dec 22, 2020 · 7 comments

Comments

@kelvinofula
Copy link

Hi, was wondering if you could please help me achieve this look. I've tried but nothing seems to work. I just want my slices to be rounded the way it's displayed in this image below. Thank you
Screen Shot 2020-12-22 at 7 28 39 PM

@Khislatjon
Copy link

I have the same problem.

@4np
Copy link
Contributor

4np commented Jan 29, 2021

You could probably accomplish this by overriding the pie renderer, like in #4297

@ecemmine
Copy link

@kelvinofula Hi,

Did you this? I have same problem. I can't do slices rounded

@Paget96
Copy link

Paget96 commented Apr 4, 2023

Maybe this can help you, I cannot help more than this. This is a renderer code for MPAndroidChart PieChart but on Android

import android.graphics.Canvas
import android.graphics.Path
import android.graphics.RectF
import com.github.mikephil.charting.animation.ChartAnimator
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.interfaces.datasets.IPieDataSet
import com.github.mikephil.charting.renderer.PieChartRenderer
import com.github.mikephil.charting.utils.MPPointF
import com.github.mikephil.charting.utils.ViewPortHandler
import com.github.mikephil.charting.utils.Utils
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin


class RoundedSlicesPieChartRenderer(
    chart: PieChart?,
    animator: ChartAnimator?,
    viewPortHandler: ViewPortHandler?
) : PieChartRenderer(chart, animator, viewPortHandler) {

    init {
        chart?.setDrawRoundedSlices(true)
    }

    override fun drawDataSet(c: Canvas?, dataSet: IPieDataSet) {
        var angle = 0f
        val rotationAngle = mChart.rotationAngle
        val phaseX = mAnimator.phaseX
        val phaseY = mAnimator.phaseY
        val circleBox = mChart.circleBox
        val entryCount = dataSet.entryCount
        val drawAngles = mChart.drawAngles
        val center = mChart.centerCircleBox
        val radius = mChart.radius
        val drawInnerArc = mChart.isDrawHoleEnabled && !mChart.isDrawSlicesUnderHoleEnabled
        val userInnerRadius = if (drawInnerArc) radius * (mChart.holeRadius / 100f) else 0f
        val roundedRadius = (radius - radius * mChart.holeRadius / 100f) / 2f
        val roundedCircleBox = RectF()
        var visibleAngleCount = 0
        for (j in 0 until entryCount) {
            // draw only if the value is greater than zero
            if (abs(dataSet.getEntryForIndex(j).y) > Utils.FLOAT_EPSILON) {
                visibleAngleCount++
            }
        }
        val sliceSpace = if (visibleAngleCount <= 1) 0f else getSliceSpace(dataSet)
        val pathBuffer = Path()
        val mInnerRectBuffer = RectF()
        for (j in 0 until entryCount) {
            val sliceAngle = drawAngles[j]
            var innerRadius = userInnerRadius
            val e = dataSet.getEntryForIndex(j)

            // draw only if the value is greater than zero
            if (abs(e.y) <= Utils.FLOAT_EPSILON) {
                angle += sliceAngle * phaseX
                continue
            }

            // Don't draw if it's highlighted, unless the chart uses rounded slices
            if (mChart.needsHighlight(j) && !drawInnerArc) {
                angle += sliceAngle * phaseX
                continue
            }
            val accountForSliceSpacing = sliceSpace > 0f && sliceAngle <= 180f
            mRenderPaint.color = dataSet.getColor(j)
            val sliceSpaceAngleOuter =
                if (visibleAngleCount == 1) 0f else sliceSpace / (Utils.FDEG2RAD * radius)
            val startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2f) * phaseY
            var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY
            if (sweepAngleOuter < 0f) {
                sweepAngleOuter = 0f
            }
            pathBuffer.reset()
            val arcStartPointX =
                center.x + radius * cos(startAngleOuter * Utils.FDEG2RAD).toFloat()
            val arcStartPointY =
                center.y + radius * sin(startAngleOuter * Utils.FDEG2RAD).toFloat()
            if (sweepAngleOuter >= 360f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
                // Android is doing "mod 360"
                pathBuffer.addCircle(center.x, center.y, radius, Path.Direction.CW)
            } else {
                if (drawInnerArc) {
                    val x =
                        center.x + (radius - roundedRadius) * cos(startAngleOuter * Utils.FDEG2RAD)
                    val y =
                        center.y + (radius - roundedRadius) * sin(startAngleOuter * Utils.FDEG2RAD)
                    roundedCircleBox[x - roundedRadius, y - roundedRadius, x + roundedRadius] =
                        y + roundedRadius
                    pathBuffer.arcTo(roundedCircleBox, startAngleOuter - 180, 180F)
                }
                pathBuffer.arcTo(
                    circleBox,
                    startAngleOuter,
                    sweepAngleOuter
                )
            }

            // API < 21 does not receive floats in addArc, but a RectF
            mInnerRectBuffer[center.x - innerRadius, center.y - innerRadius, center.x + innerRadius] =
                center.y + innerRadius
            if (drawInnerArc && (innerRadius > 0f || accountForSliceSpacing)) {
                if (accountForSliceSpacing) {
                    var minSpacedRadius = calculateMinimumRadiusForSpacedSlice(
                        center, radius,
                        sliceAngle * phaseY,
                        arcStartPointX, arcStartPointY,
                        startAngleOuter,
                        sweepAngleOuter
                    )
                    if (minSpacedRadius < 0f) minSpacedRadius = -minSpacedRadius
                    innerRadius = innerRadius.coerceAtLeast(minSpacedRadius)
                }
                val sliceSpaceAngleInner =
                    if (visibleAngleCount == 1 || innerRadius == 0f) 0f else sliceSpace / (Utils.FDEG2RAD * innerRadius)
                val startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2f) * phaseY
                var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY
                if (sweepAngleInner < 0f) {
                    sweepAngleInner = 0f
                }
                val endAngleInner = startAngleInner + sweepAngleInner
                if (sweepAngleOuter >= 360f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
                    // Android is doing "mod 360"
                    pathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW)
                } else {
                    val x =
                        center.x + (radius - roundedRadius) * cos(endAngleInner * Utils.FDEG2RAD)
                    val y =
                        center.y + (radius - roundedRadius) * sin(endAngleInner * Utils.FDEG2RAD)
                    roundedCircleBox[x - roundedRadius, y - roundedRadius, x + roundedRadius] =
                        y + roundedRadius
                    pathBuffer.arcTo(roundedCircleBox, endAngleInner, 180F)
                    pathBuffer.arcTo(mInnerRectBuffer, endAngleInner, -sweepAngleInner)
                }
            } else {
                if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) {
                    if (accountForSliceSpacing) {
                        val angleMiddle = startAngleOuter + sweepAngleOuter / 2f
                        val sliceSpaceOffset = calculateMinimumRadiusForSpacedSlice(
                            center,
                            radius,
                            sliceAngle * phaseY,
                            arcStartPointX,
                            arcStartPointY,
                            startAngleOuter,
                            sweepAngleOuter
                        )
                        val arcEndPointX = center.x +
                                sliceSpaceOffset * cos(angleMiddle * Utils.FDEG2RAD)
                        val arcEndPointY = center.y +
                                sliceSpaceOffset * sin(angleMiddle * Utils.FDEG2RAD)
                        pathBuffer.lineTo(
                            arcEndPointX,
                            arcEndPointY
                        )
                    } else {
                        pathBuffer.lineTo(
                            center.x,
                            center.y
                        )
                    }
                }
            }
            pathBuffer.close()
            mBitmapCanvas.drawPath(pathBuffer, mRenderPaint)
            angle += sliceAngle * phaseX
        }
        MPPointF.recycleInstance(center)
    }
}
video_2023-04-04_16-08-51.mp4

@minhDTNHust
Copy link

minhDTNHust commented Mar 13, 2024

@4np, sorry, how can I make corner radius for pie chart, can you give an example?

@4np
Copy link
Contributor

4np commented Mar 13, 2024

@minhDTNHust , you would need create a custom renderer (example) and override:

In the overrides, render a path with rounded corners. You get the pie segment path, it's up to you to transform it into one with rounded corners which shouldn't be too hard (see UIBezierPath).

@engineertoplu
Copy link

not highlight ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants