• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 
2 package com.github.mikephil.charting.renderer;
3 
4 import android.graphics.Bitmap;
5 import android.graphics.Canvas;
6 import android.graphics.Color;
7 import android.graphics.Paint;
8 import android.graphics.Paint.Align;
9 import android.graphics.Paint.Style;
10 import android.graphics.Path;
11 import android.graphics.RectF;
12 import android.graphics.drawable.Drawable;
13 import android.os.Build;
14 import android.text.Layout;
15 import android.text.StaticLayout;
16 import android.text.TextPaint;
17 
18 import com.github.mikephil.charting.animation.ChartAnimator;
19 import com.github.mikephil.charting.charts.LineChart;
20 import com.github.mikephil.charting.charts.PieChart;
21 import com.github.mikephil.charting.data.Entry;
22 import com.github.mikephil.charting.data.PieData;
23 import com.github.mikephil.charting.data.PieDataSet;
24 import com.github.mikephil.charting.data.PieEntry;
25 import com.github.mikephil.charting.formatter.IValueFormatter;
26 import com.github.mikephil.charting.highlight.Highlight;
27 import com.github.mikephil.charting.interfaces.datasets.IPieDataSet;
28 import com.github.mikephil.charting.utils.ColorTemplate;
29 import com.github.mikephil.charting.utils.MPPointF;
30 import com.github.mikephil.charting.utils.Utils;
31 import com.github.mikephil.charting.utils.ViewPortHandler;
32 
33 import java.lang.ref.WeakReference;
34 import java.util.List;
35 
36 public class PieChartRenderer extends DataRenderer {
37 
38     protected PieChart mChart;
39 
40     /**
41      * paint for the hole in the center of the pie chart and the transparent
42      * circle
43      */
44     protected Paint mHolePaint;
45     protected Paint mTransparentCirclePaint;
46     protected Paint mValueLinePaint;
47 
48     /**
49      * paint object for the text that can be displayed in the center of the
50      * chart
51      */
52     private TextPaint mCenterTextPaint;
53 
54     /**
55      * paint object used for drwing the slice-text
56      */
57     private Paint mEntryLabelsPaint;
58 
59     private StaticLayout mCenterTextLayout;
60     private CharSequence mCenterTextLastValue;
61     private RectF mCenterTextLastBounds = new RectF();
62     private RectF[] mRectBuffer = {new RectF(), new RectF(), new RectF()};
63 
64     /**
65      * Bitmap for drawing the center hole
66      */
67     protected WeakReference<Bitmap> mDrawBitmap;
68 
69     protected Canvas mBitmapCanvas;
70 
PieChartRenderer(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler)71     public PieChartRenderer(PieChart chart, ChartAnimator animator,
72                             ViewPortHandler viewPortHandler) {
73         super(animator, viewPortHandler);
74         mChart = chart;
75 
76         mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
77         mHolePaint.setColor(Color.WHITE);
78         mHolePaint.setStyle(Style.FILL);
79 
80         mTransparentCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
81         mTransparentCirclePaint.setColor(Color.WHITE);
82         mTransparentCirclePaint.setStyle(Style.FILL);
83         mTransparentCirclePaint.setAlpha(105);
84 
85         mCenterTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
86         mCenterTextPaint.setColor(Color.BLACK);
87         mCenterTextPaint.setTextSize(Utils.convertDpToPixel(12f));
88 
89         mValuePaint.setTextSize(Utils.convertDpToPixel(13f));
90         mValuePaint.setColor(Color.WHITE);
91         mValuePaint.setTextAlign(Align.CENTER);
92 
93         mEntryLabelsPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
94         mEntryLabelsPaint.setColor(Color.WHITE);
95         mEntryLabelsPaint.setTextAlign(Align.CENTER);
96         mEntryLabelsPaint.setTextSize(Utils.convertDpToPixel(13f));
97 
98         mValueLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
99         mValueLinePaint.setStyle(Style.STROKE);
100     }
101 
getPaintHole()102     public Paint getPaintHole() {
103         return mHolePaint;
104     }
105 
getPaintTransparentCircle()106     public Paint getPaintTransparentCircle() {
107         return mTransparentCirclePaint;
108     }
109 
getPaintCenterText()110     public TextPaint getPaintCenterText() {
111         return mCenterTextPaint;
112     }
113 
getPaintEntryLabels()114     public Paint getPaintEntryLabels() {
115         return mEntryLabelsPaint;
116     }
117 
118     @Override
initBuffers()119     public void initBuffers() {
120         // TODO Auto-generated method stub
121     }
122 
123     @Override
drawData(Canvas c)124     public void drawData(Canvas c) {
125 
126         int width = (int) mViewPortHandler.getChartWidth();
127         int height = (int) mViewPortHandler.getChartHeight();
128 
129         Bitmap drawBitmap = mDrawBitmap == null ? null : mDrawBitmap.get();
130 
131         if (drawBitmap == null
132                 || (drawBitmap.getWidth() != width)
133                 || (drawBitmap.getHeight() != height)) {
134 
135             if (width > 0 && height > 0) {
136                 drawBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
137                 mDrawBitmap = new WeakReference<>(drawBitmap);
138                 mBitmapCanvas = new Canvas(drawBitmap);
139             } else
140                 return;
141         }
142 
143         drawBitmap.eraseColor(Color.TRANSPARENT);
144 
145         PieData pieData = mChart.getData();
146 
147         for (IPieDataSet set : pieData.getDataSets()) {
148 
149             if (set.isVisible() && set.getEntryCount() > 0)
150                 drawDataSet(c, set);
151         }
152     }
153 
154     private Path mPathBuffer = new Path();
155     private RectF mInnerRectBuffer = new RectF();
156 
calculateMinimumRadiusForSpacedSlice( MPPointF center, float radius, float angle, float arcStartPointX, float arcStartPointY, float startAngle, float sweepAngle)157     protected float calculateMinimumRadiusForSpacedSlice(
158             MPPointF center,
159             float radius,
160             float angle,
161             float arcStartPointX,
162             float arcStartPointY,
163             float startAngle,
164             float sweepAngle) {
165         final float angleMiddle = startAngle + sweepAngle / 2.f;
166 
167         // Other point of the arc
168         float arcEndPointX = center.x + radius * (float) Math.cos((startAngle + sweepAngle) * Utils.FDEG2RAD);
169         float arcEndPointY = center.y + radius * (float) Math.sin((startAngle + sweepAngle) * Utils.FDEG2RAD);
170 
171         // Middle point on the arc
172         float arcMidPointX = center.x + radius * (float) Math.cos(angleMiddle * Utils.FDEG2RAD);
173         float arcMidPointY = center.y + radius * (float) Math.sin(angleMiddle * Utils.FDEG2RAD);
174 
175         // This is the base of the contained triangle
176         double basePointsDistance = Math.sqrt(
177                 Math.pow(arcEndPointX - arcStartPointX, 2) +
178                         Math.pow(arcEndPointY - arcStartPointY, 2));
179 
180         // After reducing space from both sides of the "slice",
181         //   the angle of the contained triangle should stay the same.
182         // So let's find out the height of that triangle.
183         float containedTriangleHeight = (float) (basePointsDistance / 2.0 *
184                 Math.tan((180.0 - angle) / 2.0 * Utils.DEG2RAD));
185 
186         // Now we subtract that from the radius
187         float spacedRadius = radius - containedTriangleHeight;
188 
189         // And now subtract the height of the arc that's between the triangle and the outer circle
190         spacedRadius -= Math.sqrt(
191                 Math.pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.f, 2) +
192                         Math.pow(arcMidPointY - (arcEndPointY + arcStartPointY) / 2.f, 2));
193 
194         return spacedRadius;
195     }
196 
197     /**
198      * Calculates the sliceSpace to use based on visible values and their size compared to the set sliceSpace.
199      *
200      * @param dataSet
201      * @return
202      */
getSliceSpace(IPieDataSet dataSet)203     protected float getSliceSpace(IPieDataSet dataSet) {
204 
205         if (!dataSet.isAutomaticallyDisableSliceSpacingEnabled())
206             return dataSet.getSliceSpace();
207 
208         float spaceSizeRatio = dataSet.getSliceSpace() / mViewPortHandler.getSmallestContentExtension();
209         float minValueRatio = dataSet.getYMin() / mChart.getData().getYValueSum() * 2;
210 
211         float sliceSpace = spaceSizeRatio > minValueRatio ? 0f : dataSet.getSliceSpace();
212 
213         return sliceSpace;
214     }
215 
drawDataSet(Canvas c, IPieDataSet dataSet)216     protected void drawDataSet(Canvas c, IPieDataSet dataSet) {
217 
218         float angle = 0;
219         float rotationAngle = mChart.getRotationAngle();
220 
221         float phaseX = mAnimator.getPhaseX();
222         float phaseY = mAnimator.getPhaseY();
223 
224         final RectF circleBox = mChart.getCircleBox();
225 
226         final int entryCount = dataSet.getEntryCount();
227         final float[] drawAngles = mChart.getDrawAngles();
228         final MPPointF center = mChart.getCenterCircleBox();
229         final float radius = mChart.getRadius();
230         final boolean drawInnerArc = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled();
231         final float userInnerRadius = drawInnerArc
232                 ? radius * (mChart.getHoleRadius() / 100.f)
233                 : 0.f;
234         final float roundedRadius = (radius - (radius * mChart.getHoleRadius() / 100f)) / 2f;
235         final RectF roundedCircleBox = new RectF();
236         final boolean drawRoundedSlices = drawInnerArc && mChart.isDrawRoundedSlicesEnabled();
237 
238         int visibleAngleCount = 0;
239         for (int j = 0; j < entryCount; j++) {
240             // draw only if the value is greater than zero
241             if ((Math.abs(dataSet.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) {
242                 visibleAngleCount++;
243             }
244         }
245 
246         final float sliceSpace = visibleAngleCount <= 1 ? 0.f : getSliceSpace(dataSet);
247 
248         for (int j = 0; j < entryCount; j++) {
249 
250             float sliceAngle = drawAngles[j];
251             float innerRadius = userInnerRadius;
252 
253             Entry e = dataSet.getEntryForIndex(j);
254 
255             // draw only if the value is greater than zero
256             if (!(Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
257                 angle += sliceAngle * phaseX;
258                 continue;
259             }
260 
261             // Don't draw if it's highlighted, unless the chart uses rounded slices
262             if (dataSet.isHighlightEnabled() && mChart.needsHighlight(j) && !drawRoundedSlices) {
263                 angle += sliceAngle * phaseX;
264                 continue;
265             }
266 
267             final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f;
268 
269             mRenderPaint.setColor(dataSet.getColor(j));
270 
271             final float sliceSpaceAngleOuter = visibleAngleCount == 1 ?
272                     0.f :
273                     sliceSpace / (Utils.FDEG2RAD * radius);
274             final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY;
275             float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY;
276             if (sweepAngleOuter < 0.f) {
277                 sweepAngleOuter = 0.f;
278             }
279 
280             mPathBuffer.reset();
281 
282             if (drawRoundedSlices) {
283                 float x = center.x + (radius - roundedRadius) * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD);
284                 float y = center.y + (radius - roundedRadius) * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD);
285                 roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius);
286             }
287 
288             float arcStartPointX = center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD);
289             float arcStartPointY = center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD);
290 
291             if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
292                 // Android is doing "mod 360"
293                 mPathBuffer.addCircle(center.x, center.y, radius, Path.Direction.CW);
294             } else {
295 
296                 if (drawRoundedSlices) {
297                     mPathBuffer.arcTo(roundedCircleBox, startAngleOuter + 180, -180);
298                 }
299 
300                 mPathBuffer.arcTo(
301                         circleBox,
302                         startAngleOuter,
303                         sweepAngleOuter
304                 );
305             }
306 
307             // API < 21 does not receive floats in addArc, but a RectF
308             mInnerRectBuffer.set(
309                     center.x - innerRadius,
310                     center.y - innerRadius,
311                     center.x + innerRadius,
312                     center.y + innerRadius);
313 
314             if (drawInnerArc && (innerRadius > 0.f || accountForSliceSpacing)) {
315 
316                 if (accountForSliceSpacing) {
317                     float minSpacedRadius =
318                             calculateMinimumRadiusForSpacedSlice(
319                                     center, radius,
320                                     sliceAngle * phaseY,
321                                     arcStartPointX, arcStartPointY,
322                                     startAngleOuter,
323                                     sweepAngleOuter);
324 
325                     if (minSpacedRadius < 0.f)
326                         minSpacedRadius = -minSpacedRadius;
327 
328                     innerRadius = Math.max(innerRadius, minSpacedRadius);
329                 }
330 
331                 final float sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.f ?
332                         0.f :
333                         sliceSpace / (Utils.FDEG2RAD * innerRadius);
334                 final float startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.f) * phaseY;
335                 float sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY;
336                 if (sweepAngleInner < 0.f) {
337                     sweepAngleInner = 0.f;
338                 }
339                 final float endAngleInner = startAngleInner + sweepAngleInner;
340 
341                 if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
342                     // Android is doing "mod 360"
343                     mPathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW);
344                 } else {
345 
346                     if (drawRoundedSlices) {
347                         float x = center.x + (radius - roundedRadius) * (float) Math.cos(endAngleInner * Utils.FDEG2RAD);
348                         float y = center.y + (radius - roundedRadius) * (float) Math.sin(endAngleInner * Utils.FDEG2RAD);
349                         roundedCircleBox.set(x - roundedRadius, y - roundedRadius, x + roundedRadius, y + roundedRadius);
350                         mPathBuffer.arcTo(roundedCircleBox, endAngleInner, 180);
351                     } else
352                         mPathBuffer.lineTo(
353                                 center.x + innerRadius * (float) Math.cos(endAngleInner * Utils.FDEG2RAD),
354                                 center.y + innerRadius * (float) Math.sin(endAngleInner * Utils.FDEG2RAD));
355 
356                     mPathBuffer.arcTo(
357                             mInnerRectBuffer,
358                             endAngleInner,
359                             -sweepAngleInner
360                     );
361                 }
362             } else {
363 
364                 if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) {
365                     if (accountForSliceSpacing) {
366 
367                         float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f;
368 
369                         float sliceSpaceOffset =
370                                 calculateMinimumRadiusForSpacedSlice(
371                                         center,
372                                         radius,
373                                         sliceAngle * phaseY,
374                                         arcStartPointX,
375                                         arcStartPointY,
376                                         startAngleOuter,
377                                         sweepAngleOuter);
378 
379                         float arcEndPointX = center.x +
380                                 sliceSpaceOffset * (float) Math.cos(angleMiddle * Utils.FDEG2RAD);
381                         float arcEndPointY = center.y +
382                                 sliceSpaceOffset * (float) Math.sin(angleMiddle * Utils.FDEG2RAD);
383 
384                         mPathBuffer.lineTo(
385                                 arcEndPointX,
386                                 arcEndPointY);
387 
388                     } else {
389                         mPathBuffer.lineTo(
390                                 center.x,
391                                 center.y);
392                     }
393                 }
394 
395             }
396 
397             mPathBuffer.close();
398 
399             mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint);
400 
401             angle += sliceAngle * phaseX;
402         }
403 
404         MPPointF.recycleInstance(center);
405     }
406 
407     @Override
drawValues(Canvas c)408     public void drawValues(Canvas c) {
409 
410         MPPointF center = mChart.getCenterCircleBox();
411 
412         // get whole the radius
413         float radius = mChart.getRadius();
414         float rotationAngle = mChart.getRotationAngle();
415         float[] drawAngles = mChart.getDrawAngles();
416         float[] absoluteAngles = mChart.getAbsoluteAngles();
417 
418         float phaseX = mAnimator.getPhaseX();
419         float phaseY = mAnimator.getPhaseY();
420 
421         final float roundedRadius = (radius - (radius * mChart.getHoleRadius() / 100f)) / 2f;
422         final float holeRadiusPercent = mChart.getHoleRadius() / 100.f;
423         float labelRadiusOffset = radius / 10f * 3.6f;
424 
425         if (mChart.isDrawHoleEnabled()) {
426             labelRadiusOffset = (radius - (radius * holeRadiusPercent)) / 2f;
427 
428             if (!mChart.isDrawSlicesUnderHoleEnabled() && mChart.isDrawRoundedSlicesEnabled()) {
429                 // Add curved circle slice and spacing to rotation angle, so that it sits nicely inside
430                 rotationAngle += roundedRadius * 360 / (Math.PI * 2 * radius);
431             }
432         }
433 
434         final float labelRadius = radius - labelRadiusOffset;
435 
436         PieData data = mChart.getData();
437         List<IPieDataSet> dataSets = data.getDataSets();
438 
439         float yValueSum = data.getYValueSum();
440 
441         boolean drawEntryLabels = mChart.isDrawEntryLabelsEnabled();
442 
443         float angle;
444         int xIndex = 0;
445 
446         c.save();
447 
448         float offset = Utils.convertDpToPixel(5.f);
449 
450         for (int i = 0; i < dataSets.size(); i++) {
451 
452             IPieDataSet dataSet = dataSets.get(i);
453 
454             final boolean drawValues = dataSet.isDrawValuesEnabled();
455 
456             if (!drawValues && !drawEntryLabels)
457                 continue;
458 
459             final PieDataSet.ValuePosition xValuePosition = dataSet.getXValuePosition();
460             final PieDataSet.ValuePosition yValuePosition = dataSet.getYValuePosition();
461 
462             // apply the text-styling defined by the DataSet
463             applyValueTextStyle(dataSet);
464 
465             float lineHeight = Utils.calcTextHeight(mValuePaint, "Q")
466                     + Utils.convertDpToPixel(4f);
467 
468             IValueFormatter formatter = dataSet.getValueFormatter();
469 
470             int entryCount = dataSet.getEntryCount();
471 
472             boolean isUseValueColorForLineEnabled = dataSet.isUseValueColorForLineEnabled();
473             int valueLineColor = dataSet.getValueLineColor();
474 
475             mValueLinePaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getValueLineWidth()));
476 
477             final float sliceSpace = getSliceSpace(dataSet);
478 
479             MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
480             iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
481             iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
482 
483             for (int j = 0; j < entryCount; j++) {
484 
485                 PieEntry entry = dataSet.getEntryForIndex(j);
486 
487                 if (xIndex == 0)
488                     angle = 0.f;
489                 else
490                     angle = absoluteAngles[xIndex - 1] * phaseX;
491 
492                 final float sliceAngle = drawAngles[xIndex];
493                 final float sliceSpaceMiddleAngle = sliceSpace / (Utils.FDEG2RAD * labelRadius);
494 
495                 // offset needed to center the drawn text in the slice
496                 final float angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2.f) / 2.f;
497 
498                 angle = angle + angleOffset;
499 
500                 final float transformedAngle = rotationAngle + angle * phaseY;
501 
502                 float value = mChart.isUsePercentValuesEnabled() ? entry.getY()
503                         / yValueSum * 100f : entry.getY();
504                 String entryLabel = entry.getLabel();
505 
506                 final float sliceXBase = (float) Math.cos(transformedAngle * Utils.FDEG2RAD);
507                 final float sliceYBase = (float) Math.sin(transformedAngle * Utils.FDEG2RAD);
508 
509                 final boolean drawXOutside = drawEntryLabels &&
510                         xValuePosition == PieDataSet.ValuePosition.OUTSIDE_SLICE;
511                 final boolean drawYOutside = drawValues &&
512                         yValuePosition == PieDataSet.ValuePosition.OUTSIDE_SLICE;
513                 final boolean drawXInside = drawEntryLabels &&
514                         xValuePosition == PieDataSet.ValuePosition.INSIDE_SLICE;
515                 final boolean drawYInside = drawValues &&
516                         yValuePosition == PieDataSet.ValuePosition.INSIDE_SLICE;
517 
518                 if (drawXOutside || drawYOutside) {
519 
520                     final float valueLineLength1 = dataSet.getValueLinePart1Length();
521                     final float valueLineLength2 = dataSet.getValueLinePart2Length();
522                     final float valueLinePart1OffsetPercentage = dataSet.getValueLinePart1OffsetPercentage() / 100.f;
523 
524                     float pt2x, pt2y;
525                     float labelPtx, labelPty;
526 
527                     float line1Radius;
528 
529                     if (mChart.isDrawHoleEnabled())
530                         line1Radius = (radius - (radius * holeRadiusPercent))
531                                 * valueLinePart1OffsetPercentage
532                                 + (radius * holeRadiusPercent);
533                     else
534                         line1Radius = radius * valueLinePart1OffsetPercentage;
535 
536                     final float polyline2Width = dataSet.isValueLineVariableLength()
537                             ? labelRadius * valueLineLength2 * (float) Math.abs(Math.sin(
538                             transformedAngle * Utils.FDEG2RAD))
539                             : labelRadius * valueLineLength2;
540 
541                     final float pt0x = line1Radius * sliceXBase + center.x;
542                     final float pt0y = line1Radius * sliceYBase + center.y;
543 
544                     final float pt1x = labelRadius * (1 + valueLineLength1) * sliceXBase + center.x;
545                     final float pt1y = labelRadius * (1 + valueLineLength1) * sliceYBase + center.y;
546 
547                     if (transformedAngle % 360.0 >= 90.0 && transformedAngle % 360.0 <= 270.0) {
548                         pt2x = pt1x - polyline2Width;
549                         pt2y = pt1y;
550 
551                         mValuePaint.setTextAlign(Align.RIGHT);
552 
553                         if(drawXOutside)
554                             mEntryLabelsPaint.setTextAlign(Align.RIGHT);
555 
556                         labelPtx = pt2x - offset;
557                         labelPty = pt2y;
558                     } else {
559                         pt2x = pt1x + polyline2Width;
560                         pt2y = pt1y;
561                         mValuePaint.setTextAlign(Align.LEFT);
562 
563                         if(drawXOutside)
564                             mEntryLabelsPaint.setTextAlign(Align.LEFT);
565 
566                         labelPtx = pt2x + offset;
567                         labelPty = pt2y;
568                     }
569 
570                     int lineColor = ColorTemplate.COLOR_NONE;
571 
572                     if (isUseValueColorForLineEnabled)
573                         lineColor = dataSet.getColor(j);
574                     else if (valueLineColor != ColorTemplate.COLOR_NONE)
575                         lineColor = valueLineColor;
576 
577                     if (lineColor != ColorTemplate.COLOR_NONE) {
578                         mValueLinePaint.setColor(lineColor);
579                         c.drawLine(pt0x, pt0y, pt1x, pt1y, mValueLinePaint);
580                         c.drawLine(pt1x, pt1y, pt2x, pt2y, mValueLinePaint);
581                     }
582 
583                     // draw everything, depending on settings
584                     if (drawXOutside && drawYOutside) {
585 
586                         drawValue(c,
587                                 formatter,
588                                 value,
589                                 entry,
590                                 0,
591                                 labelPtx,
592                                 labelPty,
593                                 dataSet.getValueTextColor(j));
594 
595                         if (j < data.getEntryCount() && entryLabel != null) {
596                             drawEntryLabel(c, entryLabel, labelPtx, labelPty + lineHeight);
597                         }
598 
599                     } else if (drawXOutside) {
600                         if (j < data.getEntryCount() && entryLabel != null) {
601                             drawEntryLabel(c, entryLabel, labelPtx, labelPty + lineHeight / 2.f);
602                         }
603                     } else if (drawYOutside) {
604 
605                         drawValue(c, formatter, value, entry, 0, labelPtx, labelPty + lineHeight / 2.f, dataSet
606                                 .getValueTextColor(j));
607                     }
608                 }
609 
610                 if (drawXInside || drawYInside) {
611                     // calculate the text position
612                     float x = labelRadius * sliceXBase + center.x;
613                     float y = labelRadius * sliceYBase + center.y;
614 
615                     mValuePaint.setTextAlign(Align.CENTER);
616 
617                     // draw everything, depending on settings
618                     if (drawXInside && drawYInside) {
619 
620                         drawValue(c, formatter, value, entry, 0, x, y, dataSet.getValueTextColor(j));
621 
622                         if (j < data.getEntryCount() && entryLabel != null) {
623                             drawEntryLabel(c, entryLabel, x, y + lineHeight);
624                         }
625 
626                     } else if (drawXInside) {
627                         if (j < data.getEntryCount() && entryLabel != null) {
628                             drawEntryLabel(c, entryLabel, x, y + lineHeight / 2f);
629                         }
630                     } else if (drawYInside) {
631 
632                         drawValue(c, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j));
633                     }
634                 }
635 
636                 if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
637 
638                     Drawable icon = entry.getIcon();
639 
640                     float x = (labelRadius + iconsOffset.y) * sliceXBase + center.x;
641                     float y = (labelRadius + iconsOffset.y) * sliceYBase + center.y;
642                     y += iconsOffset.x;
643 
644                     Utils.drawImage(
645                             c,
646                             icon,
647                             (int)x,
648                             (int)y,
649                             icon.getIntrinsicWidth(),
650                             icon.getIntrinsicHeight());
651                 }
652 
653                 xIndex++;
654             }
655 
656             MPPointF.recycleInstance(iconsOffset);
657         }
658         MPPointF.recycleInstance(center);
659         c.restore();
660     }
661 
662     /**
663      * Draws an entry label at the specified position.
664      *
665      * @param c
666      * @param label
667      * @param x
668      * @param y
669      */
drawEntryLabel(Canvas c, String label, float x, float y)670     protected void drawEntryLabel(Canvas c, String label, float x, float y) {
671         c.drawText(label, x, y, mEntryLabelsPaint);
672     }
673 
674     @Override
drawExtras(Canvas c)675     public void drawExtras(Canvas c) {
676         drawHole(c);
677         c.drawBitmap(mDrawBitmap.get(), 0, 0, null);
678         drawCenterText(c);
679     }
680 
681     private Path mHoleCirclePath = new Path();
682 
683     /**
684      * draws the hole in the center of the chart and the transparent circle /
685      * hole
686      */
drawHole(Canvas c)687     protected void drawHole(Canvas c) {
688 
689         if (mChart.isDrawHoleEnabled() && mBitmapCanvas != null) {
690 
691             float radius = mChart.getRadius();
692             float holeRadius = radius * (mChart.getHoleRadius() / 100);
693             MPPointF center = mChart.getCenterCircleBox();
694 
695             if (Color.alpha(mHolePaint.getColor()) > 0) {
696                 // draw the hole-circle
697                 mBitmapCanvas.drawCircle(
698                         center.x, center.y,
699                         holeRadius, mHolePaint);
700             }
701 
702             // only draw the circle if it can be seen (not covered by the hole)
703             if (Color.alpha(mTransparentCirclePaint.getColor()) > 0 &&
704                     mChart.getTransparentCircleRadius() > mChart.getHoleRadius()) {
705 
706                 int alpha = mTransparentCirclePaint.getAlpha();
707                 float secondHoleRadius = radius * (mChart.getTransparentCircleRadius() / 100);
708 
709                 mTransparentCirclePaint.setAlpha((int) ((float) alpha * mAnimator.getPhaseX() * mAnimator.getPhaseY()));
710 
711                 // draw the transparent-circle
712                 mHoleCirclePath.reset();
713                 mHoleCirclePath.addCircle(center.x, center.y, secondHoleRadius, Path.Direction.CW);
714                 mHoleCirclePath.addCircle(center.x, center.y, holeRadius, Path.Direction.CCW);
715                 mBitmapCanvas.drawPath(mHoleCirclePath, mTransparentCirclePaint);
716 
717                 // reset alpha
718                 mTransparentCirclePaint.setAlpha(alpha);
719             }
720             MPPointF.recycleInstance(center);
721         }
722     }
723 
724     protected Path mDrawCenterTextPathBuffer = new Path();
725     /**
726      * draws the description text in the center of the pie chart makes most
727      * sense when center-hole is enabled
728      */
drawCenterText(Canvas c)729     protected void drawCenterText(Canvas c) {
730 
731         CharSequence centerText = mChart.getCenterText();
732 
733         if (mChart.isDrawCenterTextEnabled() && centerText != null) {
734 
735             MPPointF center = mChart.getCenterCircleBox();
736             MPPointF offset = mChart.getCenterTextOffset();
737 
738             float x = center.x + offset.x;
739             float y = center.y + offset.y;
740 
741             float innerRadius = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled()
742                     ? mChart.getRadius() * (mChart.getHoleRadius() / 100f)
743                     : mChart.getRadius();
744 
745             RectF holeRect = mRectBuffer[0];
746             holeRect.left = x - innerRadius;
747             holeRect.top = y - innerRadius;
748             holeRect.right = x + innerRadius;
749             holeRect.bottom = y + innerRadius;
750             RectF boundingRect = mRectBuffer[1];
751             boundingRect.set(holeRect);
752 
753             float radiusPercent = mChart.getCenterTextRadiusPercent() / 100f;
754             if (radiusPercent > 0.0) {
755                 boundingRect.inset(
756                         (boundingRect.width() - boundingRect.width() * radiusPercent) / 2.f,
757                         (boundingRect.height() - boundingRect.height() * radiusPercent) / 2.f
758                 );
759             }
760 
761             if (!centerText.equals(mCenterTextLastValue) || !boundingRect.equals(mCenterTextLastBounds)) {
762 
763                 // Next time we won't recalculate StaticLayout...
764                 mCenterTextLastBounds.set(boundingRect);
765                 mCenterTextLastValue = centerText;
766 
767                 float width = mCenterTextLastBounds.width();
768 
769                 // If width is 0, it will crash. Always have a minimum of 1
770                 mCenterTextLayout = new StaticLayout(centerText, 0, centerText.length(),
771                         mCenterTextPaint,
772                         (int) Math.max(Math.ceil(width), 1.f),
773                         Layout.Alignment.ALIGN_CENTER, 1.f, 0.f, false);
774             }
775 
776             //float layoutWidth = Utils.getStaticLayoutMaxWidth(mCenterTextLayout);
777             float layoutHeight = mCenterTextLayout.getHeight();
778 
779             c.save();
780             if (Build.VERSION.SDK_INT >= 18) {
781                 Path path = mDrawCenterTextPathBuffer;
782                 path.reset();
783                 path.addOval(holeRect, Path.Direction.CW);
784                 c.clipPath(path);
785             }
786 
787             c.translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2.f);
788             mCenterTextLayout.draw(c);
789 
790             c.restore();
791 
792             MPPointF.recycleInstance(center);
793             MPPointF.recycleInstance(offset);
794         }
795     }
796 
797     protected RectF mDrawHighlightedRectF = new RectF();
798     @Override
drawHighlighted(Canvas c, Highlight[] indices)799     public void drawHighlighted(Canvas c, Highlight[] indices) {
800 
801         /* Skip entirely if using rounded circle slices, because it doesn't make sense to highlight
802          * in this way.
803          * TODO: add support for changing slice color with highlighting rather than only shifting the slice
804          */
805 
806         final boolean drawInnerArc = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled();
807         if (drawInnerArc && mChart.isDrawRoundedSlicesEnabled())
808             return;
809 
810         float phaseX = mAnimator.getPhaseX();
811         float phaseY = mAnimator.getPhaseY();
812 
813         float angle;
814         float rotationAngle = mChart.getRotationAngle();
815 
816         float[] drawAngles = mChart.getDrawAngles();
817         float[] absoluteAngles = mChart.getAbsoluteAngles();
818         final MPPointF center = mChart.getCenterCircleBox();
819         final float radius = mChart.getRadius();
820         final float userInnerRadius = drawInnerArc
821                 ? radius * (mChart.getHoleRadius() / 100.f)
822                 : 0.f;
823 
824         final RectF highlightedCircleBox = mDrawHighlightedRectF;
825         highlightedCircleBox.set(0,0,0,0);
826 
827         for (int i = 0; i < indices.length; i++) {
828 
829             // get the index to highlight
830             int index = (int) indices[i].getX();
831 
832             if (index >= drawAngles.length)
833                 continue;
834 
835             IPieDataSet set = mChart.getData()
836                     .getDataSetByIndex(indices[i].getDataSetIndex());
837 
838             if (set == null || !set.isHighlightEnabled())
839                 continue;
840 
841             final int entryCount = set.getEntryCount();
842             int visibleAngleCount = 0;
843             for (int j = 0; j < entryCount; j++) {
844                 // draw only if the value is greater than zero
845                 if ((Math.abs(set.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) {
846                     visibleAngleCount++;
847                 }
848             }
849 
850             if (index == 0)
851                 angle = 0.f;
852             else
853                 angle = absoluteAngles[index - 1] * phaseX;
854 
855             final float sliceSpace = visibleAngleCount <= 1 ? 0.f : set.getSliceSpace();
856 
857             float sliceAngle = drawAngles[index];
858             float innerRadius = userInnerRadius;
859 
860             float shift = set.getSelectionShift();
861             final float highlightedRadius = radius + shift;
862             highlightedCircleBox.set(mChart.getCircleBox());
863             highlightedCircleBox.inset(-shift, -shift);
864 
865             final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f;
866 
867             Integer highlightColor = set.getHighlightColor();
868             if (highlightColor == null)
869                 highlightColor = set.getColor(index);
870             mRenderPaint.setColor(highlightColor);
871 
872             final float sliceSpaceAngleOuter = visibleAngleCount == 1 ?
873                     0.f :
874                     sliceSpace / (Utils.FDEG2RAD * radius);
875 
876             final float sliceSpaceAngleShifted = visibleAngleCount == 1 ?
877                     0.f :
878                     sliceSpace / (Utils.FDEG2RAD * highlightedRadius);
879 
880             final float startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.f) * phaseY;
881             float sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * phaseY;
882             if (sweepAngleOuter < 0.f) {
883                 sweepAngleOuter = 0.f;
884             }
885 
886             final float startAngleShifted = rotationAngle + (angle + sliceSpaceAngleShifted / 2.f) * phaseY;
887             float sweepAngleShifted = (sliceAngle - sliceSpaceAngleShifted) * phaseY;
888             if (sweepAngleShifted < 0.f) {
889                 sweepAngleShifted = 0.f;
890             }
891 
892             mPathBuffer.reset();
893 
894             if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
895                 // Android is doing "mod 360"
896                 mPathBuffer.addCircle(center.x, center.y, highlightedRadius, Path.Direction.CW);
897             } else {
898 
899                 mPathBuffer.moveTo(
900                         center.x + highlightedRadius * (float) Math.cos(startAngleShifted * Utils.FDEG2RAD),
901                         center.y + highlightedRadius * (float) Math.sin(startAngleShifted * Utils.FDEG2RAD));
902 
903                 mPathBuffer.arcTo(
904                         highlightedCircleBox,
905                         startAngleShifted,
906                         sweepAngleShifted
907                 );
908             }
909 
910             float sliceSpaceRadius = 0.f;
911             if (accountForSliceSpacing) {
912                 sliceSpaceRadius =
913                         calculateMinimumRadiusForSpacedSlice(
914                                 center, radius,
915                                 sliceAngle * phaseY,
916                                 center.x + radius * (float) Math.cos(startAngleOuter * Utils.FDEG2RAD),
917                                 center.y + radius * (float) Math.sin(startAngleOuter * Utils.FDEG2RAD),
918                                 startAngleOuter,
919                                 sweepAngleOuter);
920             }
921 
922             // API < 21 does not receive floats in addArc, but a RectF
923             mInnerRectBuffer.set(
924                     center.x - innerRadius,
925                     center.y - innerRadius,
926                     center.x + innerRadius,
927                     center.y + innerRadius);
928 
929             if (drawInnerArc &&
930                     (innerRadius > 0.f || accountForSliceSpacing)) {
931 
932                 if (accountForSliceSpacing) {
933                     float minSpacedRadius = sliceSpaceRadius;
934 
935                     if (minSpacedRadius < 0.f)
936                         minSpacedRadius = -minSpacedRadius;
937 
938                     innerRadius = Math.max(innerRadius, minSpacedRadius);
939                 }
940 
941                 final float sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.f ?
942                         0.f :
943                         sliceSpace / (Utils.FDEG2RAD * innerRadius);
944                 final float startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.f) * phaseY;
945                 float sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * phaseY;
946                 if (sweepAngleInner < 0.f) {
947                     sweepAngleInner = 0.f;
948                 }
949                 final float endAngleInner = startAngleInner + sweepAngleInner;
950 
951                 if (sweepAngleOuter >= 360.f && sweepAngleOuter % 360f <= Utils.FLOAT_EPSILON) {
952                     // Android is doing "mod 360"
953                     mPathBuffer.addCircle(center.x, center.y, innerRadius, Path.Direction.CCW);
954                 } else {
955 
956                     mPathBuffer.lineTo(
957                             center.x + innerRadius * (float) Math.cos(endAngleInner * Utils.FDEG2RAD),
958                             center.y + innerRadius * (float) Math.sin(endAngleInner * Utils.FDEG2RAD));
959 
960                     mPathBuffer.arcTo(
961                             mInnerRectBuffer,
962                             endAngleInner,
963                             -sweepAngleInner
964                     );
965                 }
966             } else {
967 
968                 if (sweepAngleOuter % 360f > Utils.FLOAT_EPSILON) {
969 
970                     if (accountForSliceSpacing) {
971                         final float angleMiddle = startAngleOuter + sweepAngleOuter / 2.f;
972 
973                         final float arcEndPointX = center.x +
974                                 sliceSpaceRadius * (float) Math.cos(angleMiddle * Utils.FDEG2RAD);
975                         final float arcEndPointY = center.y +
976                                 sliceSpaceRadius * (float) Math.sin(angleMiddle * Utils.FDEG2RAD);
977 
978                         mPathBuffer.lineTo(
979                                 arcEndPointX,
980                                 arcEndPointY);
981 
982                     } else {
983 
984                         mPathBuffer.lineTo(
985                                 center.x,
986                                 center.y);
987                     }
988 
989                 }
990 
991             }
992 
993             mPathBuffer.close();
994 
995             mBitmapCanvas.drawPath(mPathBuffer, mRenderPaint);
996         }
997 
998         MPPointF.recycleInstance(center);
999     }
1000 
1001     /**
1002      * This gives all pie-slices a rounded edge.
1003      *
1004      * @param c
1005      */
drawRoundedSlices(Canvas c)1006     protected void drawRoundedSlices(Canvas c) {
1007 
1008         if (!mChart.isDrawRoundedSlicesEnabled())
1009             return;
1010 
1011         IPieDataSet dataSet = mChart.getData().getDataSet();
1012 
1013         if (!dataSet.isVisible())
1014             return;
1015 
1016         float phaseX = mAnimator.getPhaseX();
1017         float phaseY = mAnimator.getPhaseY();
1018 
1019         MPPointF center = mChart.getCenterCircleBox();
1020         float r = mChart.getRadius();
1021 
1022         // calculate the radius of the "slice-circle"
1023         float circleRadius = (r - (r * mChart.getHoleRadius() / 100f)) / 2f;
1024 
1025         float[] drawAngles = mChart.getDrawAngles();
1026         float angle = mChart.getRotationAngle();
1027 
1028         for (int j = 0; j < dataSet.getEntryCount(); j++) {
1029 
1030             float sliceAngle = drawAngles[j];
1031 
1032             Entry e = dataSet.getEntryForIndex(j);
1033 
1034             // draw only if the value is greater than zero
1035             if ((Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
1036 
1037                 float x = (float) ((r - circleRadius)
1038                         * Math.cos(Math.toRadians((angle + sliceAngle)
1039                         * phaseY)) + center.x);
1040                 float y = (float) ((r - circleRadius)
1041                         * Math.sin(Math.toRadians((angle + sliceAngle)
1042                         * phaseY)) + center.y);
1043 
1044                 mRenderPaint.setColor(dataSet.getColor(j));
1045                 mBitmapCanvas.drawCircle(x, y, circleRadius, mRenderPaint);
1046             }
1047 
1048             angle += sliceAngle * phaseX;
1049         }
1050         MPPointF.recycleInstance(center);
1051     }
1052 
1053     /**
1054      * Releases the drawing bitmap. This should be called when {@link LineChart#onDetachedFromWindow()}.
1055      */
releaseBitmap()1056     public void releaseBitmap() {
1057         if (mBitmapCanvas != null) {
1058             mBitmapCanvas.setBitmap(null);
1059             mBitmapCanvas = null;
1060         }
1061         if (mDrawBitmap != null) {
1062             Bitmap drawBitmap = mDrawBitmap.get();
1063             if (drawBitmap != null) {
1064                 drawBitmap.recycle();
1065             }
1066             mDrawBitmap.clear();
1067             mDrawBitmap = null;
1068         }
1069     }
1070 }
1071