| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.widget; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.Paint.Style; |
| import android.graphics.Path; |
| import android.graphics.Path.Direction; |
| import android.graphics.RadialGradient; |
| import android.graphics.RectF; |
| import android.graphics.Shader.TileMode; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.View; |
| |
| import com.google.android.collect.Lists; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Pie chart with multiple items. |
| */ |
| public class PieChartView extends View { |
| public static final String TAG = "PieChartView"; |
| public static final boolean LOGD = false; |
| |
| private static final boolean FILL_GRADIENT = false; |
| |
| private ArrayList<Slice> mSlices = Lists.newArrayList(); |
| |
| private int mOriginAngle; |
| private Matrix mMatrix = new Matrix(); |
| |
| private Paint mPaintOutline = new Paint(); |
| |
| private Path mPathSide = new Path(); |
| private Path mPathSideOutline = new Path(); |
| |
| private Path mPathOutline = new Path(); |
| |
| private int mSideWidth; |
| |
| public class Slice { |
| public long value; |
| |
| public Path path = new Path(); |
| public Path pathSide = new Path(); |
| public Path pathOutline = new Path(); |
| |
| public Paint paint; |
| |
| public Slice(long value, int color) { |
| this.value = value; |
| this.paint = buildFillPaint(color, getResources()); |
| } |
| } |
| |
| public PieChartView(Context context) { |
| this(context, null); |
| } |
| |
| public PieChartView(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public PieChartView(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| mPaintOutline.setColor(Color.BLACK); |
| mPaintOutline.setStyle(Style.STROKE); |
| mPaintOutline.setStrokeWidth(3f * getResources().getDisplayMetrics().density); |
| mPaintOutline.setAntiAlias(true); |
| |
| mSideWidth = (int) (20 * getResources().getDisplayMetrics().density); |
| |
| setWillNotDraw(false); |
| } |
| |
| private static Paint buildFillPaint(int color, Resources res) { |
| final Paint paint = new Paint(); |
| |
| paint.setColor(color); |
| paint.setStyle(Style.FILL_AND_STROKE); |
| paint.setAntiAlias(true); |
| |
| if (FILL_GRADIENT) { |
| final int width = (int) (280 * res.getDisplayMetrics().density); |
| paint.setShader(new RadialGradient(0, 0, width, color, darken(color), TileMode.MIRROR)); |
| } |
| |
| return paint; |
| } |
| |
| public void setOriginAngle(int originAngle) { |
| mOriginAngle = originAngle; |
| } |
| |
| public void addSlice(long value, int color) { |
| mSlices.add(new Slice(value, color)); |
| } |
| |
| public void removeAllSlices() { |
| mSlices.clear(); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| final float centerX = getWidth() / 2; |
| final float centerY = getHeight() / 2; |
| |
| mMatrix.reset(); |
| mMatrix.postScale(0.665f, 0.95f, centerX, centerY); |
| mMatrix.postRotate(-40, centerX, centerY); |
| |
| generatePath(); |
| } |
| |
| public void generatePath() { |
| if (LOGD) Log.d(TAG, "generatePath()"); |
| |
| long total = 0; |
| for (Slice slice : mSlices) { |
| slice.path.reset(); |
| slice.pathSide.reset(); |
| slice.pathOutline.reset(); |
| total += slice.value; |
| } |
| |
| mPathSide.reset(); |
| mPathSideOutline.reset(); |
| mPathOutline.reset(); |
| |
| // bail when not enough stats to render |
| if (total == 0) { |
| invalidate(); |
| return; |
| } |
| |
| final int width = getWidth(); |
| final int height = getHeight(); |
| |
| final RectF rect = new RectF(0, 0, width, height); |
| final RectF rectSide = new RectF(); |
| rectSide.set(rect); |
| rectSide.offset(-mSideWidth, 0); |
| |
| mPathSide.addOval(rectSide, Direction.CW); |
| mPathSideOutline.addOval(rectSide, Direction.CW); |
| mPathOutline.addOval(rect, Direction.CW); |
| |
| int startAngle = mOriginAngle; |
| for (Slice slice : mSlices) { |
| final int sweepAngle = (int) (slice.value * 360 / total); |
| final int endAngle = startAngle + sweepAngle; |
| |
| final float startAngleMod = startAngle % 360; |
| final float endAngleMod = endAngle % 360; |
| final boolean startSideVisible = startAngleMod > 90 && startAngleMod < 270; |
| final boolean endSideVisible = endAngleMod > 90 && endAngleMod < 270; |
| |
| // draw slice |
| slice.path.moveTo(rect.centerX(), rect.centerY()); |
| slice.path.arcTo(rect, startAngle, sweepAngle); |
| slice.path.lineTo(rect.centerX(), rect.centerY()); |
| |
| if (startSideVisible || endSideVisible) { |
| |
| // when start is beyond horizon, push until visible |
| final float startAngleSide = startSideVisible ? startAngle : 450; |
| final float endAngleSide = endSideVisible ? endAngle : 270; |
| final float sweepAngleSide = endAngleSide - startAngleSide; |
| |
| // draw slice side |
| slice.pathSide.moveTo(rect.centerX(), rect.centerY()); |
| slice.pathSide.arcTo(rect, startAngleSide, 0); |
| slice.pathSide.rLineTo(-mSideWidth, 0); |
| slice.pathSide.arcTo(rectSide, startAngleSide, sweepAngleSide); |
| slice.pathSide.rLineTo(mSideWidth, 0); |
| slice.pathSide.arcTo(rect, endAngleSide, -sweepAngleSide); |
| } |
| |
| // draw slice outline |
| slice.pathOutline.moveTo(rect.centerX(), rect.centerY()); |
| slice.pathOutline.arcTo(rect, startAngle, 0); |
| if (startSideVisible) { |
| slice.pathOutline.rLineTo(-mSideWidth, 0); |
| } |
| slice.pathOutline.moveTo(rect.centerX(), rect.centerY()); |
| slice.pathOutline.arcTo(rect, startAngle + sweepAngle, 0); |
| if (endSideVisible) { |
| slice.pathOutline.rLineTo(-mSideWidth, 0); |
| } |
| |
| startAngle += sweepAngle; |
| } |
| |
| invalidate(); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| |
| canvas.concat(mMatrix); |
| |
| for (Slice slice : mSlices) { |
| canvas.drawPath(slice.pathSide, slice.paint); |
| } |
| canvas.drawPath(mPathSideOutline, mPaintOutline); |
| |
| for (Slice slice : mSlices) { |
| canvas.drawPath(slice.path, slice.paint); |
| canvas.drawPath(slice.pathOutline, mPaintOutline); |
| } |
| canvas.drawPath(mPathOutline, mPaintOutline); |
| } |
| |
| public static int darken(int color) { |
| float[] hsv = new float[3]; |
| Color.colorToHSV(color, hsv); |
| hsv[2] /= 2; |
| hsv[1] /= 2; |
| return Color.HSVToColor(hsv); |
| } |
| |
| } |