package com.fima.chartview; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import android.widget.RelativeLayout; public class ChartView extends RelativeLayout { ////////////////////////////////////////////////////////////////////////////////////// // PRIVATE MEMBERS ////////////////////////////////////////////////////////////////////////////////////// // View private Paint mPaint = new Paint(); // Series private List<AbstractSeries> mSeries = new ArrayList<AbstractSeries>(); // Labels private LabelAdapter mLeftLabelAdapter; private LabelAdapter mTopLabelAdapter; private LabelAdapter mBottomLabelAdapter; private LabelAdapter mRightLabelAdapter; private LinearLayout mLeftLabelLayout; private LinearLayout mTopLabelLayout; private LinearLayout mBottomLabelLayout; private LinearLayout mRightLabelLayout; private int mLeftLabelWidth; private int mTopLabelHeight; private int mRightLabelWidth; private int mBottomLabelHeight; // Range private RectD mValueBounds = new RectD(); private double mMinX = Double.MAX_VALUE; private double mMaxX = Double.MIN_VALUE; private double mMinY = Double.MAX_VALUE; private double mMaxY = Double.MIN_VALUE; // Grid private Rect mGridBounds = new Rect(); private int mGridLineColor; private int mGridLineWidth; private int mGridLinesHorizontal; private int mGridLinesVertical; ////////////////////////////////////////////////////////////////////////////////////// // CONSTRUCTORS ////////////////////////////////////////////////////////////////////////////////////// public ChartView(Context context) { this(context, null, 0); } public ChartView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ChartView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setWillNotDraw(false); setBackgroundColor(Color.TRANSPARENT); final TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.ChartView); mGridLineColor = attributes.getInt(R.styleable.ChartView_gridLineColor, Color.BLACK); mGridLineWidth = attributes.getDimensionPixelSize(R.styleable.ChartView_gridLineWidth, 1); mGridLinesHorizontal = attributes.getInt(R.styleable.ChartView_gridLinesHorizontal, 5); mGridLinesVertical = attributes.getInt(R.styleable.ChartView_gridLinesVertical, 5); mLeftLabelWidth = attributes.getDimensionPixelSize(R.styleable.ChartView_leftLabelWidth, 0); mTopLabelHeight = attributes.getDimensionPixelSize(R.styleable.ChartView_topLabelHeight, 0); mRightLabelWidth = attributes.getDimensionPixelSize(R.styleable.ChartView_rightLabelWidth, 0); mBottomLabelHeight = attributes.getDimensionPixelSize(R.styleable.ChartView_bottomLabelHeight, 0); // left label layout mLeftLabelLayout = new LinearLayout(context); mLeftLabelLayout.setLayoutParams(new LayoutParams(mLeftLabelWidth, LayoutParams.MATCH_PARENT)); mLeftLabelLayout.setOrientation(LinearLayout.VERTICAL); // top label layout mTopLabelLayout = new LinearLayout(context); mTopLabelLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, mTopLabelHeight)); mTopLabelLayout.setOrientation(LinearLayout.HORIZONTAL); // right label layout LayoutParams rightLabelParams = new LayoutParams(mRightLabelWidth, LayoutParams.MATCH_PARENT); rightLabelParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); mRightLabelLayout = new LinearLayout(context); mRightLabelLayout.setLayoutParams(rightLabelParams); mRightLabelLayout.setOrientation(LinearLayout.VERTICAL); // bottom label layout LayoutParams bottomLabelParams = new LayoutParams(LayoutParams.MATCH_PARENT, mBottomLabelHeight); bottomLabelParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); mBottomLabelLayout = new LinearLayout(context); mBottomLabelLayout.setLayoutParams(bottomLabelParams); mBottomLabelLayout.setOrientation(LinearLayout.HORIZONTAL); // Add label views addView(mLeftLabelLayout); addView(mTopLabelLayout); addView(mRightLabelLayout); addView(mBottomLabelLayout); } ////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ////////////////////////////////////////////////////////////////////////////////////// public void clearSeries() { mSeries = new ArrayList<AbstractSeries>(); resetRange(); invalidate(); } public void addSeries(AbstractSeries series) { if (mSeries == null) { mSeries = new ArrayList<AbstractSeries>(); } extendRange(series.getMinX(), series.getMinY()); extendRange(series.getMaxX(), series.getMaxY()); mSeries.add(series); invalidate(); } // Label adapters public void setLeftLabelAdapter(LabelAdapter adapter) { mLeftLabelAdapter = adapter; final double[] values = new double[mGridLinesVertical + 2]; final double step = mValueBounds.height() / (mGridLinesVertical + 1); for (int i = 0; i < values.length; i++) { values[i] = mValueBounds.top + (step * i); } mLeftLabelAdapter.setValues(values); } public void setTopLabelAdapter(LabelAdapter adapter) { mTopLabelAdapter = adapter; final double[] values = new double[mGridLinesHorizontal + 2]; final double step = mValueBounds.width() / (mGridLinesHorizontal + 1); for (int i = 0; i < values.length; i++) { values[i] = mValueBounds.left + (step * i); } mTopLabelAdapter.setValues(values); } public void setRightLabelAdapter(LabelAdapter adapter) { mRightLabelAdapter = adapter; final double[] values = new double[mGridLinesVertical + 2]; final double step = mValueBounds.height() / (mGridLinesVertical + 1); for (int i = 0; i < values.length; i++) { values[i] = mValueBounds.top + (step * i); } mRightLabelAdapter.setValues(values); } public void setBottomLabelAdapter(LabelAdapter adapter) { mBottomLabelAdapter = adapter; final double[] values = new double[mGridLinesHorizontal + 2]; final double step = mValueBounds.width() / (mGridLinesHorizontal + 1); for (int i = 0; i < values.length; i++) { values[i] = mValueBounds.left + (step * i); } mBottomLabelAdapter.setValues(values); } // Grid properties public void setGridLineColor(int color) { mGridLineColor = color; } public void setGridLineWidth(int width) { mGridLineWidth = width; } public void setGridLinesHorizontal(int count) { mGridLinesHorizontal = count; } public void setGridLinesVertical(int count) { mGridLinesVertical = count; } ////////////////////////////////////////////////////////////////////////////////////// // OVERRIDEN METHODS ////////////////////////////////////////////////////////////////////////////////////// @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); final int gridLeft = mLeftLabelWidth + mGridLineWidth - 1; final int gridTop = mTopLabelHeight + mGridLineWidth - 1; final int gridRight = getWidth() - mRightLabelWidth - mGridLineWidth; final int gridBottom = getHeight() - mBottomLabelHeight - mGridLineWidth; mGridBounds.set(gridLeft, gridTop, gridRight, gridBottom); // Set sizes LayoutParams leftParams = (LayoutParams) mLeftLabelLayout.getLayoutParams(); leftParams.height = mGridBounds.height(); mLeftLabelLayout.setLayoutParams(leftParams); LayoutParams topParams = (LayoutParams) mTopLabelLayout.getLayoutParams(); topParams.width = mGridBounds.width(); mTopLabelLayout.setLayoutParams(topParams); LayoutParams rightParams = (LayoutParams) mRightLabelLayout.getLayoutParams(); rightParams.height = mGridBounds.height(); mRightLabelLayout.setLayoutParams(rightParams); LayoutParams bottomParams = (LayoutParams) mBottomLabelLayout.getLayoutParams(); bottomParams.width = mGridBounds.width(); mBottomLabelLayout.setLayoutParams(bottomParams); // Set layouts mLeftLabelLayout.layout(0, gridTop, gridLeft, gridBottom); mTopLabelLayout.layout(gridLeft, 0, gridRight, gridTop); mRightLabelLayout.layout(gridRight, gridTop, getWidth(), gridBottom); mBottomLabelLayout.layout(gridLeft, gridBottom, gridRight, getHeight()); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Start drawing drawGrid(canvas); drawLabels(canvas); for (AbstractSeries series : mSeries) { series.draw(canvas, mGridBounds, mValueBounds); } } ////////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS ////////////////////////////////////////////////////////////////////////////////////// private void resetRange() { mMinX = Double.MAX_VALUE; mMaxX = Double.MIN_VALUE; mMinY = Double.MAX_VALUE; mMaxY = Double.MIN_VALUE; mValueBounds.set(mMinX, mMinY, mMaxX, mMaxY); } private void extendRange(double x, double y) { if (x < mMinX) { mMinX = x; } if (x > mMaxX) { mMaxX = x; } if (y < mMinY) { mMinY = y; } if (y > mMaxY) { mMaxY = y; } mValueBounds.set(mMinX, mMinY, mMaxX, mMaxY); } // Drawing methods private void drawGrid(Canvas canvas) { mPaint.setColor(mGridLineColor); mPaint.setStrokeWidth(mGridLineWidth); final float stepX = mGridBounds.width() / (float) (mGridLinesHorizontal + 1); final float stepY = mGridBounds.height() / (float) (mGridLinesVertical + 1); final float left = mGridBounds.left; final float top = mGridBounds.top; final float bottom = mGridBounds.bottom; final float right = mGridBounds.right; for (int i = 0; i < mGridLinesHorizontal + 2; i++) { canvas.drawLine(left + (stepX * i), top, left + (stepX * i), bottom, mPaint); } for (int i = 0; i < mGridLinesVertical + 2; i++) { canvas.drawLine(left, top + (stepY * i), right, top + (stepY * i), mPaint); } } private void drawLabels(Canvas canvas) { if (mLeftLabelAdapter != null) { drawLeftLabels(canvas); } if (mTopLabelAdapter != null) { drawTopLabels(canvas); } if (mRightLabelAdapter != null) { drawRightLabels(canvas); } if (mBottomLabelAdapter != null) { drawBottomLabels(canvas); } } // Label drawing routines private void drawLeftLabels(Canvas canvas) { // Add views from adapter final int labelCount = mLeftLabelAdapter.getCount(); for (int i = 0; i < labelCount; i++) { View view = mLeftLabelLayout.getChildAt(i); if (view == null) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0); if (i == 0 || i == labelCount - 1) { params.weight = 0.5f; } else { params.weight = 1; } view = mLeftLabelAdapter.getView((labelCount - 1) - i, view, mLeftLabelLayout); view.setLayoutParams(params); mLeftLabelLayout.addView(view); } else { mLeftLabelAdapter.getView((labelCount - 1) - i, view, mLeftLabelLayout); } } // Remove extra views final int childCount = mLeftLabelLayout.getChildCount(); for (int i = labelCount; i < childCount; i++) { mLeftLabelLayout.removeViewAt(i); } } private void drawTopLabels(final Canvas canvas) { // Add views from adapter final int labelCount = mTopLabelAdapter.getCount(); for (int i = 0; i < labelCount; i++) { View view = mTopLabelLayout.getChildAt(i); if (view == null) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT); if (i == 0 || i == labelCount - 1) { params.weight = 0.5f; } else { params.weight = 1; } view = mTopLabelAdapter.getView(i, view, mTopLabelLayout); view.setLayoutParams(params); mTopLabelLayout.addView(view); } else { mTopLabelAdapter.getView(i, view, mTopLabelLayout); } } // Remove extra views final int childCount = mTopLabelLayout.getChildCount(); for (int i = labelCount; i < childCount; i++) { mTopLabelLayout.removeViewAt(i); } } private void drawRightLabels(Canvas canvas) { // Add views from adapter final int labelCount = mRightLabelAdapter.getCount(); for (int i = 0; i < labelCount; i++) { View view = mRightLabelLayout.getChildAt(i); if (view == null) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0); if (i == 0 || i == labelCount - 1) { params.weight = 0.5f; } else { params.weight = 1; } view = mRightLabelAdapter.getView((labelCount - 1) - i, view, mRightLabelLayout); view.setLayoutParams(params); mRightLabelLayout.addView(view); } else { mRightLabelAdapter.getView((labelCount - 1) - i, view, mRightLabelLayout); } } // Remove extra views final int childCount = mRightLabelLayout.getChildCount(); for (int i = labelCount; i < childCount; i++) { mRightLabelLayout.removeViewAt(i); } } private void drawBottomLabels(final Canvas canvas) { // Add views from adapter final int labelCount = mBottomLabelAdapter.getCount(); for (int i = 0; i < labelCount; i++) { View view = mBottomLabelLayout.getChildAt(i); if (view == null) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT); if (i == 0 || i == labelCount - 1) { params.weight = 0.5f; } else { params.weight = 1; } view = mBottomLabelAdapter.getView(i, view, mBottomLabelLayout); view.setLayoutParams(params); mBottomLabelLayout.addView(view); } else { mBottomLabelAdapter.getView(i, view, mBottomLabelLayout); } } // Remove extra views final int childCount = mBottomLabelLayout.getChildCount(); for (int i = labelCount; i < childCount; i++) { mBottomLabelLayout.removeViewAt(i); } } }