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.
Spent several hours to customize own simple one. Learned how to use path and Path.arcTo.
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[i % 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 * (i + 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 

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.
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… 

Comments
Post a Comment