package hr.tacitus.graphs; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.Nullable; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; /** * Created by Bero on 7.7.2017.. */ public class PieChartView extends View { class PieChartItem { float value; // color for this pie chart item Paint paint; // text String description; // cached value int descriptionWidth; float getAngle() { return value * 3.6f; } } private static float MAX_LEGEND_WIDTH_PERCENTAGE = 35f; private static float MAX_LEGEND_WIDTH_PX = 300; private static float LEGEND_PADDING_HORIZONTAL = 20f; private static float LEGEND_PADDING_TOP = 20f; private float titleTextSizeSp = 16.f; private boolean showLegend = true; private float legendTextSizeSp = 10.f; private float legendSquareSize = 20f; private float legendWidth; private float chartPadding = 64.f; private float chartTextSizeSp = 10.f; private String title = "This is a title"; private List colors; private TextPaint titlePaint; private TextPaint valuePaint; private TextPaint legendDescriptionPaint; private StaticLayout titleTextLayout; private RectF titleRect; private RectF legendRect; private RectF pieChartRect; private List items = new ArrayList<>(); private boolean readyToDraw = false; public PieChartView(Context context) { super(context); init(null); } public PieChartView(Context context, AttributeSet attrs) { super(context, attrs); init(attrs); } public PieChartView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(attrs); } private void readAttributes(AttributeSet attributeSet) { TypedArray a = getContext().obtainStyledAttributes(attributeSet, R.styleable.PieChartView); if (a != null) { try { title = a.getString(R.styleable.PieChartView_title); showLegend = a.getBoolean(R.styleable.PieChartView_showLegend, true); } finally { a.recycle(); } } } @Nullable private RectF getLegendRect() { if (showLegend) { float left = titleRect.right - legendWidth; float right = titleRect.right; float top = titleRect.bottom; float bottom = getBottom(); return new RectF(left, top, right, bottom); } return null; } private RectF getPieChartRect() { float left = titleRect.left + chartPadding; float top = titleRect.top + chartPadding; float right = titleRect.right - chartPadding; if (showLegend) right -= legendRect.width(); float bottom = getBottom() - getPaddingLeft() - chartPadding; RectF rectF = new RectF(left, top, right, bottom); // width must be equal to height if (rectF.width() != rectF.height()) { float width = Math.min(rectF.width(), rectF.height()); left = rectF.centerX() - width / 2; right = left + width; top = rectF.centerY() - width / 2; bottom = top + width; rectF = new RectF(left, top, right, bottom); } return rectF; } private RectF getTitleRect() { return new RectF(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingLeft() - getPaddingRight(), 32.f); } private void initTitleText() { // int width = (int) titlePaint.measureText(title); titleTextLayout = new StaticLayout(title, titlePaint, (int) titleRect.width(), Layout.Alignment.ALIGN_CENTER, 1.0f, 0, false); } private void init(AttributeSet attributeSet) { if (attributeSet != null) readAttributes(attributeSet); titlePaint = new TextPaint(); titlePaint.setAntiAlias(true); titlePaint.setTextSize(Utils.spsToPixels(titleTextSizeSp, getResources())); titlePaint.setColor(Color.BLACK); valuePaint = new TextPaint(); valuePaint.setAntiAlias(true); valuePaint.setTextSize(Utils.spsToPixels(chartTextSizeSp, getResources())); valuePaint.setColor(Color.BLACK); legendDescriptionPaint = new TextPaint(); legendDescriptionPaint.setAntiAlias(true); legendDescriptionPaint.setTextSize(Utils.spsToPixels(legendTextSizeSp, getResources())); } private void initRectangles() { titleRect = getTitleRect(); if (showLegend) { updateLegendWidth(); } legendRect = getLegendRect(); pieChartRect = getPieChartRect(); } private void drawTitle(Canvas canvas) { if (titleTextLayout != null) titleTextLayout.draw(canvas); } private void drawChart(Canvas canvas) { float startAngle = -90.f; for (PieChartItem item : items) { canvas.drawArc(pieChartRect, startAngle, item.getAngle(), true, item.paint); startAngle += item.getAngle(); } startAngle = 90.f; for (PieChartItem item : items) { float angleMiddle = startAngle - item.getAngle() / 2; float r = pieChartRect.width() / 2; float x = (float) Math.cos(Math.toRadians(angleMiddle)) * r; float y = (float) Math.sin(Math.toRadians(angleMiddle)) * r; String text = Double.toString(item.value); // Depending on quadrant, adjust text. float textWidth = valuePaint.measureText(text); float left = x + pieChartRect.centerX(); float top = pieChartRect.centerY() - y; // RectF textRect = new RectF(left, top, left + textWidth, top + valuePaint.getTextSize()); canvas.drawText(Integer.toHexString(item.paint.getColor()), left, top, valuePaint); startAngle -= item.getAngle(); } } private void drawLegend(Canvas canvas) { Paint p = new Paint(); p.setColor(Color.RED); canvas.drawRect(legendRect, p); float left = legendRect.left + LEGEND_PADDING_HORIZONTAL; float top = legendRect.top + LEGEND_PADDING_TOP; // Draw color explanations for (int i = 0; i < items.size(); ++i) { PieChartItem item = items.get(i); canvas.drawRect(new RectF(left, top, left + legendSquareSize, top + legendSquareSize), item.paint); // canvas.drawText(Double.toString(item.value), left + 30f, top + 20f, valuePaint); StaticLayout staticLayout = new StaticLayout(item.description, valuePaint, Utils.textWidth(item.description, valuePaint, true), Layout.Alignment.ALIGN_NORMAL, 0, 0, false); canvas.save(); canvas.translate(left + (legendSquareSize * 1.5f), top); staticLayout.draw(canvas); canvas.restore(); top += legendSquareSize * 1.5f; } } @Override public void onDraw(Canvas canvas) { if (!readyToDraw) return; drawTitle(canvas); drawChart(canvas); if (showLegend) drawLegend(canvas); } @Override public void onSizeChanged(int xNew, int yNew, int xOld, int yOld) { super.onSizeChanged(xNew, yNew, xOld, yOld); initRectangles(); initTitleText(); readyToDraw = true; } private void initColors(int count) { colors = new ArrayList<>(); // 3 sets of 2 main colors - rb, rg, bg int maxValue = 220; int perSetGain = (int) (maxValue / (Math.ceil(count / 6.) + 1)); float r = maxValue, g = 0, b = 0; for (int i = 0; i < count; ++i) { int color = Color.rgb((int) r, (int) g, (int) b); colors.add(color); r = Color.green(color); g = Color.blue(color); b = Color.red(color); if (i != 0 && (i + 1) % 3 == 0) { if (i == 2) { g = maxValue; } else { r -= perSetGain; } } } } private void createItems(float[] values) { for (int i = 0; i < values.length; ++i) { PieChartItem pieChartItem = new PieChartItem(); pieChartItem.value = values[i]; pieChartItem.paint = new Paint(Paint.ANTI_ALIAS_FLAG); pieChartItem.paint.setColor(colors.get(i)); pieChartItem.description = Double.toString(values[i]); pieChartItem.descriptionWidth = Utils.textWidth(pieChartItem.description, legendDescriptionPaint, true); items.add(pieChartItem); } } private float getMaxLegendWidth() { float width = getWidth(); float maxPercWidth = width * MAX_LEGEND_WIDTH_PERCENTAGE; return Math.min(maxPercWidth, MAX_LEGEND_WIDTH_PX); } /** * Calculates required legend width. */ private void updateLegendWidth() { float maxWidth = getMaxLegendWidth(); legendWidth = 0; for (PieChartItem item : items) { float width = item.descriptionWidth + 2 * LEGEND_PADDING_HORIZONTAL + legendSquareSize * 1.5f; if (width > legendWidth) legendWidth = width; if (legendWidth >= maxWidth) { legendWidth = maxWidth; break; } } } public void setValues(float[] values) { items.clear(); initColors(values.length); createItems(values); initRectangles(); invalidate(); } }