diff options
author | almic <mick.ashton@flare-esports.net> | 2018-11-12 10:58:17 -0700 |
---|---|---|
committer | almic <mick.ashton@flare-esports.net> | 2018-11-12 10:59:48 -0700 |
commit | 42cdba535f5af8076f3a376afba543c7e41eb9d0 (patch) | |
tree | c10fdd05a179f8c4f90153ddd303aaeb4eccc08f /MPChartLib/src | |
parent | 29f4cc5c2c5c7e5468759bb5ad414e9855cd09c6 (diff) | |
download | MPAndroidChart-42cdba535f5af8076f3a376afba543c7e41eb9d0.tar.gz |
Add Curved Slices to Pie Chart
Finally added support for rounded slices, and somewhat improved the code
for the PieChartRenderer class.
Diffstat (limited to 'MPChartLib/src')
-rw-r--r-- | MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java | 10 | ||||
-rw-r--r-- | MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java | 273 |
2 files changed, 164 insertions, 119 deletions
diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java index 660fd29b..0ed45027 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java @@ -651,6 +651,16 @@ public class PieChart extends PieRadarChartBase<PieData> { } /** + * Sets whether to draw slices in a curved fashion, only works if drawing the hole is enabled + * and if the slices are not drawn under the hole. + * + * @param enabled draw curved ends of slices + */ + public void setDrawRoundedSlices(boolean enabled) { + mDrawRoundedSlices = enabled; + } + + /** * Returns true if the chart is set to draw each end of a pie-slice * "rounded". * diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java index b14657ce..c9f653e8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.java @@ -230,6 +230,9 @@ public class PieChartRenderer extends DataRenderer { final float userInnerRadius = drawInnerArc ? radius * (mChart.getHoleRadius() / 100.f) : 0.f; + final float roundedRadius = (radius - (radius * mChart.getHoleRadius() / 100f)) / 2f; + final RectF roundedCircleBox = new RectF(); + final boolean drawRoundedSlices = drawInnerArc && mChart.isDrawRoundedSlicesEnabled(); int visibleAngleCount = 0; for (int j = 0; j < entryCount; j++) { @@ -249,133 +252,151 @@ public class PieChartRenderer extends DataRenderer { Entry e = dataSet.getEntryForIndex(j); // draw only if the value is greater than zero - if ((Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) { + if (!(Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) { + angle += sliceAngle * phaseX; + continue; + } - if (!mChart.needsHighlight(j)) { + // Don't draw if it's highlighted, unless the chart uses rounded slices + if (mChart.needsHighlight(j) && !drawRoundedSlices) { + angle += sliceAngle * phaseX; + continue; + } - final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f; + final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f; - mRenderPaint.setColor(dataSet.getColor(j)); + mRenderPaint.setColor(dataSet.getColor(j)); - final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? - 0.f : - sliceSpace / (Utils.FDEG2RAD * radius); - final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY; - float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY; - if (sweepAngleOuter < 0.f) { - sweepAngleOuter = 0.f; - } + final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? + 0.f : + sliceSpace / (Utils.FDEG2RAD * radius); + final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY; + float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY; + if (sweepAngleOuter < 0.f) { + sweepAngleOuter = 0.f; + } - mPathBuffer.reset(); + mPathBuffer.reset(); - float arcStartPointX = center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD); - float arcStartPointY = center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD); + if (drawRoundedSlices) { + float x = center.x + (radius - roundedRadius) * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD); + float y = center.y + (radius - roundedRadius) * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD); + roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius); + } - if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) { - // Android is doing "mod 360" - mPathBuffer.addCircle(center.x, center.y, radius, Path.Direction.CW); - } else { + float arcStartPointX = center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD); + float arcStartPointY = center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD); - mPathBuffer.moveTo(arcStartPointX, arcStartPointY); + if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) { + // Android is doing "mod 360" + mPathBuffer.addCircle(center.x, center.y, radius, Path.Direction.CW); + } else { - mPathBuffer.arcTo( - circleBox, - startAngleOuter, - sweepAngleOuter - ); - } + if (drawRoundedSlices) { + mPathBuffer.arcTo(roundedCircleBox, startAngleOuter + 180, -180); + } - // API < 21 does not receive floats in addArc, but a RectF - mInnerRectBuffer.set( - center.x - innerRadius, - center.y - innerRadius, - center.x + innerRadius, - center.y + innerRadius); - - if (drawInnerArc && - (innerRadius > 0.f || accountForSliceSpacing)) { - - if (accountForSliceSpacing) { - float minSpacedRadius = - calculateMinimumRadiusForSpacedSlice( - center, radius, - sliceAngle * phaseY, - arcStartPointX, arcStartPointY, - startAngleOuter, - sweepAngleOuter); - - if (minSpacedRadius < 0.f) - minSpacedRadius = -minSpacedRadius; - - innerRadius = Math.max(innerRadius, minSpacedRadius); - } + mPathBuffer.arcTo( + circleBox, + startAngleOuter, + sweepAngleOuter + ); + } - final float sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.f ? - 0.f : - sliceSpace / (Utils.FDEG2RAD * innerRadius); - final float startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.f) * phaseY; - float sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY; - if (sweepAngleInner < 0.f) { - sweepAngleInner = 0.f; - } - final float endAngleInner = startAngleInner + sweepAngleInner; - - if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) { - // Android is doing "mod 360" - mPathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW); - } else { - - mPathBuffer.lineTo( - center.x + innerRadius * (float) Math.cos(endAngleInner * Utils.FDEG2RAD), - center.y + innerRadius * (float) Math.sin(endAngleInner * Utils.FDEG2RAD)); - - mPathBuffer.arcTo( - mInnerRectBuffer, - endAngleInner, - -sweepAngleInner - ); - } - } else { + // API < 21 does not receive floats in addArc, but a RectF + mInnerRectBuffer.set( + center.x - innerRadius, + center.y - innerRadius, + center.x + innerRadius, + center.y + innerRadius); - if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) { - if (accountForSliceSpacing) { - - float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f; - - float sliceSpaceOffset = - calculateMinimumRadiusForSpacedSlice( - center, - radius, - sliceAngle * phaseY, - arcStartPointX, - arcStartPointY, - startAngleOuter, - sweepAngleOuter); - - float arcEndPointX = center.x + - sliceSpaceOffset * (float) Math.cos(angleMiddle * Utils.FDEG2RAD); - float arcEndPointY = center.y + - sliceSpaceOffset * (float) Math.sin(angleMiddle * Utils.FDEG2RAD); - - mPathBuffer.lineTo( - arcEndPointX, - arcEndPointY); - - } else { - mPathBuffer.lineTo( - center.x, - center.y); - } - } + if (drawInnerArc && (innerRadius > 0.f || accountForSliceSpacing)) { - } + if (accountForSliceSpacing) { + float minSpacedRadius = + calculateMinimumRadiusForSpacedSlice( + center, radius, + sliceAngle * phaseY, + arcStartPointX, arcStartPointY, + startAngleOuter, + sweepAngleOuter); - mPathBuffer.close(); + if (minSpacedRadius < 0.f) + minSpacedRadius = -minSpacedRadius; - mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint); + innerRadius = Math.max(innerRadius, minSpacedRadius); } + + final float sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.f ? + 0.f : + sliceSpace / (Utils.FDEG2RAD * innerRadius); + final float startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.f) * phaseY; + float sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY; + if (sweepAngleInner < 0.f) { + sweepAngleInner = 0.f; + } + final float endAngleInner = startAngleInner + sweepAngleInner; + + if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) { + // Android is doing "mod 360" + mPathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW); + } else { + + if (drawRoundedSlices) { + float x = center.x + (radius - roundedRadius) * (float) Math.cos(endAngleInner * Utils.FDEG2RAD); + float y = center.y + (radius - roundedRadius) * (float) Math.sin(endAngleInner * Utils.FDEG2RAD); + roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius); + mPathBuffer.arcTo(roundedCircleBox, endAngleInner, 180); + } else + mPathBuffer.lineTo( + center.x + innerRadius * (float) Math.cos(endAngleInner * Utils.FDEG2RAD), + center.y + innerRadius * (float) Math.sin(endAngleInner * Utils.FDEG2RAD)); + + mPathBuffer.arcTo( + mInnerRectBuffer, + endAngleInner, + -sweepAngleInner + ); + } + } else { + + if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) { + if (accountForSliceSpacing) { + + float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f; + + float sliceSpaceOffset = + calculateMinimumRadiusForSpacedSlice( + center, + radius, + sliceAngle * phaseY, + arcStartPointX, + arcStartPointY, + startAngleOuter, + sweepAngleOuter); + + float arcEndPointX = center.x + + sliceSpaceOffset * (float) Math.cos(angleMiddle * Utils.FDEG2RAD); + float arcEndPointY = center.y + + sliceSpaceOffset * (float) Math.sin(angleMiddle * Utils.FDEG2RAD); + + mPathBuffer.lineTo( + arcEndPointX, + arcEndPointY); + + } else { + mPathBuffer.lineTo( + center.x, + center.y); + } + } + } + mPathBuffer.close(); + + mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint); + angle += sliceAngle * phaseX; } @@ -396,11 +417,17 @@ public class PieChartRenderer extends DataRenderer { float phaseX = mAnimator.getPhaseX(); float phaseY = mAnimator.getPhaseY(); + final float roundedRadius = (radius - (radius * mChart.getHoleRadius() / 100f)) / 2f; final float holeRadiusPercent = mChart.getHoleRadius() / 100.f; float labelRadiusOffset = radius / 10f * 3.6f; if (mChart.isDrawHoleEnabled()) { labelRadiusOffset = (radius - (radius * holeRadiusPercent)) / 2f; + + if (!mChart.isDrawSlicesUnderHoleEnabled() && mChart.isDrawRoundedSlicesEnabled()) { + // Add curved circle slice and spacing to rotation angle, so that it sits nicely inside + rotationAngle += roundedRadius * 360 / (Math.PI * 2 * radius); + } } final float labelRadius = radius - labelRadiusOffset; @@ -472,6 +499,7 @@ public class PieChartRenderer extends DataRenderer { float value = mChart.isUsePercentValuesEnabled() ? entry.getY() / yValueSum * 100f : entry.getY(); String formattedValue = formatter.getPieLabel(value, entry); + String entryLabel = entry.getLabel(); final float sliceXBase = (float) Math.cos(transformedAngle * Utils.FDEG2RAD); final float sliceYBase = (float) Math.sin(transformedAngle * Utils.FDEG2RAD); @@ -552,13 +580,13 @@ public class PieChartRenderer extends DataRenderer { drawValue(c, formattedValue, labelPtx, labelPty, dataSet.getValueTextColor(j)); - if (j < data.getEntryCount() && entry.getLabel() != null) { - drawEntryLabel(c, entry.getLabel(), labelPtx, labelPty + lineHeight); + if (j < data.getEntryCount() && entryLabel != null) { + drawEntryLabel(c, entryLabel, labelPtx, labelPty + lineHeight); } } else if (drawXOutside) { - if (j < data.getEntryCount() && entry.getLabel() != null) { - drawEntryLabel(c, entry.getLabel(), labelPtx, labelPty + lineHeight / 2.f); + if (j < data.getEntryCount() && entryLabel != null) { + drawEntryLabel(c, entryLabel, labelPtx, labelPty + lineHeight / 2.f); } } else if (drawYOutside) { @@ -578,13 +606,13 @@ public class PieChartRenderer extends DataRenderer { drawValue(c, formattedValue, x, y, dataSet.getValueTextColor(j)); - if (j < data.getEntryCount() && entry.getLabel() != null) { - drawEntryLabel(c, entry.getLabel(), x, y + lineHeight); + if (j < data.getEntryCount() && entryLabel != null) { + drawEntryLabel(c, entryLabel, x, y + lineHeight); } } else if (drawXInside) { - if (j < data.getEntryCount() && entry.getLabel() != null) { - drawEntryLabel(c, entry.getLabel(), x, y + lineHeight / 2f); + if (j < data.getEntryCount() && entryLabel != null) { + drawEntryLabel(c, entryLabel, x, y + lineHeight / 2f); } } else if (drawYInside) { drawValue(c, formattedValue, x, y + lineHeight / 2f, dataSet.getValueTextColor(j)); @@ -637,7 +665,6 @@ public class PieChartRenderer extends DataRenderer { @Override public void drawExtras(Canvas c) { - // drawCircles(c); drawHole(c); c.drawBitmap(mDrawBitmap.get(), 0, 0, null); drawCenterText(c); @@ -763,6 +790,15 @@ public class PieChartRenderer extends DataRenderer { @Override public void drawHighlighted(Canvas c, Highlight[] indices) { + /* Skip entirely if using rounded circle slices, because it doesn't make sense to highlight + * in this way. + * TODO: add support for changing slice color with highlighting rather than only shifting the slice + */ + + final boolean drawInnerArc = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled(); + if (drawInnerArc && mChart.isDrawRoundedSlicesEnabled()) + return; + float phaseX = mAnimator.getPhaseX(); float phaseY = mAnimator.getPhaseY(); @@ -773,7 +809,6 @@ public class PieChartRenderer extends DataRenderer { float[] absoluteAngles = mChart.getAbsoluteAngles(); final MPPointF center = mChart.getCenterCircleBox(); final float radius = mChart.getRadius(); - final boolean drawInnerArc = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled(); final float userInnerRadius = drawInnerArc ? radius * (mChart.getHoleRadius() / 100.f) : 0.f; |