• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.mobileer.oboetester;
2 
3 import android.content.Context;
4 import android.content.res.TypedArray;
5 import android.graphics.Canvas;
6 import android.graphics.Color;
7 import android.graphics.Paint;
8 import android.util.AttributeSet;
9 import android.view.View;
10 
11 import java.util.ArrayList;
12 
13 /**
14  * Draw a chart with multiple traces
15  */
16 public class MultiLineChart extends View {
17     public static final int NUM_DATA_VALUES = 512;
18     private Paint mWavePaint;
19     private Paint mCursorPaint;
20     private int mBackgroundColor = 0xFFF0F0F0;
21     private int mLineColor = Color.RED;
22     private Paint mBackgroundPaint;
23     float[] mVertices = new float[4];
24 
25     CircularFloatArray mXData = new CircularFloatArray(NUM_DATA_VALUES);
26     private ArrayList<Trace> mTraceList = new ArrayList<>();
27 
MultiLineChart(Context context)28     public MultiLineChart(Context context) {
29         super(context);
30         init(null, 0);
31     }
32 
MultiLineChart(Context context, AttributeSet attrs)33     public MultiLineChart(Context context, AttributeSet attrs) {
34         super(context, attrs);
35         init(attrs, 0);
36     }
37 
MultiLineChart(Context context, AttributeSet attrs, int defStyle)38     public MultiLineChart(Context context, AttributeSet attrs, int defStyle) {
39         super(context, attrs, defStyle);
40         init(attrs, defStyle);
41     }
42 
init(AttributeSet attrs, int defStyle)43     private void init(AttributeSet attrs, int defStyle) {
44         // Load attributes
45         final TypedArray a = getContext().obtainStyledAttributes(
46                 attrs, R.styleable.MultiLineChart, defStyle, 0);
47 
48         mBackgroundColor = a.getColor(
49                 R.styleable.MultiLineChart_backgroundColor,
50                 mBackgroundColor);
51         mLineColor = a.getColor(
52                 R.styleable.MultiLineChart_backgroundColor,
53                 mLineColor);
54 
55         a.recycle();
56 
57         mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
58         mWavePaint.setColor(mLineColor);
59 
60         mCursorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
61         mCursorPaint.setColor(Color.RED);
62         mCursorPaint.setStrokeWidth(3.0f);
63 
64         mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
65         mBackgroundPaint.setColor(mBackgroundColor);
66         mBackgroundPaint.setStyle(Paint.Style.FILL);
67     }
68 
69     @Override
onDraw(Canvas canvas)70     protected void onDraw(Canvas canvas) {
71         super.onDraw(canvas);
72 
73         canvas.drawRect(0.0f, 0.0f, getWidth(),
74                 getHeight(), mBackgroundPaint);
75 
76         for (Trace trace : mTraceList) {
77             drawTrace(canvas, trace);
78         }
79     }
80 
drawTrace(Canvas canvas, Trace trace)81     void drawTrace(Canvas canvas, Trace trace) {
82         // Determine bounds and XY conversion.
83         int numPoints = mXData.size();
84         if (numPoints < 2) return;
85         // Allocate array for polyline.
86         int arraySize = (numPoints - 1) * 4;
87         if (arraySize > mVertices.length) {
88             mVertices = new float[arraySize];
89         }
90         // Setup scaling.
91         float previousX = 0.0f;
92         float previousY = 0.0f;
93         float xMax = getXData(1);
94         float xRange = xMax - getXData(numPoints);
95         float yMin =  trace.getMin();
96         float yRange = trace.getMax() - yMin;
97         float width = getWidth();
98         float height = getHeight();
99         float xScaler =  width / xRange;
100         float yScaler =  height / yRange;
101         // Iterate through the available data.
102         int vertexIndex = 0;
103         for (int i = 1; i < numPoints; i++) {
104             float xData = getXData(i);
105             float yData = trace.get(i);
106             float xPos = width - ((xMax - xData) * xScaler);
107             float yPos = height - ((yData - yMin) * yScaler);
108             if (i > 1) {
109                 // Each line segment requires 4 values!
110                 mVertices[vertexIndex++] = previousX;
111                 mVertices[vertexIndex++] = previousY;
112                 mVertices[vertexIndex++] = xPos;
113                 mVertices[vertexIndex++] = yPos;
114             }
115             previousX = xPos;
116             previousY = yPos;
117         }
118         canvas.drawLines(mVertices, 0, vertexIndex, trace.paint);
119     }
120 
getXData(int i)121     public float getXData(int i) {
122         return mXData.get(i);
123     }
124 
createTrace(String name, int color, float min, float max)125     public MultiLineChart.Trace createTrace(String name, int color, float min, float max) {
126         Trace trace = new Trace(name, color, NUM_DATA_VALUES, min, max);
127         mTraceList.add(trace);
128         return trace;
129     }
130 
update()131     public void update() {
132         post(new Runnable() {
133             public void run() {
134                 postInvalidate();
135             }
136         });
137     }
138 
addX(float value)139     public void addX(float value) {
140         mXData.add(value);
141     }
142 
reset()143     public void reset() {
144         mXData.clear();
145         for (Trace trace : mTraceList) {
146             trace.reset();
147         }
148     }
149 
150     public static class Trace {
151         private final String mName;
152         public Paint paint;
153         protected float mMin;
154         protected float mMax;
155         protected CircularFloatArray mData;
156 
Trace(String name, int color, int numValues, float min, float max)157         private Trace(String name, int color, int numValues, float min, float max) {
158             mName = name;
159             mMin = min;
160             mMax = max;
161             mData = new CircularFloatArray(numValues);
162 
163             paint = new Paint(Paint.ANTI_ALIAS_FLAG);
164             paint.setColor(color);
165             paint.setStrokeWidth(3.0f);
166         }
167 
reset()168         public void reset() {
169             mData.clear();
170         }
171 
add(float value)172         public void add(float value) {
173             mData.add(value);
174             // Take the hit here instead of when drawing.
175             mData.add(Math.min(mMax, Math.max(mMin, value)));
176         }
177 
size()178         public int size() {
179             return mData.size();
180         }
181 
182         /**
183          * Fetch a previous value. A delayIndex of 1 will return the most recently written value.
184          * A delayIndex of 2 will return the previously written value;
185          * @param delayIndex positive index of previously written data
186          * @return old value
187          */
get(int delayIndex)188         public float get(int delayIndex) {
189             return mData.get(delayIndex);
190         }
getMax()191         public float getMax() {
192             return mMax;
193         }
getMin()194         public float getMin() {
195             return mMin;
196         }
197 
setMin(float min)198         public void setMin(float min) {
199             mMin = min;
200         }
setMax(float max)201         public void setMax(float max) {
202             mMax = max;
203         }
204     }
205 
206     // Use explicit type for performance reasons.
207     private static class CircularFloatArray {
208         private float[] mData;
209         private int mIndexMask;
210         private int mCursor; // next location to be written
211 
CircularFloatArray(int numValuesPowerOf2)212         public CircularFloatArray(int numValuesPowerOf2) {
213             if ((numValuesPowerOf2 & (numValuesPowerOf2 - 1)) != 0) {
214                 throw new IllegalArgumentException("numValuesPowerOf2 not 2^N, was " + numValuesPowerOf2);
215             }
216             mData = new float[numValuesPowerOf2];
217             mIndexMask = numValuesPowerOf2 - 1;
218         }
219 
220         /**
221          * Add one value to the array.
222          * This may overwrite the oldest data.
223          * @param value
224          */
add(float value)225         public void add(float value) {
226             int index = mCursor & mIndexMask;
227             mData[index] = value;
228             mCursor++;
229         }
230 
231         /**
232          * Number of valid entries.
233          * @return
234          */
size()235         public int size() {
236             return Math.min(mCursor, mData.length);
237         }
238 
239         /**
240          * Fetch a previous value. A delayIndex of 1 will return the most recently written value.
241          * A delayIndex of 2 will return the previously written value;
242          * @param delayIndex positive index of previously written data
243          * @return old value
244          */
get(int delayIndex)245         public float get(int delayIndex) {
246             int index = (mCursor - delayIndex) & mIndexMask;
247             return mData[index];
248         }
249 
clear()250         public void clear() {
251             mCursor = 0;
252         }
253     }
254 }