How to customize Pie Chart View in Android

Damon

How to customize Pie Chart View in Android

My app needs customize pie chart view. Found some open source projects. Either too complex or not so good.
Spent several hours to customize own simple one. Learned how to use path and Path.arcTo.
Customize Pie Chart View
Code first.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
public class PieChartView extends View {

    private int width;
    private int height;

    private float centerX;
    private float centerY;
    private float radius;

    private Paint paintBorder;

    private ArrayList<ChartSegmentInfo> segmentsInfos;

    private Paint paintConver;
    private RectF oval;
    private int[] colorArray = { R.color.holo_green_light,
            R.color.holo_blue_light, R.color.holo_red_light,
            R.color.holo_orange_light, R.color.holo_purple };

    public PieChartView(Context context, AttributeSet attrs) {
        super(context, attrs);

        paintBorder = new Paint();
        paintBorder.setAntiAlias(true);
        paintBorder.setColor(getResources().getColor(R.color.holo_blue_dark));
        paintBorder.setStyle(Paint.Style.STROKE);

        paintConver = new Paint();
        paintConver.setAntiAlias(true);
        paintConver.setColor(Color.WHITE);
        paintConver.setStyle(Paint.Style.FILL);

        segmentsInfos = new ArrayList<PieChartView.ChartSegmentInfo>();
        segmentsInfos.add(new ChartSegmentInfo(0.33333333f, "test1"));
        segmentsInfos.add(new ChartSegmentInfo(0.33333333f, "test2"));
        segmentsInfos.add(new ChartSegmentInfo(0.33333333f, "test3"));

        if (!isInEditMode()) {
            ViewTreeObserver vto = getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    startAnimation();
                }
            });
        }else{
            countTime = 360/angleOffset;
        }
    }

    public void setPieInfo(ArrayList<ChartSegmentInfo> segments) {
        segmentsInfos = segments;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        resetValues(w, h);
    }

    private void resetValues(int w, int h) {
        centerX = ((float) w) / 2;
        centerY = ((float) h) / 2;
        radius = ((float) Math.min(width, height)) / 3;

        oval = new RectF();
        oval.set(centerX - radius, centerY - radius, centerX + radius, centerY
                + radius);

        if (segmentsInfos != null) {
            float lastPosition = -90;
            for (int i = 0; i < segmentsInfos.size(); i++) {
                ChartSegmentInfo csi = segmentsInfos.get(i);
                csi.path = new Path();
                // csi.path.moveTo(centerX, centerX);
                // csi.path.lineTo(centerX, centerY - radius);
                csi.path.arcTo(oval, lastPosition, csi.percentage * 360);

                float angle = -lastPosition - csi.percentage * 360 / 2;

                csi.textX = caculateX(centerX, centerY, radius, angle);
                csi.textY = caculateY(centerX, centerY, radius, angle);

                lastPosition = lastPosition + csi.percentage * 360;

                csi.path.lineTo(centerX, centerY);
                csi.path.close();

                csi.paint = new Paint();
                csi.paint.setAntiAlias(true);
                csi.paint.setTextSize(radius / 5);
                csi.paint.setColor(getResources().getColor(
                        colorArray[% colorArray.length]));
                csi.paint.setStyle(Paint.Style.FILL_AND_STROKE);
            }
        }
    }

    private float caculateX(float centerX2, float centerY2, float radius2,
            float angle) {
        angle = (float) (angle / 360 * Math.PI * 2);
        double temp = 1 / (Math.tan(angle) * Math.tan(angle) + 1);
        float xOffset = (float) (radius2 * Math.sqrt(temp));
        if (angle <= -Math.PI / 2 || angle >= Math.PI / 2)
            xOffset = -xOffset;
        return centerX2 + xOffset * 1.3f - (radius2 / 6);
    }

    private float caculateY(float centerX2, float centerY2, float radius2,
            float angle) {
        angle = (float) (angle / 360 * Math.PI * 2);
        double temp = 1 / (Math.tan(angle) * Math.tan(angle) + 1);
        float yOffset = (float) (radius2 * Math.sqrt(1 - temp));
        if (angle <= 0 && angle >= -Math.PI)
            yOffset = -yOffset;
        return centerY2 - yOffset * 1.3f + (radius2 / 8);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawCircle(centerX, centerY, radius, paintBorder);
        if (segmentsInfos != null) {
            for (int i = 0; i < segmentsInfos.size(); i++) {
                ChartSegmentInfo csi = segmentsInfos.get(i);
                if (csi.percentage == 1) {
                    canvas.drawCircle(centerX, centerY, radius, csi.paint);
                } else {
                    canvas.drawPath(csi.path, csi.paint);
                }
                if (csi.percentage > 0) {
                    canvas.drawText(csi.name + " : " + csi.getPercentage()0,
                            radius / 5 * (+ 1), csi.paint);
                }
            }
        }

        if (countTime * angleOffset <= 360) {
            canvas.drawArc(oval, -90 + countTime * angleOffset, 360 - countTime
                    * angleOffset, true, paintConver);
        }
    }

    public void startAnimation() {
        countTime = 0;
        Thread animThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(timeoffset);
                        countTime++;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (countTime * angleOffset <= 360) {
                        postInvalidate();
                    } else {
                        break;
                    }
                }
            }
        });
        animThread.start();
    }

    private int timeoffset = 25;
    private int countTime = 0;
    private int angleOffset = 8;

    public class ChartSegmentInfo {
        public float percentage;
        public float textX;
        public float textY;
        public String name;
        public Path path;
        public Paint paint;

        public ChartSegmentInfo(float percentage, String name) {
            this.percentage = percentage;
            this.name = name;
        }

        public String getPercentage() {
            String result = String.format("%.2f", percentage * 100);
            return result + "%";
        }
    }
1. Using a list of ChartSegmentInfo to manage the segments informations like percentage ,path, paint.
2. Make a function setPieInfo() to set the segments informations
3. Override the onDraw() method . inside of it draw a circle first and draw different segments depends on list of informations.
4. Optional feature: once this view be loaded start an animation to show out the customize pie view.
There are something need to be noticed:
1. caculateX() and caculateY() functions are used for calculating the point on circle with specified angle. These 2 functions are not so optimized coz of my poor math :P
2. Path.arcTo() This function is a little confused. If you can understand Chinese see this If not you can also see that article there are some images to help you to understand how this api works.
path arcTo
this is calling Path.arcTo(oval, 0, 90);
3. Actually at first I want to make every segments with text outside of segments. But found to calculate the position of text is a little hard. coz texts always have different length. Be lazy… :P

Comments

Popular posts from this blog

A wired issue of MediaPlayer on Android

How to add pre-receive hook in GitLab

Problem when using Jackson json parser in Android.