• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.widget;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Matrix;
24 import android.graphics.Paint;
25 import android.graphics.Paint.Style;
26 import android.graphics.Path;
27 import android.graphics.Path.Direction;
28 import android.graphics.RadialGradient;
29 import android.graphics.RectF;
30 import android.graphics.Shader.TileMode;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.view.View;
34 
35 import com.google.common.collect.Lists;
36 
37 import java.util.ArrayList;
38 
39 /**
40  * Pie chart with multiple items.
41  */
42 public class PieChartView extends View {
43     public static final String TAG = "PieChartView";
44     public static final boolean LOGD = false;
45 
46     private static final boolean FILL_GRADIENT = false;
47 
48     private ArrayList<Slice> mSlices = Lists.newArrayList();
49 
50     private int mOriginAngle;
51     private Matrix mMatrix = new Matrix();
52 
53     private Paint mPaintOutline = new Paint();
54 
55     private Path mPathSide = new Path();
56     private Path mPathSideOutline = new Path();
57 
58     private Path mPathOutline = new Path();
59 
60     private int mSideWidth;
61 
62     public class Slice {
63         public long value;
64 
65         public Path path = new Path();
66         public Path pathSide = new Path();
67         public Path pathOutline = new Path();
68 
69         public Paint paint;
70 
Slice(long value, int color)71         public Slice(long value, int color) {
72             this.value = value;
73             this.paint = buildFillPaint(color, getResources());
74         }
75     }
76 
PieChartView(Context context)77     public PieChartView(Context context) {
78         this(context, null);
79     }
80 
PieChartView(Context context, AttributeSet attrs)81     public PieChartView(Context context, AttributeSet attrs) {
82         this(context, attrs, 0);
83     }
84 
PieChartView(Context context, AttributeSet attrs, int defStyle)85     public PieChartView(Context context, AttributeSet attrs, int defStyle) {
86         super(context, attrs, defStyle);
87 
88         mPaintOutline.setColor(Color.BLACK);
89         mPaintOutline.setStyle(Style.STROKE);
90         mPaintOutline.setStrokeWidth(3f * getResources().getDisplayMetrics().density);
91         mPaintOutline.setAntiAlias(true);
92 
93         mSideWidth = (int) (20 * getResources().getDisplayMetrics().density);
94 
95         setWillNotDraw(false);
96     }
97 
buildFillPaint(int color, Resources res)98     private static Paint buildFillPaint(int color, Resources res) {
99         final Paint paint = new Paint();
100 
101         paint.setColor(color);
102         paint.setStyle(Style.FILL_AND_STROKE);
103         paint.setAntiAlias(true);
104 
105         if (FILL_GRADIENT) {
106             final int width = (int) (280 * res.getDisplayMetrics().density);
107             paint.setShader(new RadialGradient(0, 0, width, color, darken(color), TileMode.MIRROR));
108         }
109 
110         return paint;
111     }
112 
setOriginAngle(int originAngle)113     public void setOriginAngle(int originAngle) {
114         mOriginAngle = originAngle;
115     }
116 
addSlice(long value, int color)117     public void addSlice(long value, int color) {
118         mSlices.add(new Slice(value, color));
119     }
120 
removeAllSlices()121     public void removeAllSlices() {
122         mSlices.clear();
123     }
124 
125     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)126     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
127         final float centerX = getWidth() / 2;
128         final float centerY = getHeight() / 2;
129 
130         mMatrix.reset();
131         mMatrix.postScale(0.665f, 0.95f, centerX, centerY);
132         mMatrix.postRotate(-40, centerX, centerY);
133 
134         generatePath();
135     }
136 
generatePath()137     public void generatePath() {
138         if (LOGD) Log.d(TAG, "generatePath()");
139 
140         long total = 0;
141         for (Slice slice : mSlices) {
142             slice.path.reset();
143             slice.pathSide.reset();
144             slice.pathOutline.reset();
145             total += slice.value;
146         }
147 
148         mPathSide.reset();
149         mPathSideOutline.reset();
150         mPathOutline.reset();
151 
152         // bail when not enough stats to render
153         if (total == 0) {
154             invalidate();
155             return;
156         }
157 
158         final int width = getWidth();
159         final int height = getHeight();
160 
161         final RectF rect = new RectF(0, 0, width, height);
162         final RectF rectSide = new RectF();
163         rectSide.set(rect);
164         rectSide.offset(-mSideWidth, 0);
165 
166         mPathSide.addOval(rectSide, Direction.CW);
167         mPathSideOutline.addOval(rectSide, Direction.CW);
168         mPathOutline.addOval(rect, Direction.CW);
169 
170         int startAngle = mOriginAngle;
171         for (Slice slice : mSlices) {
172             final int sweepAngle = (int) (slice.value * 360 / total);
173             final int endAngle = startAngle + sweepAngle;
174 
175             final float startAngleMod = startAngle % 360;
176             final float endAngleMod = endAngle % 360;
177             final boolean startSideVisible = startAngleMod > 90 && startAngleMod < 270;
178             final boolean endSideVisible = endAngleMod > 90 && endAngleMod < 270;
179 
180             // draw slice
181             slice.path.moveTo(rect.centerX(), rect.centerY());
182             slice.path.arcTo(rect, startAngle, sweepAngle);
183             slice.path.lineTo(rect.centerX(), rect.centerY());
184 
185             if (startSideVisible || endSideVisible) {
186 
187                 // when start is beyond horizon, push until visible
188                 final float startAngleSide = startSideVisible ? startAngle : 450;
189                 final float endAngleSide = endSideVisible ? endAngle : 270;
190                 final float sweepAngleSide = endAngleSide - startAngleSide;
191 
192                 // draw slice side
193                 slice.pathSide.moveTo(rect.centerX(), rect.centerY());
194                 slice.pathSide.arcTo(rect, startAngleSide, 0);
195                 slice.pathSide.rLineTo(-mSideWidth, 0);
196                 slice.pathSide.arcTo(rectSide, startAngleSide, sweepAngleSide);
197                 slice.pathSide.rLineTo(mSideWidth, 0);
198                 slice.pathSide.arcTo(rect, endAngleSide, -sweepAngleSide);
199             }
200 
201             // draw slice outline
202             slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
203             slice.pathOutline.arcTo(rect, startAngle, 0);
204             if (startSideVisible) {
205                 slice.pathOutline.rLineTo(-mSideWidth, 0);
206             }
207             slice.pathOutline.moveTo(rect.centerX(), rect.centerY());
208             slice.pathOutline.arcTo(rect, startAngle + sweepAngle, 0);
209             if (endSideVisible) {
210                 slice.pathOutline.rLineTo(-mSideWidth, 0);
211             }
212 
213             startAngle += sweepAngle;
214         }
215 
216         invalidate();
217     }
218 
219     @Override
220     protected void onDraw(Canvas canvas) {
221 
222         canvas.concat(mMatrix);
223 
224         for (Slice slice : mSlices) {
225             canvas.drawPath(slice.pathSide, slice.paint);
226         }
227         canvas.drawPath(mPathSideOutline, mPaintOutline);
228 
229         for (Slice slice : mSlices) {
230             canvas.drawPath(slice.path, slice.paint);
231             canvas.drawPath(slice.pathOutline, mPaintOutline);
232         }
233         canvas.drawPath(mPathOutline, mPaintOutline);
234     }
235 
236     public static int darken(int color) {
237         float[] hsv = new float[3];
238         Color.colorToHSV(color, hsv);
239         hsv[2] /= 2;
240         hsv[1] /= 2;
241         return Color.HSVToColor(hsv);
242     }
243 
244 }
245