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.text.TextUtils; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; import static android.R.attr.ellipsize; /** * 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; String valueDescription; 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 legendMaxTextWidth; private float CHART_PADDING_DP = 24.f; private float chartTextSizeSp = 10.f; private float chartPaddingPx; private PieChartAdapter adapter; private PieChartColorProvider colorProvider; private String 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 = getHeight(); return new RectF(left, top, right, bottom); } return null; } private RectF getPieChartRect() { // Pie chart max rectangle float left = titleRect.left + chartPaddingPx; float top = titleRect.bottom + chartPaddingPx; float right = titleRect.right - chartPaddingPx; if (showLegend) right -= legendRect.width(); float bottom = getHeight() - getPaddingBottom() - chartPaddingPx; 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) { chartPaddingPx = Utils.dipsToPixels(CHART_PADDING_DP, getResources()); 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) { // Paint p = new Paint(); // p.setColor(Color.CYAN); // canvas.drawRect(pieChartRect, p); if (items.size() == 0) { StaticLayout staticLayout = new StaticLayout("No data available", valuePaint, (int)titleRect.width(), Layout.Alignment.ALIGN_CENTER, 0, 0, false); canvas.save(); canvas.translate(0, titleRect.top + chartPaddingPx); staticLayout.draw(canvas); canvas.restore(); return; } 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; float left = x + pieChartRect.centerX(); float top = pieChartRect.centerY() - y; // RectF textRect = new RectF(left, top, left + textWidth, top + valuePaint.getTextSize()); canvas.drawText(item.valueDescription, 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); StaticLayout staticLayout = new StaticLayout(item.description, 0, item.description.length(), legendDescriptionPaint, item.descriptionWidth, Layout.Alignment.ALIGN_NORMAL, 0, 0, false, TextUtils.TruncateAt.END, (int)legendMaxTextWidth); 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() { colors = new ArrayList<>(); int count = adapter.getCount(); if (colorProvider != null) { for (int i = 0; i < adapter.getCount(); ++i) { colors.add(colorProvider.getColor(i)); } } else { // 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() { for (int i = 0; i < adapter.getCount(); ++i) { PieChartItem pieChartItem = new PieChartItem(); pieChartItem.value = adapter.getValue(i); pieChartItem.paint = new Paint(Paint.ANTI_ALIAS_FLAG); pieChartItem.paint.setColor(colors.get(i)); pieChartItem.description = adapter.getDescription(i); pieChartItem.valueDescription = adapter.getValueDescription(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; } } if (legendWidth > 0) { legendMaxTextWidth = legendWidth - 2 * LEGEND_PADDING_HORIZONTAL - legendSquareSize * 1.5f; } } public PieChartAdapter getAdapter() { return adapter; } public void setAdapter(PieChartAdapter adapter) { this.adapter = adapter; adapterChanged(); } public void setColorProvider(PieChartColorProvider colorProvider) { this.colorProvider = colorProvider; adapterChanged(); } private void adapterChanged() { items.clear(); if (adapter != null) { initColors(); createItems(); initRectangles(); } invalidate(); } }