/* * Copyright 2014 Diogo Bernardino * * 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 com.db.chart.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DashPathEffect; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Region; import android.graphics.Shader; import android.util.AttributeSet; import com.db.chart.Tools; import com.db.chart.model.ChartEntry; import com.db.chart.model.ChartSet; import com.db.chart.model.LineSet; import com.db.chart.model.Point; import com.db.williamchart.R; import java.util.ArrayList; /** * Implements a line chart extending {@link ChartView} */ public class LineChartView extends ChartView { /** * Radius clickable region */ private float mClickableRadius; /** * Style applied to line chart */ private Style mStyle; public LineChartView(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(Orientation.VERTICAL); mStyle = new Style(context.getTheme() .obtainStyledAttributes(attrs, R.styleable.ChartAttrs, 0, 0)); mClickableRadius = (float) getResources() .getDimension(R.dimen.dot_region_radius); } public LineChartView(Context context) { super(context); setOrientation(Orientation.VERTICAL); mStyle = new Style(); mClickableRadius = (float) getResources() .getDimension(R.dimen.dot_region_radius); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mStyle.init(); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); mStyle.clean(); } /** * Method responsible to draw a line with the parsed screen points. * * @param canvas The canvas to draw on. */ @Override public void onDrawChart(Canvas canvas, ArrayList<ChartSet> data) { LineSet lineSet; for (ChartSet set : data) { lineSet = (LineSet) set; if (lineSet.isVisible()) { mStyle.mLinePaint.setColor(lineSet.getColor()); mStyle.mLinePaint.setStrokeWidth(lineSet.getThickness()); applyAlpha(mStyle.mLinePaint, lineSet.getAlpha()); if (lineSet.isDashed()) mStyle.mLinePaint .setPathEffect(new DashPathEffect(lineSet.getDashedIntervals(), lineSet.getDashedPhase())); else mStyle.mLinePaint.setPathEffect(null); //Draw line if (!lineSet.isSmooth()) drawLine(canvas, lineSet); else drawSmoothLine(canvas, lineSet); //Draw points drawPoints(canvas, lineSet); } } } /** * Responsible for drawing points */ private void drawPoints(Canvas canvas, LineSet set) { int begin = set.getBegin(); int end = set.getEnd(); Point dot; for (int i = begin; i < end; i++) { dot = (Point) set.getEntry(i); if (dot.isVisible()) { // Style dot mStyle.mDotsPaint.setColor(dot.getColor()); applyAlpha(mStyle.mDotsPaint, set.getAlpha()); // Draw dot canvas.drawCircle(dot.getX(), dot.getY(), dot.getRadius(), mStyle.mDotsPaint); //Draw dots stroke if (dot.hasStroke()) { // Style stroke mStyle.mDotsStrokePaint.setStrokeWidth(dot.getStrokeThickness()); mStyle.mDotsStrokePaint.setColor(dot.getStrokeColor()); applyAlpha(mStyle.mDotsStrokePaint, set.getAlpha()); canvas.drawCircle(dot.getX(), dot.getY(), dot.getRadius(), mStyle.mDotsStrokePaint); } // Draw drawable if (dot.getDrawable() != null) { Bitmap dotsBitmap = Tools.drawableToBitmap(dot.getDrawable()); canvas.drawBitmap(dotsBitmap, dot.getX() - dotsBitmap.getWidth() / 2, dot.getY() - dotsBitmap.getHeight() / 2, mStyle.mDotsPaint); } } } } /** * Responsible for drawing a (non smooth) line */ public void drawLine(Canvas canvas, LineSet set) { float minY = this.getInnerChartBottom(); Path path = new Path(); Path bgPath = new Path(); int begin = set.getBegin(); int end = set.getEnd(); float x; float y; for (int i = begin; i < end; i++) { x = set.getEntry(i).getX(); y = set.getEntry(i).getY(); // Get minimum display Y to optimize gradient if (y < minY) minY = y; if (i == begin) { //Defining outline path.moveTo(x, y); //Defining background bgPath.moveTo(x, y); } else { //Defining outline path.lineTo(x, y); //Defining background bgPath.lineTo(x, y); } } //Draw background if (set.hasFill() || set.hasGradientFill()) drawBackground(canvas, bgPath, set, minY); //Draw line canvas.drawPath(path, mStyle.mLinePaint); } /** * Credits: http://www.jayway.com/author/andersericsson/ * Method responsible to draw a smooth line with the parsed screen points. */ private void drawSmoothLine(Canvas canvas, LineSet set) { float minY = this.getInnerChartBottom(); float thisPointX; float thisPointY; float nextPointX; float nextPointY; float startdiffX; float startdiffY; float endDiffX; float endDiffY; float firstControlX; float firstControlY; float secondControlX; float secondControlY; Path path = new Path(); path.moveTo(set.getEntry(set.getBegin()).getX(), set.getEntry(set.getBegin()).getY()); Path bgPath = new Path(); bgPath.moveTo(set.getEntry(set.getBegin()).getX(), set.getEntry(set.getBegin()).getY()); int begin = set.getBegin(); int end = set.getEnd(); float x; float y; for (int i = begin; i < end - 1; i++) { x = set.getEntry(i).getX(); y = set.getEntry(i).getY(); // Get minimum display Y to optimize gradient if (y < minY) minY = y; thisPointX = x; thisPointY = y; nextPointX = set.getEntry(i + 1).getX(); nextPointY = set.getEntry(i + 1).getY(); startdiffX = (nextPointX - set.getEntry(si(set.size(), i - 1)).getX()); startdiffY = (nextPointY - set.getEntry(si(set.size(), i - 1)).getY()); endDiffX = (set.getEntry(si(set.size(), i + 2)).getX() - thisPointX); endDiffY = (set.getEntry(si(set.size(), i + 2)).getY() - thisPointY); firstControlX = thisPointX + (0.15f * startdiffX); firstControlY = thisPointY + (0.15f * startdiffY); secondControlX = nextPointX - (0.15f * endDiffX); secondControlY = nextPointY - (0.15f * endDiffY); //Define outline path.cubicTo(firstControlX, firstControlY, secondControlX, secondControlY, nextPointX, nextPointY); //Define background bgPath.cubicTo(firstControlX, firstControlY, secondControlX, secondControlY, nextPointX, nextPointY); } //Draw background if (set.hasFill() || set.hasGradientFill()) drawBackground(canvas, bgPath, set, minY); //Draw outline canvas.drawPath(path, mStyle.mLinePaint); } /** * Responsible for drawing line background */ private void drawBackground(Canvas canvas, Path path, LineSet set, float minDisplayY) { float innerChartBottom = super.getInnerChartBottom(); mStyle.mFillPaint.setAlpha((int) (set.getAlpha() * 255)); if (set.hasFill()) mStyle.mFillPaint.setColor(set.getFillColor()); if (set.hasGradientFill()) mStyle.mFillPaint.setShader( new LinearGradient( super.getInnerChartLeft(), minDisplayY, super.getInnerChartLeft(), innerChartBottom, set.getGradientColors(), set.getGradientPositions(), Shader.TileMode.MIRROR)); path.lineTo(set.getEntry(set.getEnd() - 1).getX(), innerChartBottom); path.lineTo(set.getEntry(set.getBegin()).getX(), innerChartBottom); path.close(); canvas.drawPath(path, mStyle.mFillPaint); } /** * (Optional) To be overridden in order for each chart to define its own clickable regions. * This way, classes extending ChartView will only define their clickable regions. * <p/> * Important: the returned vector must match the order of the data passed * by the user. This ensures that onTouchEvent will return the correct index. * * @param data {@link java.util.ArrayList} of {@link com.db.chart.model.ChartSet} * to use while defining each region of a {@link com.db.chart.view.BarChartView} * @return {@link java.util.ArrayList} of {@link android.graphics.Region} with regions * where click will be detected */ @Override public ArrayList<ArrayList<Region>> defineRegions(ArrayList<ChartSet> data) { ArrayList<ArrayList<Region>> result = new ArrayList<ArrayList<Region>>(); ArrayList<Region> regionSet; float x; float y; for (ChartSet set : data) { regionSet = new ArrayList<Region>(set.size()); for (ChartEntry e : set.getEntries()) { x = e.getX(); y = e.getY(); regionSet.add(new Region((int) (x - mClickableRadius), (int) (y - mClickableRadius), (int) (x + mClickableRadius), (int) (y + mClickableRadius))); } result.add(regionSet); } return result; } private void applyAlpha(Paint paint, float alpha) { paint.setAlpha((int) (alpha * 255)); paint.setShadowLayer(mStyle.mShadowRadius, mStyle.mShadowDx, mStyle.mShadowDy, Color.argb(((int) (alpha * 255) < mStyle.mAlpha) ? (int) (alpha * 255) : mStyle.mAlpha, mStyle.mRed, mStyle.mGreen, mStyle.mBlue)); } /** * Credits: http://www.jayway.com/author/andersericsson/ * Given an index in points, it will make sure the the returned index is * within the array. */ private static int si(int setSize, int i) { if (i > setSize - 1) return setSize - 1; else if (i < 0) return 0; return i; } /** * @param radius * @param dx * @param dy * @param color * @return */ public LineChartView setShadow(float radius, float dx, float dy, int color) { mStyle.mShadowRadius = radius; mStyle.mShadowDx = dx; mStyle.mShadowDy = dy; mStyle.mShadowColor = color; return this; } /** * @param radius * @return */ public LineChartView setClickablePointRadius(float radius) { mClickableRadius = radius; return this; } /** * Class responsible to style the LineChart! * Can be instantiated with or without attributes. */ class Style { /** * Paint variables */ private Paint mDotsPaint; private Paint mDotsStrokePaint; private Paint mLinePaint; private Paint mFillPaint; /** * Shadow variables */ private int mShadowColor; private float mShadowRadius; private float mShadowDx; private float mShadowDy; /** * Shadow color */ private int mAlpha; private int mRed; private int mBlue; private int mGreen; protected Style() { mShadowRadius = 0; mShadowDx = 0; mShadowDy = 0; mShadowColor = 0; } protected Style(TypedArray attrs) { mShadowRadius = attrs.getDimension( R.styleable.ChartAttrs_chart_shadowRadius, 0); mShadowDx = attrs.getDimension( R.styleable.ChartAttrs_chart_shadowDx, 0); mShadowDy = attrs.getDimension( R.styleable.ChartAttrs_chart_shadowDy, 0); mShadowColor = attrs.getColor( R.styleable.ChartAttrs_chart_shadowColor, 0); } private void init() { mAlpha = Color.alpha(mShadowColor); mRed = Color.red(mShadowColor); mBlue = Color.blue(mShadowColor); mGreen = Color.green(mShadowColor); mDotsPaint = new Paint(); mDotsPaint.setStyle(Paint.Style.FILL_AND_STROKE); mDotsPaint.setAntiAlias(true); mDotsStrokePaint = new Paint(); mDotsStrokePaint.setStyle(Paint.Style.STROKE); mDotsStrokePaint.setAntiAlias(true); mLinePaint = new Paint(); mLinePaint.setStyle(Paint.Style.STROKE); mLinePaint.setAntiAlias(true); mFillPaint = new Paint(); mFillPaint.setStyle(Paint.Style.FILL); } private void clean() { mLinePaint = null; mFillPaint = null; mDotsPaint = null; } } }