Home | History | Annotate | Download | only in graph
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.settingslib.graph;
     16 
     17 import android.annotation.Nullable;
     18 import android.content.Context;
     19 import android.content.res.Resources;
     20 import android.graphics.Canvas;
     21 import android.graphics.CornerPathEffect;
     22 import android.graphics.DashPathEffect;
     23 import android.graphics.LinearGradient;
     24 import android.graphics.Paint;
     25 import android.graphics.Paint.Cap;
     26 import android.graphics.Paint.Join;
     27 import android.graphics.Paint.Style;
     28 import android.graphics.Path;
     29 import android.graphics.Shader.TileMode;
     30 import android.graphics.drawable.Drawable;
     31 import android.util.AttributeSet;
     32 import android.util.SparseIntArray;
     33 import android.util.TypedValue;
     34 import android.view.View;
     35 import com.android.settingslib.R;
     36 
     37 public class UsageGraph extends View {
     38 
     39     private static final int PATH_DELIM = -1;
     40 
     41     private final Paint mLinePaint;
     42     private final Paint mFillPaint;
     43     private final Paint mDottedPaint;
     44 
     45     private final Drawable mDivider;
     46     private final Drawable mTintedDivider;
     47     private final int mDividerSize;
     48 
     49     private final Path mPath = new Path();
     50 
     51     // Paths in coordinates they are passed in.
     52     private final SparseIntArray mPaths = new SparseIntArray();
     53     // Paths in local coordinates for drawing.
     54     private final SparseIntArray mLocalPaths = new SparseIntArray();
     55     private final int mCornerRadius;
     56 
     57     private int mAccentColor;
     58     private boolean mShowProjection;
     59     private boolean mProjectUp;
     60 
     61     private float mMaxX = 100;
     62     private float mMaxY = 100;
     63 
     64     private float mMiddleDividerLoc = .5f;
     65     private int mMiddleDividerTint = -1;
     66     private int mTopDividerTint = -1;
     67 
     68     public UsageGraph(Context context, @Nullable AttributeSet attrs) {
     69         super(context, attrs);
     70         final Resources resources = context.getResources();
     71 
     72         mLinePaint = new Paint();
     73         mLinePaint.setStyle(Style.STROKE);
     74         mLinePaint.setStrokeCap(Cap.ROUND);
     75         mLinePaint.setStrokeJoin(Join.ROUND);
     76         mLinePaint.setAntiAlias(true);
     77         mCornerRadius = resources.getDimensionPixelSize(R.dimen.usage_graph_line_corner_radius);
     78         mLinePaint.setPathEffect(new CornerPathEffect(mCornerRadius));
     79         mLinePaint.setStrokeWidth(resources.getDimensionPixelSize(R.dimen.usage_graph_line_width));
     80 
     81         mFillPaint = new Paint(mLinePaint);
     82         mFillPaint.setStyle(Style.FILL);
     83 
     84         mDottedPaint = new Paint(mLinePaint);
     85         mDottedPaint.setStyle(Style.STROKE);
     86         float dots = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_size);
     87         float interval = resources.getDimensionPixelSize(R.dimen.usage_graph_dot_interval);
     88         mDottedPaint.setStrokeWidth(dots * 3);
     89         mDottedPaint.setPathEffect(new DashPathEffect(new float[] {dots, interval}, 0));
     90         mDottedPaint.setColor(context.getColor(R.color.usage_graph_dots));
     91 
     92         TypedValue v = new TypedValue();
     93         context.getTheme().resolveAttribute(com.android.internal.R.attr.listDivider, v, true);
     94         mDivider = context.getDrawable(v.resourceId);
     95         mTintedDivider = context.getDrawable(v.resourceId);
     96         mDividerSize = resources.getDimensionPixelSize(R.dimen.usage_graph_divider_size);
     97     }
     98 
     99     void clearPaths() {
    100         mPaths.clear();
    101     }
    102 
    103     void setMax(int maxX, int maxY) {
    104         mMaxX = maxX;
    105         mMaxY = maxY;
    106     }
    107 
    108     void setDividerLoc(int height) {
    109         mMiddleDividerLoc = 1 - height / mMaxY;
    110     }
    111 
    112     void setDividerColors(int middleColor, int topColor) {
    113         mMiddleDividerTint = middleColor;
    114         mTopDividerTint = topColor;
    115     }
    116 
    117     public void addPath(SparseIntArray points) {
    118         for (int i = 0; i < points.size(); i++) {
    119             mPaths.put(points.keyAt(i), points.valueAt(i));
    120         }
    121         mPaths.put(points.keyAt(points.size() - 1) + 1, PATH_DELIM);
    122         calculateLocalPaths();
    123         postInvalidate();
    124     }
    125 
    126     void setAccentColor(int color) {
    127         mAccentColor = color;
    128         mLinePaint.setColor(mAccentColor);
    129         updateGradient();
    130         postInvalidate();
    131     }
    132 
    133     void setShowProjection(boolean showProjection, boolean projectUp) {
    134         mShowProjection = showProjection;
    135         mProjectUp = projectUp;
    136         postInvalidate();
    137     }
    138 
    139     @Override
    140     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    141         super.onSizeChanged(w, h, oldw, oldh);
    142         updateGradient();
    143         calculateLocalPaths();
    144     }
    145 
    146     private void calculateLocalPaths() {
    147         if (getWidth() == 0) return;
    148         mLocalPaths.clear();
    149         int pendingXLoc = 0;
    150         int pendingYLoc = PATH_DELIM;
    151         for (int i = 0; i < mPaths.size(); i++) {
    152             int x = mPaths.keyAt(i);
    153             int y = mPaths.valueAt(i);
    154             if (y == PATH_DELIM) {
    155                 if (i == mPaths.size() - 1 && pendingYLoc != PATH_DELIM) {
    156                     // Connect to the end of the graph.
    157                     mLocalPaths.put(pendingXLoc, pendingYLoc);
    158                 }
    159                 // Clear out any pending points.
    160                 pendingYLoc = PATH_DELIM;
    161                 mLocalPaths.put(pendingXLoc + 1, PATH_DELIM);
    162             } else {
    163                 final int lx = getX(x);
    164                 final int ly = getY(y);
    165                 pendingXLoc = lx;
    166                 if (mLocalPaths.size() > 0) {
    167                     int lastX = mLocalPaths.keyAt(mLocalPaths.size() - 1);
    168                     int lastY = mLocalPaths.valueAt(mLocalPaths.size() - 1);
    169                     if (lastY != PATH_DELIM && !hasDiff(lastX, lx) && !hasDiff(lastY, ly)) {
    170                         pendingYLoc = ly;
    171                         continue;
    172                     }
    173                 }
    174                 mLocalPaths.put(lx, ly);
    175             }
    176         }
    177     }
    178 
    179     private boolean hasDiff(int x1, int x2) {
    180         return Math.abs(x2 - x1) >= mCornerRadius;
    181     }
    182 
    183     private int getX(float x) {
    184         return (int) (x / mMaxX * getWidth());
    185     }
    186 
    187     private int getY(float y) {
    188         return (int) (getHeight() * (1 - (y / mMaxY)));
    189     }
    190 
    191     private void updateGradient() {
    192         mFillPaint.setShader(new LinearGradient(0, 0, 0, getHeight(),
    193                 getColor(mAccentColor, .2f), 0, TileMode.CLAMP));
    194     }
    195 
    196     private int getColor(int color, float alphaScale) {
    197         return (color & (((int) (0xff * alphaScale) << 24) | 0xffffff));
    198     }
    199 
    200     @Override
    201     protected void onDraw(Canvas canvas) {
    202         // Draw lines across the top, middle, and bottom.
    203         if (mMiddleDividerLoc != 0) {
    204             drawDivider(0, canvas, mTopDividerTint);
    205         }
    206         drawDivider((int) ((canvas.getHeight() - mDividerSize) * mMiddleDividerLoc), canvas,
    207                 mMiddleDividerTint);
    208         drawDivider(canvas.getHeight() - mDividerSize, canvas, -1);
    209 
    210         if (mLocalPaths.size() == 0) {
    211             return;
    212         }
    213         if (mShowProjection) {
    214             drawProjection(canvas);
    215         }
    216         drawFilledPath(canvas);
    217         drawLinePath(canvas);
    218     }
    219 
    220     private void drawProjection(Canvas canvas) {
    221         mPath.reset();
    222         int x = mLocalPaths.keyAt(mLocalPaths.size() - 2);
    223         int y = mLocalPaths.valueAt(mLocalPaths.size() - 2);
    224         mPath.moveTo(x, y);
    225         mPath.lineTo(canvas.getWidth(), mProjectUp ? 0 : canvas.getHeight());
    226         canvas.drawPath(mPath, mDottedPaint);
    227     }
    228 
    229     private void drawLinePath(Canvas canvas) {
    230         mPath.reset();
    231         mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0));
    232         for (int i = 1; i < mLocalPaths.size(); i++) {
    233             int x = mLocalPaths.keyAt(i);
    234             int y = mLocalPaths.valueAt(i);
    235             if (y == PATH_DELIM) {
    236                 if (++i < mLocalPaths.size()) {
    237                     mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i));
    238                 }
    239             } else {
    240                 mPath.lineTo(x, y);
    241             }
    242         }
    243         canvas.drawPath(mPath, mLinePaint);
    244     }
    245 
    246     private void drawFilledPath(Canvas canvas) {
    247         mPath.reset();
    248         float lastStartX = mLocalPaths.keyAt(0);
    249         mPath.moveTo(mLocalPaths.keyAt(0), mLocalPaths.valueAt(0));
    250         for (int i = 1; i < mLocalPaths.size(); i++) {
    251             int x = mLocalPaths.keyAt(i);
    252             int y = mLocalPaths.valueAt(i);
    253             if (y == PATH_DELIM) {
    254                 mPath.lineTo(mLocalPaths.keyAt(i - 1), getHeight());
    255                 mPath.lineTo(lastStartX, getHeight());
    256                 mPath.close();
    257                 if (++i < mLocalPaths.size()) {
    258                     lastStartX = mLocalPaths.keyAt(i);
    259                     mPath.moveTo(mLocalPaths.keyAt(i), mLocalPaths.valueAt(i));
    260                 }
    261             } else {
    262                 mPath.lineTo(x, y);
    263             }
    264         }
    265         canvas.drawPath(mPath, mFillPaint);
    266     }
    267 
    268     private void drawDivider(int y, Canvas canvas, int tintColor) {
    269         Drawable d = mDivider;
    270         if (tintColor != -1) {
    271             mTintedDivider.setTint(tintColor);
    272             d = mTintedDivider;
    273         }
    274         d.setBounds(0, y, canvas.getWidth(), y + mDividerSize);
    275         d.draw(canvas);
    276     }
    277 }
    278