package com.evancharlton.mileage.views; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.util.AttributeSet; import android.view.View; public class LineChart extends View { private Paint m_axisLabelPaint; private Paint m_aboveAveragePaint; private Paint m_belowAveragePaint; private Paint m_axisLinesPaint; private Paint m_dataPaint; private Paint m_dataLinePaint; private Paint m_unitPaint; private Paint m_minXPaint; private Paint m_maxXPaint; private String m_verticalLabel = ""; private String m_horizontalLabel = ""; private String m_minXLabel = ""; private String m_maxXLabel = ""; private String m_minYLabel = ""; private String m_maxYLabel = ""; private String m_avgLabel = ""; private boolean m_frozen = false; private float m_avg = 0F; private float m_minX = Float.MAX_VALUE; private float m_maxX = Float.MIN_VALUE; private float m_minY = Float.MAX_VALUE; private float m_maxY = Float.MIN_VALUE; private float[] m_data = null; private boolean m_betterOnBottom; public LineChart(Context context) { super(context); init(); } public LineChart(Context context, AttributeSet attrs) { super(context, attrs); init(); } public LineChart(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } protected void init() { m_axisLabelPaint = new Paint(); m_axisLabelPaint.setColor(0xFF999999); m_axisLabelPaint.setTextSize(12); m_axisLabelPaint.setTextAlign(Align.CENTER); m_axisLabelPaint.setAntiAlias(true); m_axisLinesPaint = new Paint(); m_axisLabelPaint.setColor(0xFFBBBBBB); m_aboveAveragePaint = new Paint(); m_aboveAveragePaint.setColor(0xDD47FF6F); m_belowAveragePaint = new Paint(); m_belowAveragePaint.setColor(0x99FF3021); m_dataPaint = new Paint(); m_dataPaint.setColor(Color.WHITE); m_dataPaint.setStrokeWidth(5); m_dataLinePaint = new Paint(); m_dataLinePaint.setColor(Color.GRAY); m_dataLinePaint.setStrokeWidth(3); m_dataLinePaint.setAntiAlias(true); m_unitPaint = new Paint(); m_unitPaint.setColor(Color.BLACK); m_unitPaint.setTextSize(12); m_unitPaint.setTextAlign(Align.LEFT); m_unitPaint.setAntiAlias(true); m_minXPaint = new Paint(); m_minXPaint.setColor(0xFF999999); m_minXPaint.setTextSize(12); m_minXPaint.setTextAlign(Align.LEFT); m_minXPaint.setAntiAlias(true); m_maxXPaint = new Paint(); m_maxXPaint.setColor(0xFF999999); m_maxXPaint.setTextSize(12); m_maxXPaint.setTextAlign(Align.RIGHT); m_maxXPaint.setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!m_frozen) { final int width = canvas.getWidth(); final int height = canvas.getHeight() - 50; if (m_minYLabel.length() == 0) { m_minYLabel = String.valueOf(m_minY); } if (m_maxYLabel.length() == 0) { m_maxYLabel = String.valueOf(m_maxY); } // axis labels canvas.drawText(m_horizontalLabel, width / 2, height, m_axisLabelPaint); canvas.save(); canvas.translate(0, height / 2); canvas.rotate(90); canvas.drawText(m_verticalLabel, 0, 0, m_axisLabelPaint); canvas.restore(); // lines final float axisTextHeight = m_axisLabelPaint.getTextSize(); float[] lines = new float[] { axisTextHeight, height - axisTextHeight, width, height - axisTextHeight, axisTextHeight, 0, axisTextHeight, height - axisTextHeight }; canvas.drawLines(lines, m_axisLinesPaint); // data regions if (m_betterOnBottom == false) { canvas.drawRect(axisTextHeight + 1, 0, width, m_avg * height, m_aboveAveragePaint); canvas.drawRect(axisTextHeight + 1, m_avg * height, width, height - (axisTextHeight + 1), m_belowAveragePaint); } else { canvas.drawRect(axisTextHeight + 1, 0, width, m_avg * height, m_belowAveragePaint); canvas.drawRect(axisTextHeight + 1, m_avg * height, width, height - (axisTextHeight + 1), m_aboveAveragePaint); } // data points if (m_data != null) { final float deltaX = m_maxX - m_minX; final float deltaY = m_maxY - m_minY; float prevX = Float.MIN_VALUE; float prevY = Float.MAX_VALUE; boolean maxRendered = false; boolean minRendered = false; for (int i = 0; i < m_data.length; i += 2) { final float x_raw = m_data[i]; final float y_raw = m_data[i + 1]; // adjust the data to fit the chart float x = (x_raw - m_minX) / deltaX; float y = (y_raw - m_minY) / deltaY; float posX = (float) ((x * (width - axisTextHeight)) + axisTextHeight); float posY = (float) ((height - axisTextHeight) - (y * (height - axisTextHeight))); // render the labels if (y_raw == m_maxY && maxRendered == false) { if (i < (m_data.length + 1) / 4) { m_maxXPaint.setTextAlign(Align.LEFT); } canvas.drawText(m_maxXLabel, posX, height, m_maxXPaint); maxRendered = true; } else if (y_raw == m_minY && minRendered == false) { if (i > (m_data.length + 1) / 4) { // we need to flip the alignment m_minXPaint.setTextAlign(Align.RIGHT); } canvas.drawText(m_minXLabel, posX, height, m_minXPaint); minRendered = true; } canvas.drawPoint(posX, posY, m_dataPaint); if (prevX != Float.MIN_VALUE && prevY != Float.MAX_VALUE) { canvas.drawLine(prevX, prevY, posX, posY, m_dataLinePaint); } prevX = posX; prevY = posY; } canvas.drawText(m_maxYLabel, axisTextHeight + 1, m_unitPaint.getTextSize(), m_unitPaint); canvas.drawText(m_minYLabel, axisTextHeight + 1, height - (m_unitPaint.getTextSize() + 3), m_unitPaint); } canvas.drawText(m_avgLabel, axisTextHeight + 1, (m_avg * height) - 1, m_unitPaint); } } public void freeze() { m_frozen = true; } public void thaw() { m_frozen = false; invalidate(); } public void setYAxisLabel(String label) { m_verticalLabel = label; invalidate(); } public void setYAxisLabels(String min_label, String max_label) { m_minYLabel = min_label; m_maxYLabel = max_label; } public void setXAxisLabel(String label) { m_horizontalLabel = label; invalidate(); } public void setXAxisLabels(String min_label, String max_label) { m_minXLabel = min_label; m_maxXLabel = max_label; } public void setAverageLabel(String label) { m_avgLabel = label; } /** * Set the raw data. Note that the caller does not need to modify the data * to fit the graph; this function will normalize the data appropriately. * * @param data Chart data in [x0 y0 x1 y1 ... xn yn] format */ public void setDataPoints(float[] data) { if (data.length % 2 != 0) { throw new IllegalArgumentException("Invalid number of data points!"); } float min_x = Float.MAX_VALUE; float max_x = Float.MIN_VALUE; float tot_x = 0F; float min_y = Float.MAX_VALUE; float max_y = Float.MIN_VALUE; float tot_y = 0F; for (int i = 0; i < data.length; i += 2) { float x = data[i]; float y = data[i + 1]; tot_x += x; tot_y += y; if (x < min_x) { min_x = x; } if (x > max_x) { max_x = x; } if (y < min_y) { min_y = y; } if (y > max_y) { max_y = y; } } m_minX = (float) min_x; m_minY = (float) min_y; m_maxX = (float) max_x; m_maxY = (float) max_y; m_avg = (((float) (tot_y / (data.length / 2))) - m_minY) / (m_maxY - m_minY); m_data = data; } public void setBetterOnBottom(boolean b) { m_betterOnBottom = b; } public void setDataPoints(final Float[] array) { float[] data = new float[array.length]; for (int i = 0; i < array.length; i++) { data[i] = array[i]; } setDataPoints(data); } }