package com.github.mikephil.charting.renderer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Rect;
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;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.formatter.IValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IPieDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;
import java.lang.ref.WeakReference;
import java.util.List;
public class PieChartRenderer extends DataRenderer {
protected PieChart mChart;
/**
* paint for the hole in the center of the pie chart and the transparent
* circle
*/
protected Paint mHolePaint;
protected Paint mTransparentCirclePaint;
protected Paint mValueLinePaint;
/**
* paint object for the text that can be displayed in the center of the
* chart
*/
private TextPaint mCenterTextPaint;
/**
* paint object used for drwing the slice-text
*/
private Paint mEntryLabelsPaint;
private StaticLayout mCenterTextLayout;
private CharSequence mCenterTextLastValue;
private RectF mCenterTextLastBounds = new RectF();
private RectF[] mRectBuffer = {new RectF(), new RectF(), new RectF()};
/**
* Bitmap for drawing the center hole
*/
protected WeakReference<Bitmap> mDrawBitmap;
protected Canvas mBitmapCanvas;
public PieChartRenderer(PieChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(animator, viewPortHandler);
mChart = chart;
mHolePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHolePaint.setColor(Color.WHITE);
mHolePaint.setStyle(Style.FILL);
mTransparentCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTransparentCirclePaint.setColor(Color.WHITE);
mTransparentCirclePaint.setStyle(Style.FILL);
mTransparentCirclePaint.setAlpha(105);
mCenterTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mCenterTextPaint.setColor(Color.BLACK);
mCenterTextPaint.setTextSize(Utils.convertDpToPixel(12f));
mValuePaint.setTextSize(Utils.convertDpToPixel(13f));
mValuePaint.setColor(Color.WHITE);
mValuePaint.setTextAlign(Align.CENTER);
mEntryLabelsPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mEntryLabelsPaint.setColor(Color.WHITE);
mEntryLabelsPaint.setTextAlign(Align.CENTER);
mEntryLabelsPaint.setTextSize(Utils.convertDpToPixel(13f));
mValueLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mValueLinePaint.setStyle(Style.STROKE);
}
public Paint getPaintHole() {
return mHolePaint;
}
public Paint getPaintTransparentCircle() {
return mTransparentCirclePaint;
}
public TextPaint getPaintCenterText() {
return mCenterTextPaint;
}
public Paint getPaintEntryLabels() {
return mEntryLabelsPaint;
}
@Override
public void initBuffers() {
// TODO Auto-generated method stub
}
@Override
public void drawData(Canvas c) {
int width = (int) mViewPortHandler.getChartWidth();
int height = (int) mViewPortHandler.getChartHeight();
if (mDrawBitmap == null || (mDrawBitmap.get().getWidth() != width) || (mDrawBitmap.get().getHeight() != height)) {
if (width > 0 && height > 0) {
mDrawBitmap = new WeakReference<Bitmap>(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444));
mBitmapCanvas = new Canvas(mDrawBitmap.get());
} else
return;
}
mDrawBitmap.get().eraseColor(Color.TRANSPARENT);
PieData pieData = mChart.getData();
for (IPieDataSet set : pieData.getDataSets()) {
if (set.isVisible() && set.getEntryCount() > 0)
drawDataSet(c, set);
}
}
private Path mPathBuffer = new Path();
private RectF mInnerRectBuffer = new RectF();
protected float calculateMinimumRadiusForSpacedSlice(MPPointF center, float radius, float angle, float arcStartPointX, float
arcStartPointY, float startAngle, float sweepAngle) {
final float angleMiddle = startAngle + sweepAngle / 2.f;
// Other point of the arc
float arcEndPointX = center.x + radius * (float) Math.cos((startAngle + sweepAngle) * Utils.FDEG2RAD);
float arcEndPointY = center.y + radius * (float) Math.sin((startAngle + sweepAngle) * Utils.FDEG2RAD);
// Middle point on the arc
float arcMidPointX = center.x + radius * (float) Math.cos(angleMiddle * Utils.FDEG2RAD);
float arcMidPointY = center.y + radius * (float) Math.sin(angleMiddle * Utils.FDEG2RAD);
// This is the base of the contained triangle
double basePointsDistance = Math.sqrt(Math.pow(arcEndPointX - arcStartPointX, 2) + Math.pow(arcEndPointY - arcStartPointY, 2));
// After reducing space from both sides of the "slice",
// the angle of the contained triangle should stay the same.
// So let's find out the height of that triangle.
float containedTriangleHeight = (float) (basePointsDistance / 2.0 * Math.tan((180.0 - angle) / 2.0 * Utils.DEG2RAD));
// Now we subtract that from the radius
float spacedRadius = radius - containedTriangleHeight;
// And now subtract the height of the arc that's between the triangle and the outer circle
spacedRadius -= Math.sqrt(Math.pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.f, 2) + Math.pow(arcMidPointY -
(arcEndPointY + arcStartPointY) / 2.f, 2));
return spacedRadius;
}
/**
* 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) {
if (!dataSet.isAutomaticallyDisableSliceSpacingEnabled())
return dataSet.getSliceSpace();
float spaceSizeRatio = dataSet.getSliceSpace() / mViewPortHandler.getSmallestContentExtension();
float minValueRatio = dataSet.getYMin() / mChart.getData().getYValueSum() * 2;
float sliceSpace = spaceSizeRatio > minValueRatio ? 0f : dataSet.getSliceSpace();
return sliceSpace;
}
protected void drawDataSet(Canvas c, IPieDataSet dataSet) {
float angle = 0;
float rotationAngle = mChart.getRotationAngle();
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
final RectF circleBox = mChart.getCircleBox();
final int entryCount = dataSet.getEntryCount();
final float[] drawAngles = mChart.getDrawAngles();
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;
int visibleAngleCount = 0;
for (int j = 0; j < entryCount; j++) {
// draw only if the value is greater than zero
if ((Math.abs(dataSet.getEntryForIndex(j).getY()) > Utils.FLOAT_EPSILON)) {
visibleAngleCount++;
}
}
final float sliceSpace = visibleAngleCount <= 1 ? 0.f : getSliceSpace(dataSet);
for (int j = 0; j < entryCount; j++) {
float sliceAngle = drawAngles[j];
float innerRadius = userInnerRadius;
Entry e = dataSet.getEntryForIndex(j);
// draw only if the value is greater than zero
if ((Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
if (!mChart.needsHighlight(j)) {
final boolean accountForSliceSpacing = sliceSpace > 0.f && sliceAngle <= 180.f;
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;
}
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 (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.moveTo(arcStartPointX, arcStartPointY);
mPathBuffer.arcTo(circleBox, 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 = calculateMinimumRadiusForSpacedSlice(center, radius, sliceAngle * phaseY,
arcStartPointX, arcStartPointY, startAngleOuter, sweepAngleOuter);
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) {
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;
}
MPPointF.recycleInstance(center);
}
@Override
public void drawValues(Canvas c) {
MPPointF center = mChart.getCenterCircleBox();
// get whole the radius
float radius = mChart.getRadius();
float rotationAngle = mChart.getRotationAngle();
float[] drawAngles = mChart.getDrawAngles();
float[] absoluteAngles = mChart.getAbsoluteAngles();
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
final float holeRadiusPercent = mChart.getHoleRadius() / 100.f;
float labelRadiusOffset = radius / 10f * 3.6f;
if (mChart.isDrawHoleEnabled()) {
labelRadiusOffset = (radius - (radius * holeRadiusPercent)) / 2f;
}
final float labelRadius = radius - labelRadiusOffset;
PieData data = mChart.getData();
List<IPieDataSet> dataSets = data.getDataSets();
float yValueSum = data.getYValueSum();
boolean drawEntryLabels = mChart.isDrawEntryLabelsEnabled();
float angle;
int xIndex = 0;
c.save();
float offset = Utils.convertDpToPixel(5.f);
for (int i = 0; i < dataSets.size(); i++) {
IPieDataSet dataSet = dataSets.get(i);
final boolean drawValues = dataSet.isDrawValuesEnabled();
if (!drawValues && !drawEntryLabels)
continue;
final PieDataSet.ValuePosition xValuePosition = dataSet.getXValuePosition();
final PieDataSet.ValuePosition yValuePosition = dataSet.getYValuePosition();
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
float lineHeight = Utils.calcTextHeight(mValuePaint, "Q") + Utils.convertDpToPixel(4f);
IValueFormatter formatter = dataSet.getValueFormatter();
int entryCount = dataSet.getEntryCount();
mValueLinePaint.setColor(dataSet.getValueLineColor());
mValueLinePaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getValueLineWidth()));
final float sliceSpace = getSliceSpace(dataSet);
MPPointF iconsOffset = MPPointF.getInstance(dataSet.getIconsOffset());
iconsOffset.x = Utils.convertDpToPixel(iconsOffset.x);
iconsOffset.y = Utils.convertDpToPixel(iconsOffset.y);
for (int j = 0; j < entryCount; j++) {
PieEntry entry = dataSet.getEntryForIndex(j);
if (xIndex == 0)
angle = 0.f;
else
angle = absoluteAngles[xIndex - 1] * phaseX;
final float sliceAngle = drawAngles[xIndex];
final float sliceSpaceMiddleAngle = sliceSpace / (Utils.FDEG2RAD * labelRadius);
// offset needed to center the drawn text in the slice
final float angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2.f) / 2.f;
angle = angle + angleOffset;
final float transformedAngle = rotationAngle + angle * phaseY;
// float value = mChart.isUsePercentValuesEnabled() ? entry.getY() / yValueSum * 100f : entry.getY();
float value = entry.getY() ;
float value2 = entry.getY() / yValueSum * 100f;
// String valueString = entry.getY() + "\r\n" + entry.getY() / yValueSum * 100f;
final float sliceXBase = (float) Math.cos(transformedAngle * Utils.FDEG2RAD);
final float sliceYBase = (float) Math.sin(transformedAngle * Utils.FDEG2RAD);
final boolean drawXOutside = drawEntryLabels && xValuePosition == PieDataSet.ValuePosition.OUTSIDE_SLICE;
final boolean drawYOutside = drawValues && yValuePosition == PieDataSet.ValuePosition.OUTSIDE_SLICE;
final boolean drawXInside = drawEntryLabels && xValuePosition == PieDataSet.ValuePosition.INSIDE_SLICE;
final boolean drawYInside = drawValues && yValuePosition == PieDataSet.ValuePosition.INSIDE_SLICE;
if (drawXOutside || drawYOutside) {
final float valueLineLength1 = dataSet.getValueLinePart1Length();
final float valueLineLength2 = dataSet.getValueLinePart2Length();
final float valueLinePart1OffsetPercentage = dataSet.getValueLinePart1OffsetPercentage() / 100.f;
float pt2x, pt2y;
float labelPtx, labelPty;
float line1Radius;
if (mChart.isDrawHoleEnabled())
line1Radius = (radius - (radius * holeRadiusPercent)) * valueLinePart1OffsetPercentage + (radius *
holeRadiusPercent);
else
line1Radius = radius * valueLinePart1OffsetPercentage;
final float polyline2Width = dataSet.isValueLineVariableLength() ? labelRadius * valueLineLength2 * (float) Math.abs
(Math.sin(transformedAngle * Utils.FDEG2RAD)) : labelRadius * valueLineLength2;
final float pt0x = line1Radius * sliceXBase + center.x;
final float pt0y = line1Radius * sliceYBase + center.y;
final float pt1x = labelRadius * (1 + valueLineLength1) * sliceXBase + center.x;
final float pt1y = labelRadius * (1 + valueLineLength1) * sliceYBase + center.y;
if (transformedAngle % 360.0 >= 90.0 && transformedAngle % 360.0 <= 270.0) {
pt2x = pt1x - polyline2Width;
pt2y = pt1y;
mValuePaint.setTextAlign(Align.RIGHT);
if (drawXOutside)
mEntryLabelsPaint.setTextAlign(Align.RIGHT);
labelPtx = pt2x - offset;
labelPty = pt2y;
} else {
pt2x = pt1x + polyline2Width;
pt2y = pt1y;
mValuePaint.setTextAlign(Align.LEFT);
if (drawXOutside)
mEntryLabelsPaint.setTextAlign(Align.LEFT);
labelPtx = pt2x + offset;
labelPty = pt2y;
}
if (dataSet.getValueLineColor() != ColorTemplate.COLOR_NONE) {
c.drawLine(pt0x, pt0y, pt1x, pt1y, mValueLinePaint);
c.drawLine(pt1x, pt1y, pt2x, pt2y, mValueLinePaint);
}
// draw everything, depending on settings
if (drawXOutside && drawYOutside) {
if (mChart.getMyLabelStyle()) {
drawValue(c, null, value, entry, 0, labelPtx, labelPty - 30, dataSet.getValueTextColor(j));
drawValue(c, formatter, value2, entry, 0, labelPtx, labelPty, Color.parseColor("#ff6633"));
} else {
drawValue(c, formatter, value, entry, 0, labelPtx, labelPty, dataSet.getValueTextColor(j));
}
if (j < data.getEntryCount() && entry.getLabel() != null) {
drawEntryLabel(c, entry.getLabel(), labelPtx, labelPty + lineHeight, dataSet.getColor(j));
}
} else if (drawXOutside) {
if (j < data.getEntryCount() && entry.getLabel() != null) {
drawEntryLabel(c, entry.getLabel(), labelPtx, labelPty + lineHeight / 2.f, dataSet.getColor(j));
}
} else if (drawYOutside) {
if (mChart.getMyLabelStyle()) {
drawValue(c, null, value, entry, 0, labelPtx, labelPty - 30 + lineHeight / 2.f, dataSet
.getValueTextColor(j));
drawValue(c, formatter, value2, entry, 0, labelPtx, labelPty + lineHeight / 2.f, Color.parseColor("#ff6633"));
} else {
drawValue(c, formatter, value, entry, 0, labelPtx, labelPty + lineHeight / 2.f, dataSet.getValueTextColor(j));
}
}
}
if (drawXInside || drawYInside) {
// calculate the text position
float x = labelRadius * sliceXBase + center.x;
float y = labelRadius * sliceYBase + center.y;
mValuePaint.setTextAlign(Align.CENTER);
// draw everything, depending on settings
if (drawXInside && drawYInside) {
if (mChart.getMyLabelStyle()) {
drawValue(c, null, value, entry, 0, x, y - 30, dataSet.getValueTextColor(j));
drawValue(c, formatter, value2, entry, 0, x, y , Color.parseColor("#ff6633"));
} else {
drawValue(c, formatter, value, entry, 0, x, y, dataSet.getValueTextColor(j));
}
if (j < data.getEntryCount() && entry.getLabel() != null) {
drawEntryLabel(c, entry.getLabel(), x, y + lineHeight, dataSet.getColor(j));
}
} else if (drawXInside) {
if (j < data.getEntryCount() && entry.getLabel() != null) {
drawEntryLabel(c, entry.getLabel(), x, y + lineHeight / 2f, dataSet.getColor(j));
}
} else if (drawYInside) {
if (mChart.getMyLabelStyle()) {
drawValue(c, null, value, entry, 0, x, y - 30 + lineHeight / 2f, dataSet.getValueTextColor(j));
drawValue(c, formatter, value2, entry, 0, x, y + lineHeight / 2f, Color.parseColor("#ff6633"));
} else {
drawValue(c, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j));
}
}
}
if (entry.getIcon() != null && dataSet.isDrawIconsEnabled()) {
Drawable icon = entry.getIcon();
float x = (labelRadius + iconsOffset.y) * sliceXBase + center.x;
float y = (labelRadius + iconsOffset.y) * sliceYBase + center.y;
y += iconsOffset.x;
Utils.drawImage(c, icon, (int) x, (int) y, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
}
xIndex++;
}
MPPointF.recycleInstance(iconsOffset);
}
MPPointF.recycleInstance(center);
c.restore();
}
/**
* 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, int bgColor) {
if (mChart.getMyLabelStyle()) {
Paint paint = new Paint();
paint.setColor(bgColor);
Rect rect = new Rect();
mEntryLabelsPaint.getTextBounds(label, 0, label.length(), rect);
if (mEntryLabelsPaint.getTextAlign() == Align.LEFT) {
c.drawRect(x - 10, y - rect.height() - 5, x + rect.width() + 10, y + 10, paint);
} else {
c.drawRect(x - rect.width() - 10, y - rect.height() - 5, x + 10, y + 10, paint);
}
}
c.drawText(label, x, y, mEntryLabelsPaint);
}
@Override
public void drawExtras(Canvas c) {
// drawCircles(c);
drawHole(c);
c.drawBitmap(mDrawBitmap.get(), 0, 0, null);
drawCenterText(c);
}
private Path mHoleCirclePath = new Path();
/**
* draws the hole in the center of the chart and the transparent circle /
* hole
*/
protected void drawHole(Canvas c) {
if (mChart.isDrawHoleEnabled() && mBitmapCanvas != null) {
float radius = mChart.getRadius();
float holeRadius = radius * (mChart.getHoleRadius() / 100);
MPPointF center = mChart.getCenterCircleBox();
if (Color.alpha(mHolePaint.getColor()) > 0) {
// draw the hole-circle
mBitmapCanvas.drawCircle(center.x, center.y, holeRadius, mHolePaint);
}
// only draw the circle if it can be seen (not covered by the hole)
if (Color.alpha(mTransparentCirclePaint.getColor()) > 0 && mChart.getTransparentCircleRadius() > mChart.getHoleRadius()) {
int alpha = mTransparentCirclePaint.getAlpha();
float secondHoleRadius = radius * (mChart.getTransparentCircleRadius() / 100);
mTransparentCirclePaint.setAlpha((int) ((float) alpha * mAnimator.getPhaseX() * mAnimator.getPhaseY()));
// draw the transparent-circle
mHoleCirclePath.reset();
mHoleCirclePath.addCircle(center.x, center.y, secondHoleRadius, Path.Direction.CW);
mHoleCirclePath.addCircle(center.x, center.y, holeRadius, Path.Direction.CCW);
mBitmapCanvas.drawPath(mHoleCirclePath, mTransparentCirclePaint);
// reset alpha
mTransparentCirclePaint.setAlpha(alpha);
}
MPPointF.recycleInstance(center);
}
}
protected Path mDrawCenterTextPathBuffer = new Path();
/**
* draws the description text in the center of the pie chart makes most
* sense when center-hole is enabled
*/
protected void drawCenterText(Canvas c) {
CharSequence centerText = mChart.getCenterText();
if (mChart.isDrawCenterTextEnabled() && centerText != null) {
MPPointF center = mChart.getCenterCircleBox();
MPPointF offset = mChart.getCenterTextOffset();
float x = center.x + offset.x;
float y = center.y + offset.y;
float innerRadius = mChart.isDrawHoleEnabled() && !mChart.isDrawSlicesUnderHoleEnabled() ? mChart.getRadius() * (mChart
.getHoleRadius() / 100f) : mChart.getRadius();
RectF holeRect = mRectBuffer[0];
holeRect.left = x - innerRadius;
holeRect.top = y - innerRadius;
holeRect.right = x + innerRadius;
holeRect.bottom = y + innerRadius;
RectF boundingRect = mRectBuffer[1];
boundingRect.set(holeRect);
float radiusPercent = mChart.getCenterTextRadiusPercent() / 100f;
if (radiusPercent > 0.0) {
boundingRect.inset((boundingRect.width() - boundingRect.width() * radiusPercent) / 2.f, (boundingRect.height() -
boundingRect.height() * radiusPercent) / 2.f);
}
if (!centerText.equals(mCenterTextLastValue) || !boundingRect.equals(mCenterTextLastBounds)) {
// Next time we won't recalculate StaticLayout...
mCenterTextLastBounds.set(boundingRect);
mCenterTextLastValue = centerText;
float width = mCenterTextLastBounds.width();
// If width is 0, it will crash. Always have a minimum of 1
mCenterTextLayout = new StaticLayout(centerText, 0, centerText.length(), mCenterTextPaint, (int) Math.max(Math.ceil
(width), 1.f), Layout.Alignment.ALIGN_CENTER, 1.f, 0.f, false);
}
//float layoutWidth = Utils.getStaticLayoutMaxWidth(mCenterTextLayout);
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);
}
c.translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2.f);
mCenterTextLayout.draw(c);
c.restore();
MPPointF.recycleInstance(center);
MPPointF.recycleInstance(offset);
}
}
protected RectF mDrawHighlightedRectF = new RectF();
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
float angle;
float rotationAngle = mChart.getRotationAngle();
float[] drawAngles = mChart.getDrawAngles();
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;
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;
mRenderPaint.setColor(set.getColor(index));
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);
}
/**
* This gives all pie-slices a rounded edge.
*
* @param c
*/
protected void drawRoundedSlices(Canvas c) {
if (!mChart.isDrawRoundedSlicesEnabled())
return;
IPieDataSet dataSet = mChart.getData().getDataSet();
if (!dataSet.isVisible())
return;
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
MPPointF center = mChart.getCenterCircleBox();
float r = mChart.getRadius();
// calculate the radius of the "slice-circle"
float circleRadius = (r - (r * mChart.getHoleRadius() / 100f)) / 2f;
float[] drawAngles = mChart.getDrawAngles();
float angle = mChart.getRotationAngle();
for (int j = 0; j < dataSet.getEntryCount(); j++) {
float sliceAngle = drawAngles[j];
Entry e = dataSet.getEntryForIndex(j);
// draw only if the value is greater than zero
if ((Math.abs(e.getY()) > Utils.FLOAT_EPSILON)) {
float x = (float) ((r - circleRadius) * Math.cos(Math.toRadians((angle + sliceAngle) * phaseY)) + center.x);
float y = (float) ((r - circleRadius) * Math.sin(Math.toRadians((angle + sliceAngle) * phaseY)) + center.y);
mRenderPaint.setColor(dataSet.getColor(j));
mBitmapCanvas.drawCircle(x, y, circleRadius, mRenderPaint);
}
angle += sliceAngle * phaseX;
}
MPPointF.recycleInstance(center);
}
/**
* Releases the drawing bitmap. This should be called when {@link LineChart#onDetachedFromWindow()}.
*/
public void releaseBitmap() {
if (mBitmapCanvas != null) {
mBitmapCanvas.setBitmap(null);
mBitmapCanvas = null;
}
if (mDrawBitmap != null) {
mDrawBitmap.get().recycle();
mDrawBitmap.clear();
mDrawBitmap = null;
}
}
}