Index: third_party/android_data_chart/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java |
diff --git a/third_party/android_data_chart/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java b/third_party/android_data_chart/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..33a9e6b13005f9cf0a3d130215fda8cb76f54a23 |
--- /dev/null |
+++ b/third_party/android_data_chart/java/src/org/chromium/third_party/android/datausagechart/ChartNetworkSeriesView.java |
@@ -0,0 +1,314 @@ |
+/* |
+ * Copyright (C) 2013 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 org.chromium.third_party.android.datausagechart; |
+ |
+import android.content.Context; |
+import android.content.res.TypedArray; |
+import android.graphics.Canvas; |
+import android.graphics.Color; |
+import android.graphics.DashPathEffect; |
+import android.graphics.Paint; |
+import android.graphics.Paint.Style; |
+import android.graphics.Path; |
+import android.graphics.RectF; |
+import android.util.AttributeSet; |
+import android.util.Log; |
+import android.view.View; |
+ |
+import org.chromium.third_party.android.R; |
+ |
+/** |
+ * {@link NetworkStatsHistory} series to render inside a {@link ChartView}, |
+ * using {@link ChartAxis} to map into screen coordinates. |
+ * This is derived from com.android.settings.widget.ChartNetworkSeriesView. |
+ */ |
+public class ChartNetworkSeriesView extends View { |
+ private static final String TAG = "ChartNetworkSeriesView"; |
+ private static final boolean LOGD = false; |
+ |
+ private ChartAxis mHoriz; |
+ private ChartAxis mVert; |
+ |
+ private Paint mPaintStroke; |
+ private Paint mPaintFill; |
+ private Paint mPaintFillSecondary; |
+ private Paint mPaintEstimate; |
+ |
+ private NetworkStatsHistory mStats; |
+ |
+ private Path mPathStroke; |
+ private Path mPathFill; |
+ private Path mPathEstimate; |
+ |
+ private long mStart; |
+ private long mEnd; |
+ |
+ private long mPrimaryLeft; |
+ private long mPrimaryRight; |
+ |
+ /** Series will be extended to reach this end time. */ |
+ private long mEndTime = Long.MIN_VALUE; |
+ |
+ private boolean mPathValid = false; |
+ private boolean mEstimateVisible = false; |
+ |
+ private long mMax; |
+ private long mMaxEstimate; |
+ |
+ public ChartNetworkSeriesView(Context context) { |
+ this(context, null, 0); |
+ } |
+ |
+ public ChartNetworkSeriesView(Context context, AttributeSet attrs) { |
+ this(context, attrs, 0); |
+ } |
+ |
+ public ChartNetworkSeriesView(Context context, AttributeSet attrs, int defStyle) { |
+ super(context, attrs, defStyle); |
+ |
+ final TypedArray a = context.obtainStyledAttributes( |
+ attrs, R.styleable.ChartNetworkSeriesView, defStyle, 0); |
+ |
+ final int stroke = a.getColor(R.styleable.ChartNetworkSeriesView_strokeColor, Color.RED); |
+ final int fill = a.getColor(R.styleable.ChartNetworkSeriesView_fillColor, Color.RED); |
+ final int fillSecondary = a.getColor( |
+ R.styleable.ChartNetworkSeriesView_fillColorSecondary, Color.RED); |
+ |
+ setChartColor(stroke, fill, fillSecondary); |
+ setWillNotDraw(false); |
+ |
+ a.recycle(); |
+ |
+ mPathStroke = new Path(); |
+ mPathFill = new Path(); |
+ mPathEstimate = new Path(); |
+ } |
+ |
+ void init(ChartAxis horiz, ChartAxis vert) { |
+ if (horiz == null) throw new NullPointerException("missing horiz"); |
+ if (vert == null) throw new NullPointerException("missing vert"); |
+ mHoriz = horiz; |
+ mVert = vert; |
+ } |
+ |
+ public void setChartColor(int stroke, int fill, int fillSecondary) { |
+ mPaintStroke = new Paint(); |
+ mPaintStroke.setStrokeWidth(4.0f * getResources().getDisplayMetrics().density); |
+ mPaintStroke.setColor(stroke); |
+ mPaintStroke.setStyle(Style.STROKE); |
+ mPaintStroke.setAntiAlias(true); |
+ |
+ mPaintFill = new Paint(); |
+ mPaintFill.setColor(fill); |
+ mPaintFill.setStyle(Style.FILL); |
+ mPaintFill.setAntiAlias(true); |
+ |
+ mPaintFillSecondary = new Paint(); |
+ mPaintFillSecondary.setColor(fillSecondary); |
+ mPaintFillSecondary.setStyle(Style.FILL); |
+ mPaintFillSecondary.setAntiAlias(true); |
+ |
+ mPaintEstimate = new Paint(); |
+ mPaintEstimate.setStrokeWidth(3.0f); |
+ mPaintEstimate.setColor(fillSecondary); |
+ mPaintEstimate.setStyle(Style.STROKE); |
+ mPaintEstimate.setAntiAlias(true); |
+ mPaintEstimate.setPathEffect(new DashPathEffect(new float[] { 10, 10 }, 1)); |
+ } |
+ |
+ public void bindNetworkStats(NetworkStatsHistory stats) { |
+ mStats = stats; |
+ invalidatePath(); |
+ invalidate(); |
+ } |
+ |
+ public void setBounds(long start, long end) { |
+ mStart = start; |
+ mEnd = end; |
+ } |
+ |
+ /** |
+ * Set the range to paint with {@link #mPaintFill}, leaving the remaining |
+ * area to be painted with {@link #mPaintFillSecondary}. |
+ */ |
+ public void setPrimaryRange(long left, long right) { |
+ mPrimaryLeft = left; |
+ mPrimaryRight = right; |
+ invalidate(); |
+ } |
+ |
+ public void invalidatePath() { |
+ mPathValid = false; |
+ mMax = 0; |
+ invalidate(); |
+ } |
+ |
+ /** |
+ * Erase any existing {@link Path} and generate series outline based on |
+ * currently bound {@link NetworkStatsHistory} data. |
+ */ |
+ private void generatePath() { |
+ if (LOGD) Log.d(TAG, "generatePath()"); |
+ |
+ mMax = 0; |
+ mPathStroke.reset(); |
+ mPathFill.reset(); |
+ mPathEstimate.reset(); |
+ mPathValid = true; |
+ |
+ // bail when not enough stats to render |
+ if (mStats == null || mStats.size() < 2) { |
+ return; |
+ } |
+ |
+ final int height = getHeight(); |
+ |
+ boolean started = false; |
+ float lastX = 0; |
+ float lastY = height; |
+ long lastTime = mHoriz.convertToValue(lastX); |
+ |
+ // move into starting position |
+ mPathStroke.moveTo(lastX, lastY); |
+ mPathFill.moveTo(lastX, lastY); |
+ |
+ // TODO(bengr): count fractional data from first bucket crossing start; |
+ // currently it only accepts first full bucket. |
+ |
+ long totalData = 0; |
+ |
+ NetworkStatsHistory.Entry entry = null; |
+ |
+ final int start = mStats.getIndexBefore(mStart); |
+ final int end = mStats.getIndexAfter(mEnd); |
+ for (int i = start; i <= end; i++) { |
+ entry = mStats.getValues(i, entry); |
+ |
+ final long startTime = entry.bucketStart; |
+ final long endTime = startTime + entry.bucketDuration; |
+ |
+ final float startX = mHoriz.convertToPoint(startTime); |
+ final float endX = mHoriz.convertToPoint(endTime); |
+ |
+ // skip until we find first stats on screen |
+ if (endX < 0) continue; |
+ |
+ // increment by current bucket total |
+ totalData += entry.rxBytes + entry.txBytes; |
+ |
+ final float startY = lastY; |
+ final float endY = mVert.convertToPoint(totalData); |
+ |
+ if (lastTime != startTime) { |
+ // gap in buckets; line to start of current bucket |
+ mPathStroke.lineTo(startX, startY); |
+ mPathFill.lineTo(startX, startY); |
+ } |
+ |
+ // always draw to end of current bucket |
+ mPathStroke.lineTo(endX, endY); |
+ mPathFill.lineTo(endX, endY); |
+ |
+ lastX = endX; |
+ lastY = endY; |
+ lastTime = endTime; |
+ } |
+ |
+ // when data falls short, extend to requested end time |
+ if (lastTime < mEndTime) { |
+ lastX = mHoriz.convertToPoint(mEndTime); |
+ |
+ mPathStroke.lineTo(lastX, lastY); |
+ mPathFill.lineTo(lastX, lastY); |
+ } |
+ |
+ if (LOGD) { |
+ final RectF bounds = new RectF(); |
+ mPathFill.computeBounds(bounds, true); |
+ Log.d(TAG, "onLayout() rendered with bounds=" + bounds.toString() + " and totalData=" |
+ + totalData); |
+ } |
+ |
+ // drop to bottom of graph from current location |
+ mPathFill.lineTo(lastX, height); |
+ mPathFill.lineTo(0, height); |
+ |
+ mMax = totalData; |
+ |
+ invalidate(); |
+ } |
+ |
+ public void setEndTime(long endTime) { |
+ mEndTime = endTime; |
+ } |
+ |
+ public void setEstimateVisible(boolean estimateVisible) { |
+ mEstimateVisible = false; |
+ invalidate(); |
+ } |
+ |
+ public long getMaxEstimate() { |
+ return mMaxEstimate; |
+ } |
+ |
+ public long getMaxVisible() { |
+ final long maxVisible = mEstimateVisible ? mMaxEstimate : mMax; |
+ if (maxVisible <= 0 && mStats != null) { |
+ // haven't generated path yet; fall back to raw data |
+ final NetworkStatsHistory.Entry entry = mStats.getValues(mStart, mEnd, null); |
+ return entry.rxBytes + entry.txBytes; |
+ } else { |
+ return maxVisible; |
+ } |
+ } |
+ |
+ @Override |
+ protected void onDraw(Canvas canvas) { |
+ int save; |
+ |
+ if (!mPathValid) { |
+ generatePath(); |
+ } |
+ |
+ final float primaryLeftPoint = mHoriz.convertToPoint(mPrimaryLeft); |
+ final float primaryRightPoint = mHoriz.convertToPoint(mPrimaryRight); |
+ |
+ if (mEstimateVisible) { |
+ save = canvas.save(); |
+ canvas.clipRect(0, 0, getWidth(), getHeight()); |
+ canvas.drawPath(mPathEstimate, mPaintEstimate); |
+ canvas.restoreToCount(save); |
+ } |
+ |
+ save = canvas.save(); |
+ canvas.clipRect(0, 0, primaryLeftPoint, getHeight()); |
+ canvas.drawPath(mPathFill, mPaintFillSecondary); |
+ canvas.restoreToCount(save); |
+ |
+ save = canvas.save(); |
+ canvas.clipRect(primaryRightPoint, 0, getWidth(), getHeight()); |
+ canvas.drawPath(mPathFill, mPaintFillSecondary); |
+ canvas.restoreToCount(save); |
+ |
+ save = canvas.save(); |
+ canvas.clipRect(primaryLeftPoint, 0, primaryRightPoint, getHeight()); |
+ canvas.drawPath(mPathFill, mPaintFill); |
+ canvas.drawPath(mPathStroke, mPaintStroke); |
+ canvas.restoreToCount(save); |
+ |
+ } |
+} |