/* * 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.common.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); } }