blob: ab5fb328c7388d579bf5010ecdea08f1df4bf393 [file] [log] [blame]
package com.github.mikephil.charting.renderer;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.LineDataProvider;
import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.Transformer;
import java.util.ArrayList;
public class LineChartRenderer extends DataRenderer {
protected LineDataProvider mChart;
/** paint for the inner circle of the value indicators */
protected Paint mCirclePaintInner;
public LineChartRenderer(LineDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(animator, viewPortHandler);
mChart = chart;
mCirclePaintInner = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaintInner.setStyle(Paint.Style.FILL);
mCirclePaintInner.setColor(Color.WHITE);
}
@Override
public void drawData(Canvas c) {
LineData lineData = mChart.getLineData();
for (LineDataSet set : lineData.getDataSets()) {
if (set.isVisible())
drawDataSet(c, set);
}
}
/**
* Class needed for saving the points when drawing cubic-lines.
*
* @author Philipp Jahoda
*/
protected class CPoint {
public float x = 0f;
public float y = 0f;
/** x-axis distance */
public float dx = 0f;
/** y-axis distance */
public float dy = 0f;
public CPoint(float x, float y) {
this.x = x;
this.y = y;
}
}
protected void drawDataSet(Canvas c, LineDataSet dataSet) {
ArrayList<Entry> entries = dataSet.getYVals();
if (entries.size() < 1)
return;
mRenderPaint.setStrokeWidth(dataSet.getLineWidth());
mRenderPaint.setPathEffect(dataSet.getDashPathEffect());
// if drawing cubic lines is enabled
if (dataSet.isDrawCubicEnabled()) {
drawCubic(c, dataSet, entries);
// draw normal (straight) lines
} else {
drawLinear(c, dataSet, entries);
}
mRenderPaint.setPathEffect(null);
}
protected void drawCubic(Canvas c, LineDataSet dataSet, ArrayList<Entry> entries) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
float intensity = dataSet.getCubicIntensity();
// the path for the cubic-spline
Path spline = new Path();
ArrayList<CPoint> points = new ArrayList<CPoint>();
for (Entry e : entries) {
if (e != null)
points.add(new CPoint(e.getXIndex(), e.getVal()));
}
if (points.size() > 1) {
for (int j = 0; j < points.size() * phaseX; j++) {
CPoint point = points.get(j);
if (j == 0) {
CPoint next = points.get(j + 1);
point.dx = ((next.x - point.x) * intensity);
point.dy = ((next.y - point.y) * intensity);
}
else if (j == points.size() - 1) {
CPoint prev = points.get(j - 1);
point.dx = ((point.x - prev.x) * intensity);
point.dy = ((point.y - prev.y) * intensity);
}
else {
CPoint next = points.get(j + 1);
CPoint prev = points.get(j - 1);
point.dx = ((next.x - prev.x) * intensity);
point.dy = ((next.y - prev.y) * intensity);
}
// create the cubic-spline path
if (j == 0) {
spline.moveTo(point.x, point.y * phaseY);
}
else {
CPoint prev = points.get(j - 1);
spline.cubicTo(prev.x + prev.dx, (prev.y + prev.dy) * phaseY, point.x
- point.dx,
(point.y - point.dy) * phaseY, point.x, point.y * phaseY);
}
}
}
// if filled is enabled, close the path
if (dataSet.isDrawFilledEnabled()) {
// create a new path, this is bad for performance
drawCubicFill(c, dataSet, new Path(spline), trans);
}
mRenderPaint.setColor(dataSet.getColor());
mRenderPaint.setStyle(Paint.Style.STROKE);
trans.pathValueToPixel(spline);
c.drawPath(spline, mRenderPaint);
mRenderPaint.setPathEffect(null);
}
protected void drawCubicFill(Canvas c, LineDataSet dataSet, Path spline, Transformer trans) {
float fillMin = mChart.getFillFormatter()
.getFillLinePosition(dataSet, mChart.getLineData(), mChart.getYChartMax(),
mChart.getYChartMin());
spline.lineTo(dataSet.getYVals().get((int) ((dataSet.getYVals().size() - 1) * mAnimator.getPhaseX())).getXIndex(), fillMin);
spline.lineTo(mChart.getXChartMin(), fillMin);
spline.close();
mRenderPaint.setStyle(Paint.Style.FILL);
mRenderPaint.setColor(dataSet.getFillColor());
// filled is drawn with less alpha
mRenderPaint.setAlpha(dataSet.getFillAlpha());
trans.pathValueToPixel(spline);
c.drawPath(spline, mRenderPaint);
mRenderPaint.setAlpha(255);
}
protected void drawLinear(Canvas c, LineDataSet dataSet, ArrayList<Entry> entries) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
mRenderPaint.setStyle(Paint.Style.STROKE);
// more than 1 color
if (dataSet.getColors() == null || dataSet.getColors().size() > 1) {
float[] positions = trans.generateTransformedValuesLine(
entries, phaseY);
for (int j = 0; j < (positions.length - 2) * phaseX; j += 2) {
if (!mViewPortHandler.isInBoundsRight(positions[j]))
break;
// make sure the lines don't do shitty things outside
// bounds
if (j != 0 && !mViewPortHandler.isInBoundsLeft(positions[j - 1])
&& !mViewPortHandler.isInBoundsY(positions[j + 1]))
continue;
// get the color that is set for this line-segment
mRenderPaint.setColor(dataSet.getColor(j / 2));
c.drawLine(positions[j], positions[j + 1],
positions[j + 2], positions[j + 3], mRenderPaint);
}
} else { // only one color per dataset
mRenderPaint.setColor(dataSet.getColor());
Path line = generateLinePath(entries);
trans.pathValueToPixel(line);
c.drawPath(line, mRenderPaint);
}
mRenderPaint.setPathEffect(null);
// if drawing filled is enabled
if (dataSet.isDrawFilledEnabled() && entries.size() > 0) {
drawLinearFill(c, dataSet, entries, trans);
}
}
protected void drawLinearFill(Canvas c, LineDataSet dataSet, ArrayList<Entry> entries,
Transformer trans) {
mRenderPaint.setStyle(Paint.Style.FILL);
mRenderPaint.setColor(dataSet.getFillColor());
// filled is drawn with less alpha
mRenderPaint.setAlpha(dataSet.getFillAlpha());
// mRenderPaint.setShader(dataSet.getShader());
Path filled = generateFilledPath(
entries,
mChart.getFillFormatter().getFillLinePosition(dataSet, mChart.getLineData(),
mChart.getYChartMax(), mChart.getYChartMin()));
trans.pathValueToPixel(filled);
c.drawPath(filled, mRenderPaint);
// restore alpha
mRenderPaint.setAlpha(255);
// mRenderPaint.setShader(null);
}
/**
* Generates the path that is used for filled drawing.
*
* @param entries
* @return
*/
private Path generateFilledPath(ArrayList<Entry> entries, float fillMin) {
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
Path filled = new Path();
filled.moveTo(entries.get(0).getXIndex(), entries.get(0).getVal() * phaseY);
// create a new path
for (int x = 1; x < entries.size() * phaseX; x++) {
Entry e = entries.get(x);
filled.lineTo(e.getXIndex(), e.getVal() * phaseY);
}
// close up
filled.lineTo(entries.get((int) ((entries.size() - 1) * phaseX)).getXIndex(), fillMin);
filled.lineTo(entries.get(0).getXIndex(), fillMin);
filled.close();
return filled;
}
/**
* Generates the path that is used for drawing a single line.
*
* @param entries
* @return
*/
private Path generateLinePath(ArrayList<Entry> entries) {
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
Path line = new Path();
line.moveTo(entries.get(0).getXIndex(), entries.get(0).getVal() * phaseY);
// create a new path
for (int x = 1; x < entries.size() * phaseX; x++) {
Entry e = entries.get(x);
line.lineTo(e.getXIndex(), e.getVal() * phaseY);
}
return line;
}
@Override
public void drawValues(Canvas c) {
if (mChart.getLineData().getYValCount() < mChart.getMaxVisibleCount()
* mViewPortHandler.getScaleX()) {
ArrayList<LineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
LineDataSet dataSet = dataSets.get(i);
if (!dataSet.isDrawValuesEnabled())
continue;
// apply the text-styling defined by the DataSet
applyValueTextStyle(dataSet);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleSize() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
ArrayList<Entry> entries = dataSet.getYVals();
float[] positions = trans.generateTransformedValuesLine(
entries, mAnimator.getPhaseY());
for (int j = 0; j < positions.length * mAnimator.getPhaseX(); j += 2) {
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
float val = entries.get(j / 2).getVal();
c.drawText(dataSet.getValueFormatter().getFormattedValue(val), x, y - valOffset,
mValuePaint);
}
}
}
}
@Override
public void drawExtras(Canvas c) {
drawCircles(c);
}
protected void drawCircles(Canvas c) {
mRenderPaint.setStyle(Paint.Style.FILL);
ArrayList<LineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < mChart.getLineData().getDataSetCount(); i++) {
LineDataSet dataSet = dataSets.get(i);
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
// if drawing circles is enabled for this dataset
if (dataSet.isDrawCirclesEnabled()) {
ArrayList<Entry> entries = dataSet.getYVals();
float[] positions = trans.generateTransformedValuesLine(
entries, mAnimator.getPhaseY());
for (int j = 0; j < positions.length * mAnimator.getPhaseX(); j += 2) {
mRenderPaint.setColor(dataSet.getCircleColor(j / 2));
float x = positions[j];
float y = positions[j + 1];
if (!mViewPortHandler.isInBoundsRight(x))
break;
// make sure the circles don't do shitty things outside
// bounds
if (!mViewPortHandler.isInBoundsLeft(x) || !mViewPortHandler.isInBoundsY(y))
continue;
c.drawCircle(x, y, dataSet.getCircleSize(),
mRenderPaint);
c.drawCircle(x, y,
dataSet.getCircleSize() / 2f,
mCirclePaintInner);
}
} // else do nothing
}
}
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
for (int i = 0; i < indices.length; i++) {
LineDataSet set = mChart.getLineData().getDataSetByIndex(indices[i]
.getDataSetIndex());
if (set == null)
continue;
mHighlightPaint.setColor(set.getHighLightColor());
int xIndex = indices[i].getXIndex(); // get the
// x-position
if (xIndex > mChart.getXChartMax() * mAnimator.getPhaseX())
continue;
float y = set.getYValForXIndex(xIndex) * mAnimator.getPhaseY(); // get
// the
// y-position
float[] pts = new float[] {
xIndex, mChart.getYChartMax(), xIndex, mChart.getYChartMin(), 0, y,
mChart.getXChartMax(), y
};
mChart.getTransformer(set.getAxisDependency()).pointValuesToPixel(pts);
// draw the highlight lines
c.drawLines(pts, mHighlightPaint);
}
}
}