package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import java.util.ArrayList;
/**
* Chart that draws lines, surfaces, circles, ...
*
* @author Philipp Jahoda
*/
public class LineChart extends BarLineChartBase {
/** the width of the highlighning line */
protected float mHighlightWidth = 3f;
/** paint for the inner circle of the value indicators */
protected Paint mCirclePaintInner;
public LineChart(Context context) {
super(context);
}
public LineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mCirclePaintInner = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaintInner.setStyle(Paint.Style.FILL);
mCirclePaintInner.setColor(Color.WHITE);
mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHighlightPaint.setStyle(Paint.Style.STROKE);
mHighlightPaint.setStrokeWidth(2f);
mHighlightPaint.setColor(Color.rgb(255, 187, 115));
}
/**
* Sets a LineData object as a model for the LineChart.
*
* @param data
*/
public void setData(LineData data) {
super.setData(data);
}
@Override
protected void drawHighlights() {
for (int i = 0; i < mIndicesToHightlight.length; i++) {
LineDataSet set = (LineDataSet) getDataSetByIndex(mIndicesToHightlight[i]
.getDataSetIndex());
mHighlightPaint.setColor(set.getHighLightColor());
int xIndex = mIndicesToHightlight[i].getXIndex(); // get the
// x-position
if (xIndex > mDeltaX * mPhaseX)
continue;
float y = set.getYValForXIndex(xIndex) * mPhaseY; // get the
// y-position
float[] pts = new float[] {
xIndex, mYChartMax, xIndex, mYChartMin, 0, y, mDeltaX, y
};
transformPointArray(pts);
// draw the highlight lines
mDrawCanvas.drawLines(pts, mHighlightPaint);
}
}
private class CPoint {
public float x = 0f;
public float y = 0f;
public float dx = 0f;
public float dy = 0f;
public CPoint(float x, float y) {
this.x = x;
this.y = y;
}
}
/**
* draws the given y values to the screen
*/
@Override
protected void drawData() {
ArrayList<LineDataSet> dataSets = (ArrayList<LineDataSet>) mCurrentData.getDataSets();
for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
LineDataSet dataSet = dataSets.get(i);
ArrayList<? extends Entry> entries = dataSet.getYVals();
mRenderPaint.setStrokeWidth(dataSet.getLineWidth());
mRenderPaint.setPathEffect(dataSet.getDashPathEffect());
// if drawing cubic lines is enabled
if (dataSet.isDrawCubicEnabled()) {
// get the color that is specified for this position from the
// DataSet
mRenderPaint.setColor(dataSet.getColor(i));
float intensity = dataSet.getCubicIntensity();
// the path for the cubic-spline
Path spline = new Path();
ArrayList<CPoint> points = new ArrayList<CPoint>();
for (Entry e : entries)
points.add(new CPoint(e.getXIndex(), e.getVal()));
if (points.size() > 1) {
for (int j = 0; j < points.size() * mPhaseX; 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 * mPhaseY);
}
else {
CPoint prev = points.get(j - 1);
spline.cubicTo(prev.x + prev.dx, (prev.y + prev.dy) * mPhaseY, point.x - point.dx,
(point.y - point.dy) * mPhaseY, point.x, point.y * mPhaseY);
}
}
}
// if filled is enabled, close the path
if (dataSet.isDrawFilledEnabled()) {
float fillMin = dataSet.getYMin() >= 0 ? mYChartMin : 0;
spline.lineTo((entries.size() - 1) * mPhaseX, fillMin);
spline.lineTo(0, fillMin);
spline.close();
mRenderPaint.setStyle(Paint.Style.FILL);
} else {
mRenderPaint.setStyle(Paint.Style.STROKE);
}
transformPath(spline);
mDrawCanvas.drawPath(spline, mRenderPaint);
// draw normal (straight) lines
} else {
mRenderPaint.setStyle(Paint.Style.STROKE);
float[] valuePoints = generateTransformedValuesLineScatter(entries);
for (int j = 0; j < (valuePoints.length - 2) * mPhaseX; j += 2) {
// get the color that is specified for this position from
// the DataSet, this will reuse colors, if the index is out
// of bounds
mRenderPaint.setColor(dataSet.getColor(j / 2));
if (isOffContentRight(valuePoints[j]))
break;
// make sure the lines don't do shitty things outside bounds
if (j != 0 && isOffContentLeft(valuePoints[j - 1])
&& isOffContentTop(valuePoints[j + 1])
&& isOffContentBottom(valuePoints[j + 1]))
continue;
mDrawCanvas.drawLine(valuePoints[j], valuePoints[j + 1], valuePoints[j + 2],
valuePoints[j + 3], mRenderPaint);
}
mRenderPaint.setPathEffect(null);
// if drawing filled is enabled
if (dataSet.isDrawFilledEnabled() && entries.size() > 0) {
// mDrawCanvas.drawVertices(VertexMode.TRIANGLE_STRIP,
// valuePoints.length, valuePoints, 0,
// null, 0, null, 0, null, 0, 0, paint);
mRenderPaint.setStyle(Paint.Style.FILL);
mRenderPaint.setColor(dataSet.getFillColor());
// filled is drawn with less alpha
mRenderPaint.setAlpha(dataSet.getFillAlpha());
float fillMin = dataSet.getYMin() >= 0 ? mYChartMin : 0;
Path filled = generateFilledPath(entries, fillMin);
transformPath(filled);
mDrawCanvas.drawPath(filled, mRenderPaint);
// restore alpha
mRenderPaint.setAlpha(255);
}
}
}
}
/**
* Generates the path that is used for filled drawing.
*
* @param entries
* @return
*/
private Path generateFilledPath(ArrayList<? extends Entry> entries, float fillMin) {
Path filled = new Path();
filled.moveTo(entries.get(0).getXIndex(), entries.get(0).getVal() * mPhaseY);
// create a new path
for (int x = 1; x < entries.size() * mPhaseX; x++) {
Entry e = entries.get(x);
filled.lineTo(e.getXIndex(), e.getVal() * mPhaseY);
}
// close up
filled.lineTo(entries.get((int) ((entries.size() - 1) * mPhaseX)).getXIndex(), fillMin);
filled.lineTo(entries.get(0).getXIndex(), fillMin);
filled.close();
return filled;
}
/**
* Calculates the middle point between two points and multiplies its
* coordinates with the given smoothness _Mulitplier.
*
* @param p1 First point
* @param p2 Second point
* @param _Result Resulting point
* @param mult Smoothness multiplier
*/
private void calculatePointDiff(PointF p1, PointF p2, PointF _Result, float mult) {
float diffX = p2.x - p1.x;
float diffY = p2.y - p1.y;
_Result.x = (p1.x + (diffX * mult));
_Result.y = (p1.y + (diffY * mult));
}
@Override
protected void drawValues() {
// if values are drawn
if (mDrawYValues && mCurrentData.getYValCount() < mMaxVisibleCount * mScaleX) {
ArrayList<LineDataSet> dataSets = (ArrayList<LineDataSet>) mCurrentData.getDataSets();
for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
LineDataSet dataSet = dataSets.get(i);
// make sure the values do not interfear with the circles
int valOffset = (int) (dataSet.getCircleSize() * 1.75f);
if (!dataSet.isDrawCirclesEnabled())
valOffset = valOffset / 2;
ArrayList<? extends Entry> entries = dataSet.getYVals();
float[] positions = generateTransformedValuesLineScatter(entries);
for (int j = 0; j < positions.length * mPhaseX; j += 2) {
if (isOffContentRight(positions[j]))
break;
if (isOffContentLeft(positions[j]) || isOffContentTop(positions[j + 1])
|| isOffContentBottom(positions[j + 1]))
continue;
float val = entries.get(j / 2).getVal();
if (mDrawUnitInChart) {
mDrawCanvas.drawText(mFormatValue.format(val) + mUnit, positions[j],
positions[j + 1]
- valOffset, mValuePaint);
} else {
mDrawCanvas.drawText(mFormatValue.format(val), positions[j],
positions[j + 1] - valOffset,
mValuePaint);
}
}
}
}
}
/**
* draws the circle value indicators
*/
@Override
protected void drawAdditional() {
mRenderPaint.setStyle(Paint.Style.FILL);
ArrayList<LineDataSet> dataSets = (ArrayList<LineDataSet>) mCurrentData.getDataSets();
for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
LineDataSet dataSet = dataSets.get(i);
// if drawing circles is enabled for this dataset
if (dataSet.isDrawCirclesEnabled()) {
ArrayList<? extends Entry> entries = dataSet.getYVals();
float[] positions = generateTransformedValuesLineScatter(entries);
for (int j = 0; j < positions.length * mPhaseX; j += 2) {
// Set the color for the currently drawn value. If the index
// is
// out of bounds, reuse colors.
mRenderPaint.setColor(dataSet.getCircleColor(j / 2));
if (isOffContentRight(positions[j]))
break;
// make sure the circles don't do shitty things outside
// bounds
if (isOffContentLeft(positions[j]) || isOffContentTop(positions[j + 1])
|| isOffContentBottom(positions[j + 1]))
continue;
mDrawCanvas.drawCircle(positions[j], positions[j + 1], dataSet.getCircleSize(),
mRenderPaint);
mDrawCanvas.drawCircle(positions[j], positions[j + 1],
dataSet.getCircleSize() / 2,
mCirclePaintInner);
}
} // else do nothing
}
}
/**
* set the width of the highlightning lines, default 3f
*
* @param width
*/
public void setHighlightLineWidth(float width) {
mHighlightWidth = width;
}
/**
* returns the width of the highlightning line, default 3f
*
* @return
*/
public float getHighlightLineWidth() {
return mHighlightWidth;
}
@Override
public void setPaint(Paint p, int which) {
super.setPaint(p, which);
switch (which) {
case PAINT_CIRCLES_INNER:
mCirclePaintInner = p;
break;
}
}
@Override
public Paint getPaint(int which) {
Paint p = super.getPaint(which);
if (p != null)
return p;
switch (which) {
case PAINT_CIRCLES_INNER:
return mCirclePaintInner;
}
return null;
}
}