diff --git a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java index fbc1a35ae0..fa8e220288 100644 --- a/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java +++ b/MPChartExample/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java @@ -32,6 +32,7 @@ import com.github.mikephil.charting.highlight.Highlight; import com.github.mikephil.charting.interfaces.datasets.IDataSet; import com.github.mikephil.charting.listener.OnChartValueSelectedListener; +import com.github.mikephil.charting.renderer.PieChartRenderer; import com.github.mikephil.charting.utils.ColorTemplate; import com.github.mikephil.charting.utils.MPPointF; import com.xxmassdeveloper.mpchartexample.notimportant.DemoBase; @@ -74,13 +75,14 @@ protected void onCreate(Bundle savedInstanceState) { chart.setCenterText(generateCenterSpannableText()); chart.setDrawHoleEnabled(true); - chart.setHoleColor(Color.WHITE); + chart.setHoleColor(Color.TRANSPARENT); - chart.setTransparentCircleColor(Color.WHITE); + chart.setTransparentCircleColor(Color.TRANSPARENT); chart.setTransparentCircleAlpha(110); - chart.setHoleRadius(58f); - chart.setTransparentCircleRadius(61f); + chart.setHoleRadius(50f); + + chart.setTransparentCircleRadius(0f); chart.setDrawCenterText(true); @@ -170,6 +172,10 @@ private void setData(int count, float range) { // undo all highlights chart.highlightValues(null); + PieChartRenderer renderer =(PieChartRenderer) chart.getRenderer(); + renderer.setRoundedCornerRadius(30f); + dataSet.setSliceSpace(renderer.getRoundedCornerRadius()/2); + chart.invalidate(); } 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 b557b333dd..db8d28cfda 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 @@ -1,4 +1,3 @@ - package com.github.mikephil.charting.renderer; import android.graphics.Bitmap; @@ -10,13 +9,11 @@ import android.graphics.Path; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.os.Build; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import com.github.mikephil.charting.animation.ChartAnimator; -import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.charts.PieChart; import com.github.mikephil.charting.data.Entry; import com.github.mikephil.charting.data.PieData; @@ -45,21 +42,26 @@ public class PieChartRenderer extends DataRenderer { protected Paint mTransparentCirclePaint; protected Paint mValueLinePaint; + /** + * paint object used for drawing the rounded corner slice + */ + protected Paint mRoundedCornerPaint; + /** * paint object for the text that can be displayed in the center of the * chart */ - private TextPaint mCenterTextPaint; + private final TextPaint mCenterTextPaint; /** * paint object used for drwing the slice-text */ - private Paint mEntryLabelsPaint; + private final Paint mEntryLabelsPaint; private StaticLayout mCenterTextLayout; private CharSequence mCenterTextLastValue; - private RectF mCenterTextLastBounds = new RectF(); - private RectF[] mRectBuffer = {new RectF(), new RectF(), new RectF()}; + private final RectF mCenterTextLastBounds = new RectF(); + private final RectF[] mRectBuffer = {new RectF(), new RectF(), new RectF()}; /** * Bitmap for drawing the center hole @@ -68,6 +70,24 @@ public class PieChartRenderer extends DataRenderer { protected Canvas mBitmapCanvas; + /** + * Setter for the rounded corner slice paint object + */ + public void setRoundedCornerRadius(float radius){ + mRoundedCornerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRoundedCornerPaint.setStyle(Style.STROKE); + mRoundedCornerPaint.setAntiAlias(true); + mRoundedCornerPaint.setStrokeWidth(radius); + + } + + /** + * Getter for the rounded corner slice paint object + */ + public float getRoundedCornerRadius(){ + return mRoundedCornerPaint.getStrokeWidth(); + } + public PieChartRenderer(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { super(animator, viewPortHandler); @@ -147,12 +167,12 @@ public void drawData(Canvas c) { for (IPieDataSet set : pieData.getDataSets()) { if (set.isVisible() && set.getEntryCount() > 0) - drawDataSet(c, set); + drawDataSet(set); } } - private Path mPathBuffer = new Path(); - private RectF mInnerRectBuffer = new RectF(); + private final Path mPathBuffer = new Path(); + private final RectF mInnerRectBuffer = new RectF(); protected float calculateMinimumRadiusForSpacedSlice( MPPointF center, @@ -187,7 +207,7 @@ protected float calculateMinimumRadiusForSpacedSlice( float spacedRadius = radius - containedTriangleHeight; // And now subtract the height of the arc that's between the triangle and the outer circle - spacedRadius -= Math.sqrt( + spacedRadius -= (float) Math.sqrt( Math.pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.f, 2) + Math.pow(arcMidPointY - (arcEndPointY + arcStartPointY) / 2.f, 2)); @@ -196,9 +216,6 @@ protected float calculateMinimumRadiusForSpacedSlice( /** * Calculates the sliceSpace to use based on visible values and their size compared to the set sliceSpace. - * - * @param dataSet - * @return */ protected float getSliceSpace(IPieDataSet dataSet) { @@ -208,12 +225,10 @@ protected float getSliceSpace(IPieDataSet dataSet) { float spaceSizeRatio = dataSet.getSliceSpace() / mViewPortHandler.getSmallestContentExtension(); float minValueRatio = dataSet.getYMin() / mChart.getData().getYValueSum() * 2; - float sliceSpace = spaceSizeRatio > minValueRatio ? 0f : dataSet.getSliceSpace(); - - return sliceSpace; + return spaceSizeRatio > minValueRatio ? 0f : dataSet.getSliceSpace(); } - protected void drawDataSet(Canvas c, IPieDataSet dataSet) { + protected void drawDataSet(IPieDataSet dataSet) { float angle = 0; float rotationAngle = mChart.getRotationAngle(); @@ -245,6 +260,11 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { final float sliceSpace = visibleAngleCount <= 1 ? 0.f : getSliceSpace(dataSet); + if (getRoundedCornerRadius()>0) { + mRoundedCornerPaint.setStrokeCap(Paint.Cap.ROUND); + mRoundedCornerPaint.setStrokeJoin(Paint.Join.ROUND); + } + for (int j = 0; j < entryCount; j++) { float sliceAngle = drawAngles[j]; @@ -268,6 +288,9 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { mRenderPaint.setColor(dataSet.getColor(j)); + // Set current data set color to paint object + mRoundedCornerPaint.setColor(dataSet.getColor(j)); + final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? 0.f : sliceSpace / (Utils.FDEG2RAD * radius); @@ -398,6 +421,11 @@ protected void drawDataSet(Canvas c, IPieDataSet dataSet) { mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint); + // Draw rounded corner path with paint object slice with the given radius + if (getRoundedCornerRadius()>0) { + mBitmapCanvas.drawPath(mPathBuffer, mRoundedCornerPaint); + } + angle += sliceAngle * phaseX; } @@ -427,7 +455,7 @@ public void drawValues(Canvas c) { 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); + rotationAngle += (float) (roundedRadius * 360 / (Math.PI * 2 * radius)); } } @@ -630,7 +658,6 @@ else if (valueLineColor != ColorTemplate.COLOR_NONE) drawEntryLabel(c, entryLabel, x, y + lineHeight / 2f); } } else if (drawYInside) { - drawValue(c, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j)); } } @@ -664,10 +691,6 @@ else if (valueLineColor != ColorTemplate.COLOR_NONE) /** * Draws an entry label at the specified position. * - * @param c - * @param label - * @param x - * @param y */ protected void drawEntryLabel(Canvas c, String label, float x, float y) { c.drawText(label, x, y, mEntryLabelsPaint); @@ -675,18 +698,18 @@ protected void drawEntryLabel(Canvas c, String label, float x, float y) { @Override public void drawExtras(Canvas c) { - drawHole(c); + drawHole(); c.drawBitmap(mDrawBitmap.get(), 0, 0, null); drawCenterText(c); } - private Path mHoleCirclePath = new Path(); + private final Path mHoleCirclePath = new Path(); /** * draws the hole in the center of the chart and the transparent circle / * hole */ - protected void drawHole(Canvas c) { + protected void drawHole() { if (mChart.isDrawHoleEnabled() && mBitmapCanvas != null) { @@ -780,14 +803,12 @@ protected void drawCenterText(Canvas c) { float layoutHeight = mCenterTextLayout.getHeight(); c.save(); - if (Build.VERSION.SDK_INT >= 18) { - Path path = mDrawCenterTextPathBuffer; - path.reset(); - path.addOval(holeRect, Path.Direction.CW); - c.clipPath(path); - } + Path path = mDrawCenterTextPathBuffer; + path.reset(); + path.addOval(holeRect, Path.Direction.CW); + c.clipPath(path); - c.translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2.f); + c.translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2.f); mCenterTextLayout.draw(c); c.restore(); @@ -828,176 +849,176 @@ public void drawHighlighted(Canvas c, Highlight[] indices) { final RectF highlightedCircleBox = mDrawHighlightedRectF; highlightedCircleBox.set(0, 0, 0, 0); - for (int i = 0; i < indices.length; i++) { - - // get the index to highlight - int index = (int) indices[i].getX(); - - if (index >= drawAngles.length) - continue; - - IPieDataSet set = mChart.getData() - .getDataSetByIndex(indices[i].getDataSetIndex()); - - if (set == null || !set.isHighlightEnabled()) - continue; - - final int entryCount = set.getEntryCount(); - int visibleAngleCount = 0; - for (int j = 0; j < entryCount; j++) { - // draw only if the value is greater than zero - if ((Math.abs(set.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) { - visibleAngleCount++; - } - } - - if (index == 0) - angle = 0.f; - else - angle = absoluteAngles[index - 1] * phaseX; - - final float sliceSpace = visibleAngleCount <= 1 ? 0.f : set.getSliceSpace(); - - float sliceAngle = drawAngles[index]; - float innerRadius = userInnerRadius; - - float shift = set.getSelectionShift(); - final float highlightedRadius = radius + shift; - highlightedCircleBox.set(mChart.getCircleBox()); - highlightedCircleBox.inset(-shift, -shift); - - final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f; - - Integer highlightColor = set.getHighlightColor(); - if (highlightColor == null) - highlightColor = set.getColor(index); - mRenderPaint.setColor(highlightColor); - - final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? - 0.f : - sliceSpace / (Utils.FDEG2RAD * radius); - - final float sliceSpaceAngleShifted = visibleAngleCount == 1 ? - 0.f : - sliceSpace / (Utils.FDEG2RAD * highlightedRadius); - - final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY; - float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY; - if (sweepAngleOuter < 0.f) { - sweepAngleOuter = 0.f; - } - - final float startAngleShifted = rotationAngle + (angle + sliceSpaceAngleShifted / 2.f) * phaseY; - float sweepAngleShifted = (sliceAngle - sliceSpaceAngleShifted) * phaseY; - if (sweepAngleShifted < 0.f) { - sweepAngleShifted = 0.f; - } - - mPathBuffer.reset(); - - if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) { - // Android is doing "mod 360" - mPathBuffer.addCircle(center.x, center.y, highlightedRadius, Path.Direction.CW); - } else { - - mPathBuffer.moveTo( - center.x + highlightedRadius * (float) Math.cos(startAngleShifted * Utils.FDEG2RAD), - center.y + highlightedRadius * (float) Math.sin(startAngleShifted * Utils.FDEG2RAD)); - - mPathBuffer.arcTo( - highlightedCircleBox, - startAngleShifted, - sweepAngleShifted - ); - } - - float sliceSpaceRadius = 0.f; - if (accountForSliceSpacing) { - sliceSpaceRadius = - calculateMinimumRadiusForSpacedSlice( - center, radius, - sliceAngle * phaseY, - center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD), - center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD), - startAngleOuter, - sweepAngleOuter); - } - - // 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 = sliceSpaceRadius; - - if (minSpacedRadius < 0.f) - minSpacedRadius = -minSpacedRadius; - - 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 { - - 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) { - final float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f; - - final float arcEndPointX = center.x + - sliceSpaceRadius * (float) Math.cos(angleMiddle * Utils.FDEG2RAD); - final float arcEndPointY = center.y + - sliceSpaceRadius * (float) Math.sin(angleMiddle * Utils.FDEG2RAD); - - mPathBuffer.lineTo( - arcEndPointX, - arcEndPointY); - - } else { - - mPathBuffer.lineTo( - center.x, - center.y); - } - - } - - } - - mPathBuffer.close(); - - mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint); - } + for (Highlight highlight : indices) { + + // get the index to highlight + int index = (int) highlight.getX(); + + if (index >= drawAngles.length) + continue; + + IPieDataSet set = mChart.getData() + .getDataSetByIndex(highlight.getDataSetIndex()); + + if (set == null || !set.isHighlightEnabled()) + continue; + + final int entryCount = set.getEntryCount(); + int visibleAngleCount = 0; + for (int j = 0; j < entryCount; j++) { + // draw only if the value is greater than zero + if ((Math.abs(set.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) { + visibleAngleCount++; + } + } + + if (index == 0) + angle = 0.f; + else + angle = absoluteAngles[index - 1] * phaseX; + + final float sliceSpace = visibleAngleCount <= 1 ? 0.f : set.getSliceSpace(); + + float sliceAngle = drawAngles[index]; + float innerRadius = userInnerRadius; + + float shift = set.getSelectionShift(); + final float highlightedRadius = radius + shift; + highlightedCircleBox.set(mChart.getCircleBox()); + highlightedCircleBox.inset(-shift, -shift); + + final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f; + + Integer highlightColor = set.getHighlightColor(); + if (highlightColor == null) + highlightColor = set.getColor(index); + mRenderPaint.setColor(highlightColor); + + final float sliceSpaceAngleOuter = visibleAngleCount == 1 ? + 0.f : + sliceSpace / (Utils.FDEG2RAD * radius); + + final float sliceSpaceAngleShifted = visibleAngleCount == 1 ? + 0.f : + sliceSpace / (Utils.FDEG2RAD * highlightedRadius); + + final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY; + float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY; + if (sweepAngleOuter < 0.f) { + sweepAngleOuter = 0.f; + } + + final float startAngleShifted = rotationAngle + (angle + sliceSpaceAngleShifted / 2.f) * phaseY; + float sweepAngleShifted = (sliceAngle - sliceSpaceAngleShifted) * phaseY; + if (sweepAngleShifted < 0.f) { + sweepAngleShifted = 0.f; + } + + mPathBuffer.reset(); + + if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) { + // Android is doing "mod 360" + mPathBuffer.addCircle(center.x, center.y, highlightedRadius, Path.Direction.CW); + } else { + + mPathBuffer.moveTo( + center.x + highlightedRadius * (float) Math.cos(startAngleShifted * Utils.FDEG2RAD), + center.y + highlightedRadius * (float) Math.sin(startAngleShifted * Utils.FDEG2RAD)); + + mPathBuffer.arcTo( + highlightedCircleBox, + startAngleShifted, + sweepAngleShifted + ); + } + + float sliceSpaceRadius = 0.f; + if (accountForSliceSpacing) { + sliceSpaceRadius = + calculateMinimumRadiusForSpacedSlice( + center, radius, + sliceAngle * phaseY, + center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD), + center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD), + startAngleOuter, + sweepAngleOuter); + } + + // 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 = sliceSpaceRadius; + + if (minSpacedRadius < 0.f) + minSpacedRadius = -minSpacedRadius; + + 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 { + + 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) { + final float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f; + + final float arcEndPointX = center.x + + sliceSpaceRadius * (float) Math.cos(angleMiddle * Utils.FDEG2RAD); + final float arcEndPointY = center.y + + sliceSpaceRadius * (float) Math.sin(angleMiddle * Utils.FDEG2RAD); + + mPathBuffer.lineTo( + arcEndPointX, + arcEndPointY); + + } else { + + mPathBuffer.lineTo( + center.x, + center.y); + } + + } + + } + + mPathBuffer.close(); + + mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint); + } MPPointF.recycleInstance(center); } @@ -1005,9 +1026,8 @@ public void drawHighlighted(Canvas c, Highlight[] indices) { /** * This gives all pie-slices a rounded edge. * - * @param c */ - protected void drawRoundedSlices(Canvas c) { + protected void drawRoundedSlices() { if (!mChart.isDrawRoundedSlicesEnabled()) return;