Custom ring chart

Keywords: Android

Custom ring chart

In Android development, native controls can only meet the needs of most of the time. For some special styles, custom controls are needed to implement them. Next, a circular icon is implemented. The key classes needed are Paint,RectF,Matrix,Path

Basic thinking

1. rings
It can be seen that the circle has a sector area that does not pass through. We can understand that drawing two arcs and connecting the head and tail can form this shape

Path path = new Path();
path.addArc(mMaxRect, startAngle, allData.get(i).getAngle());
path.arcTo(mMinRect, startAngle + allData.get(i).getAngle(), -allData.get(i).getAngle());
path.close();

2. lines
Call drawLine() method

canvas.drawLine(startX, startY, endX, endY, mPaint);

3. character writing

Pay attention to the position of base class line when changing text

 mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);

All code

/**
 * Description: Circular pie
 * Created by Buuu on 2018/04/27.
 */

public class CirclePieChart extends View {
    private List<PieChartPart> allData;
    private Paint mPaint;
    private RectF mMinRect;
    private RectF mMaxRect;
    private Matrix mMatrix;
    private List<Path> mPathsList;
    private int mWH;
    private int mMaxWH;
    private int defaultSpace = 250;//Width around
    private float startAngle = 0f; //Initial angle
    private float lineLength = 50; //Length of indicator line

    public CirclePieChart(Context context) {
        this(context, null);
    }

    public CirclePieChart(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CirclePieChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMatrix.reset();
        if (allData != null && !allData.isEmpty()) {
            mMaxWH = Math.min(w, h);
            mWH = Math.min(w, h) - defaultSpace;
            //Adjust the width by adjusting the size of the inner or outer ring
            mMaxRect = new RectF(-mWH / 2, -mWH / 2, mWH / 2, mWH / 2);
            mMinRect = new RectF(-mWH / 4, -mWH / 4, mWH / 4, mWH / 4);
            //Draw a circular figure. If you need to click on an event, add it as required
            for (int i = 0; i < allData.size(); i++) {
                Path path = new Path();
                path.addArc(mMaxRect, startAngle, allData.get(i).getAngle());
                path.arcTo(mMinRect, startAngle + allData.get(i).getAngle(), -allData.get(i).getAngle());
                path.close();
                startAngle+=allData.get(i).getAngle();
                mPathsList.add(path);
            }
        }
    }

    private void initData() {
        mPathsList = new ArrayList<>();
        mPaint = new Paint();
        mPaint.setTextSize(28);
        mPaint.setColor(Color.parseColor("#3300f0ff"));
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(3);
        mMatrix = new Matrix();
    }

