package com.github.pocmo.sensordashboard.ui; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; import android.hardware.SensorManager; import android.util.AttributeSet; import android.view.View; import com.github.pocmo.sensordashboard.R; import com.github.pocmo.sensordashboard.data.TagData; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * Created by juhani on 01/11/14. */ public class SensorGraphView extends View { private static final int CIRCLE_SIZE_ACCURACY_HIGH = 4; private static final int CIRCLE_SIZE_ACCURACY_MEDIUM = 10; private static final int CIRCLE_SIZE_ACCURACY_LOW = 20; private static final int CIRCLE_SIZE_DEFAULT = 4; private static final int MAX_DATA_SIZE = 300; private static final int NUM_DRAW_SENSOR = 20; private static final int NUM_RECT_PAINTS = 9; // FIXME don't hardcode 9 private final Paint[] rectPaints = new Paint[NUM_RECT_PAINTS]; private float zeroline = 0; private LinkedList<TagData> tags = new LinkedList<>(); private boolean[] drawSensors = new boolean[NUM_DRAW_SENSOR]; private Paint infoPaint; private Paint tagPaint; private ArrayList<Float>[] normalisedDataPoints; private ArrayList<Integer>[] dataPointsAccuracy; private ArrayList<Long>[] dataPointsTimeStamps; private String maxValueLabel = ""; private String minValueValue = ""; public SensorGraphView(Context context, AttributeSet attrs) { super(context, attrs); Resources res = context.getResources(); rectPaints[0] = new Paint(); rectPaints[0].setColor(res.getColor(R.color.graph_color_1)); rectPaints[1] = new Paint(); rectPaints[1].setColor(res.getColor(R.color.graph_color_2)); rectPaints[2] = new Paint(); rectPaints[2].setColor(res.getColor(R.color.graph_color_3)); rectPaints[3] = new Paint(); rectPaints[3].setColor(res.getColor(R.color.graph_color_4)); rectPaints[4] = new Paint(); rectPaints[4].setColor(res.getColor(R.color.graph_color_5)); rectPaints[5] = new Paint(); rectPaints[5].setColor(res.getColor(R.color.graph_color_6)); rectPaints[6] = new Paint(); rectPaints[6].setColor(res.getColor(R.color.graph_color_7)); rectPaints[7] = new Paint(); rectPaints[7].setColor(res.getColor(R.color.graph_color_8)); rectPaints[8] = new Paint(); rectPaints[8].setColor(res.getColor(R.color.graph_color_9)); infoPaint = new Paint(); infoPaint.setColor(res.getColor(R.color.graph_color_info)); infoPaint.setTextSize(48f); infoPaint.setAntiAlias(true); tagPaint = new Paint(); tagPaint.setColor(res.getColor(R.color.graph_color_info)); tagPaint.setAntiAlias(true); } public void setDrawSensors(boolean[] drawSensors) { this.drawSensors = drawSensors; invalidate(); } public void setNormalisedDataPoints(ArrayList<Float>[] normalisedDataPoints, ArrayList<Integer>[] dataPointsAccuracy, ArrayList<Long>[] dataPointsTimeStamps, LinkedList<TagData> tags) { this.tags = tags; this.dataPointsTimeStamps = dataPointsTimeStamps; for (int i = 0; i < this.dataPointsTimeStamps.length; ++i) { if (this.dataPointsTimeStamps[i].size() > MAX_DATA_SIZE) { List tmp = this.dataPointsTimeStamps[i].subList(this.dataPointsTimeStamps[i].size() - MAX_DATA_SIZE - 1, this.dataPointsTimeStamps[i].size() - 1); this.dataPointsTimeStamps[i] = new ArrayList<>(); this.dataPointsTimeStamps[i].addAll(tmp); } } this.normalisedDataPoints = normalisedDataPoints; for (int i = 0; i < this.normalisedDataPoints.length; ++i) { if (this.normalisedDataPoints[i].size() > MAX_DATA_SIZE) { List tmp = this.normalisedDataPoints[i].subList(this.normalisedDataPoints[i].size() - MAX_DATA_SIZE - 1, this.normalisedDataPoints[i].size() - 1); this.normalisedDataPoints[i] = new ArrayList<>(); this.normalisedDataPoints[i].addAll(tmp); } } this.dataPointsAccuracy = dataPointsAccuracy; for (int i = 0; i < this.dataPointsAccuracy.length; ++i) { if (this.dataPointsAccuracy[i].size() > MAX_DATA_SIZE) { List tmp = this.dataPointsAccuracy[i].subList(this.dataPointsAccuracy[i].size() - MAX_DATA_SIZE - 1, this.dataPointsAccuracy[i].size() - 1); this.dataPointsAccuracy[i] = new ArrayList<>(); this.dataPointsAccuracy[i].addAll(tmp); } } for (int i = 0; i < this.dataPointsAccuracy.length; ++i) { ArrayList<Integer> tmp = new ArrayList<>(); for (Integer integer : this.dataPointsAccuracy[i]) { tmp.add(dataPointAccuracyToDotSize(integer)); } this.dataPointsAccuracy[i] = tmp; } invalidate(); } public void setMaxValueLabel(String maxValue) { this.maxValueLabel = maxValue; } public void setMinValueLabel(String minValue) { this.minValueValue = minValue; } public void setZeroLine(float zeroline) { this.zeroline = zeroline; } public void addNewTag(TagData tagData) { this.tags.add(tagData); // allow max / 2 tags... that's probably enough if (this.tags.size() > MAX_DATA_SIZE / 2) { this.tags.removeFirst(); } } public void addNewDataPoint(float point, int accuracy, int index, long timestamp) { if (index >= normalisedDataPoints.length) { throw new ArrayIndexOutOfBoundsException("index too large!!"); } this.dataPointsTimeStamps[index].add(timestamp); if (this.dataPointsTimeStamps[index].size() > MAX_DATA_SIZE) { this.dataPointsTimeStamps[index].remove(0); } this.normalisedDataPoints[index].add(point); if (this.normalisedDataPoints[index].size() > MAX_DATA_SIZE) { this.normalisedDataPoints[index].remove(0); } this.dataPointsAccuracy[index].add(dataPointAccuracyToDotSize(accuracy)); if (this.dataPointsAccuracy[index].size() > MAX_DATA_SIZE) { this.dataPointsAccuracy[index].remove(0); } invalidate(); } private int dataPointAccuracyToDotSize(int accuracy) { switch (accuracy) { case SensorManager.SENSOR_STATUS_ACCURACY_HIGH: return CIRCLE_SIZE_ACCURACY_HIGH; case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM: return CIRCLE_SIZE_ACCURACY_MEDIUM; case SensorManager.SENSOR_STATUS_ACCURACY_LOW: return CIRCLE_SIZE_ACCURACY_LOW; default: return CIRCLE_SIZE_DEFAULT; } } protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (normalisedDataPoints.length <= 0) { return; } int height = canvas.getHeight(); int width = canvas.getWidth(); float zeroLine = height - (height * zeroline); canvas.drawLine(0, zeroLine, width, zeroLine, infoPaint); if (zeroline < 0.8f && zeroline > 0.2f) { canvas.drawText("0", width - 70, zeroLine - 5, infoPaint); } canvas.drawText(maxValueLabel, width - 70, 60, infoPaint); canvas.drawText(minValueValue, width - 70, height - 40, infoPaint); int maxValues = MAX_DATA_SIZE; int pointSpan = width / maxValues; boolean firstSensorDrawn = true; long previousTimeStamp = -1; float previousX = -1; float previousY = -1; for (int i = 0; i < this.normalisedDataPoints.length; ++i) { if (!drawSensors[i]) { continue; } if (this.normalisedDataPoints[i] == null) { continue; } int currentX = 0;//width - pointSpan; int index = 0; int lastDrawnTagIndex = -1; for (Float dataPoint : this.normalisedDataPoints[i]) { float y = height - (height * dataPoint); canvas.drawCircle(currentX, y, dataPointsAccuracy[i].get(index), rectPaints[i]); if (i >= rectPaints.length) { canvas.drawCircle(currentX, y, dataPointsAccuracy[i].get(index), rectPaints[rectPaints.length - 1]); if (previousX != -1 && previousY != -1) { canvas.drawLine(previousX, previousY, currentX, y, rectPaints[rectPaints.length - 1]); } } else { canvas.drawCircle(currentX, y, dataPointsAccuracy[i].get(index), rectPaints[i]); if (previousX != -1 && previousY != -1) { canvas.drawLine(previousX, previousY, currentX, y, rectPaints[i]); } } // draw tags here if (firstSensorDrawn) { if (previousTimeStamp != -1) { int nextIndexToDraw = findStartingIndexForTag(previousTimeStamp / 1000000, dataPointsTimeStamps[i].get(index) / 1000000, lastDrawnTagIndex + 1); if (nextIndexToDraw != -1) { drawTag(canvas, this.tags.get(nextIndexToDraw), previousX + ((currentX - previousX) / 2)); lastDrawnTagIndex = nextIndexToDraw; } } previousTimeStamp = dataPointsTimeStamps[i].get(index); } previousX = currentX; previousY = y; currentX += pointSpan; ++index; } firstSensorDrawn = false; previousX = -1; previousY = -1; } } private void drawTag(Canvas canvas, TagData tag, float x) { canvas.drawRect(x - 3, 0 + 1, x + 3, canvas.getHeight() - 1, tagPaint); } private int findStartingIndexForTag(long startTimestamp, long endTimestamp, int startIndex) { for (int i = startIndex; i < this.tags.size(); ++i) { if (this.tags.get(i).getTimestamp() > startTimestamp && this.tags.get(i).getTimestamp() <= endTimestamp) { return i; } } return -1; } }