Home | History | Annotate | Download | only in widget
      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.android.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 
     71         public Slice(long value, int color) {
     72             this.value = value;
     73             this.paint = buildFillPaint(color, getResources());
     74         }
     75     }
     76 
     77     public PieChartView(Context context) {
     78         this(context, null);
     79     }
     80 
     81     public PieChartView(Context context, AttributeSet attrs) {
     82         this(context, attrs, 0);
     83     }
     84 
     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 
     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 
    113     public void setOriginAngle(int originAngle) {
    114         mOriginAngle = originAngle;
    115     }
    116 
    117     public void addSlice(long value, int color) {
    118         mSlices.add(new Slice(value, color));
    119     }
    120 
    121     public void removeAllSlices() {
    122         mSlices.clear();
    123     }
    124 
    125     @Override
    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 
    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