    private Rect mTmpRect = new Rect();
    private float defaultS = 10F;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(mMaxWH / 2, mMaxWH / 2);
        if (mMatrix.isIdentity()) {
            canvas.getMatrix().invert(mMatrix);
        }
        for (int i = 0; i < mPathsList.size(); i++) {
            mPaint.setColor(allData.get(i).getColor());
            canvas.drawPath(mPathsList.get(i), mPaint);
        }
        float childStartAngle = startAngle;
        //Lines and characters
        for (int i = 0; i < allData.size(); i++) {
            double cos = Math.cos(Math.toRadians(childStartAngle + (allData.get(i).getAngle() ) / 2));
            double sin = Math.sin(Math.toRadians(childStartAngle + (allData.get(i).getAngle() ) / 2));
            float startX = (float) (cos * mWH) / 2;
            float endX = (float) (lineLength * cos + startX);
            float startY = (float) (sin * mWH) / 2;
            float endY = (float) (lineLength * sin + startY);
            mPaint.setColor(allData.get(i).getColor());
            canvas.drawLine(startX, startY, endX, endY, mPaint);
            String tmpText = (int) Math.ceil(allData.get(i).getAngle()*100/360) +"%" ;
            mPaint.getTextBounds(tmpText, 0, tmpText.length(), mTmpRect);

            if (endY == 0 && endX > 0) {// x-axis positive direction
                mPaint.setTextAlign(Paint.Align.LEFT);
                canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
            } else if (endX > 0 && endY > 0 && endX >= endY) {//x-axis positive direction 0 < angle < = 45
                mPaint.setTextAlign(Paint.Align.LEFT);
                canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
            } else if (endX > 0 && endY > 0 && endX < endY) {//x-axis positive direction 45 < angle < 90
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX, endY + mTmpRect.height() + defaultS, mPaint);
            } else if (endX == 0 && endY > 0) { //Positive direction of y axis
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX, endY + mTmpRect.height() + defaultS, mPaint);
            } else if (endX < 0 && endY > 0 && Math.abs(endX) <= Math.abs(endY)) {//Positive direction of y axis 90 < a < = 135
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX, endY + mTmpRect.height() + defaultS, mPaint);
            } else if (endX < 0 && endY > 0 && Math.abs(endX) > Math.abs(endY)) {//Positive direction of y axis 135 < a < 180
                mPaint.setTextAlign(Paint.Align.RIGHT);
                canvas.drawText(tmpText, endX - defaultS, endY + mTmpRect.height() / 2, mPaint);
            } else if (endX < 0 && endY == 0) {//x-axis negative reverse
                mPaint.setTextAlign(Paint.Align.RIGHT);
                canvas.drawText(tmpText, endX - defaultS, endY + mTmpRect.height() / 2, mPaint);
            } else if (endX < 0 && endY < 0 && Math.abs(endX) >= Math.abs(endY)) {//x-axis negative reverse 180 < a < = 225
                mPaint.setTextAlign(Paint.Align.RIGHT);
                canvas.drawText(tmpText, endX - defaultS, endY + mTmpRect.height() / 2, mPaint);
            } else if (endX < 0 && endY < 0 && Math.abs(endX) < Math.abs(endY)) {//x-axis negative reverse 225 < a < 270
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX, endY - defaultS, mPaint);
            } else if (endX == 0 && endY < 0) {//y-axis negative direction
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX, endY - defaultS, mPaint);
            } else if (endX > 0 && endY < 0 && Math.abs(endX) <= Math.abs(endY)) {//Negative direction of y axis 270 < a < = 315
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX, endY - defaultS, mPaint);
            } else if (endX > 0 && endY < 0 && Math.abs(endX) > Math.abs(endY)) {//y-axis negative direction 315 < a < 360
                mPaint.setTextAlign(Paint.Align.LEFT);
                canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
            } else {//Other
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(tmpText, endX + defaultS, endY + defaultS, mPaint);
            }
            childStartAngle += allData.get(i).getAngle();
        }
    }

    /**
     * Setting data
     *
     * @param allDatas PieChartPart
     */
    public void setData(List<PieChartPart> allDatas) {
        this.allData = allDatas;


        int numericalSum = 0;
        int tempAngle;

        int standardAngle = 360;

        for (int i = 0; i < allData.size(); i++) {
            numericalSum+=allData.get(i).getNumerical();
        }
        for (int i = 0; i < allData.size(); i++) {
            tempAngle = (int) (allData.get(i).getNumerical() * 360 / numericalSum);

            if(i>1){
                tempAngle +=1;
            }

            if (allData.size()-1 == i){
                tempAngle = standardAngle;
            }else {
                standardAngle -= tempAngle;
            }
            allData.get(i).setAngle(tempAngle);

        }

        invalidate();
    }
}
//PieChartPart
public class PieChartPart {
    private int color;
    private float angle;
    private float numerical;

    public PieChartPart() {
    }

    public PieChartPart(int color, float numerical) {
        this.color = color;
        this.numerical = numerical;
    }

    public PieChartPart(int color, float numerical,float angle) {
        this.color = color;
        this.numerical = numerical;
        this.angle = angle;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public float getAngle() {
        return angle;
    }

    public void setAngle(float angle) {
        this.angle = angle;
    }

    public float getNumerical() {
        return numerical;
    }

    public void setNumerical(int numerical) {
        this.numerical = numerical;
    }
}

Basic thinking

The Path class is mainly used. For a better understanding, you can focus on this class. Oh, by the way, you can use the region class if the click event is involved

Posted by anf.etienne on Sun, 29 Mar 2020 09:40:01 -0700