/* * 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 java.util.ArrayList; import com.db.williamchart.R; 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.view.animation.style.DashAnimation; 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.os.Handler; import android.util.AttributeSet; import android.graphics.Shader; /** * Implements a line chart extending {@link ChartView} */ public class LineChartView extends ChartView { /** Radius clickable region */ private static float sRegionRadius; /** 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)); sRegionRadius = (float) getResources() .getDimension(R.dimen.dot_region_radius); } public LineChartView(Context context) { super(context); setOrientation(Orientation.VERTICAL); mStyle = new Style(); } @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.getLineColor()); mStyle.mLinePaint.setStrokeWidth(lineSet.getLineThickness()); applyAlpha(mStyle.mLinePaint, lineSet.getAlpha()); if(lineSet.isDashed()) mStyle.mLinePaint .setPathEffect(new DashPathEffect(new float[] {10,10}, lineSet.getPhase())); else mStyle.mLinePaint.setPathEffect(null); //Draw line if (!lineSet.isSmooth()) drawLine(canvas, lineSet); else drawSmoothLine(canvas, lineSet); //Draw points if(lineSet.hasDots()) drawPoints(canvas, lineSet); } } } /** * Responsible for drawing points */ private void drawPoints(Canvas canvas, LineSet set) { Bitmap dotsBitmap = null; float dotsBitmapWidthCenter = 0; float dotsBitmapHeightCenter = 0; if(set.getDotsDrawable() != null){ dotsBitmap = Tools.drawableToBitmap(set.getDotsDrawable()); dotsBitmapWidthCenter = dotsBitmap.getWidth()/2; dotsBitmapHeightCenter = dotsBitmap.getHeight()/2; } mStyle.mDotsPaint.setColor(set.getDotsColor()); applyAlpha(mStyle.mDotsPaint, set.getAlpha()); mStyle.mDotsStrokePaint.setStrokeWidth(set.getDotsStrokeThickness()); mStyle.mDotsStrokePaint.setColor(set.getDotsStrokeColor()); applyAlpha(mStyle.mDotsStrokePaint, set.getAlpha()); Path path = new Path(); int begin = set.getBegin(); int end = set.getEnd(); for (int i = begin; i < end; i++){ path.addCircle(set.getEntry(i).getX(), set.getEntry(i).getY(), set.getDotsRadius(), Path.Direction.CW); if(dotsBitmap != null) canvas.drawBitmap(dotsBitmap, set.getEntry(i).getX() - dotsBitmapWidthCenter, set.getEntry(i).getY() - dotsBitmapHeightCenter, mStyle.mDotsPaint); } //Draw dots fill canvas.drawPath(path, mStyle.mDotsPaint); //Draw dots stroke if(set.hasDotsStroke()) canvas.drawPath(path, mStyle.mDotsStrokePaint); } /** * 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. * * 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 - sRegionRadius), (int)(y - sRegionRadius), (int)(x + sRegionRadius), (int)(y + sRegionRadius))); } 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; } /** * 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 final int mShadowColor; private final float mShadowRadius; private final float mShadowDx; private final 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; } } }