content: 
  package com.github.mikephil.charting.charts;
  
  import android.content.Context;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Path;
  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 com.github.mikephil.charting.utils.FillFormatter;
  
  import java.util.ArrayList;
  
  /**
   * Chart that draws lines, surfaces, circles, ...
   * 
   * @author Philipp Jahoda
   */
  public class LineChart extends BarLineChartBase<LineData> {
  
      /** the width of the highlighning line */
      protected float mHighlightWidth = 3f;
  
      /** paint for the inner circle of the value indicators */
      protected Paint mCirclePaintInner;
  
      private FillFormatter mFillFormatter;
  
      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();
  
          mFillFormatter = new DefaultFillFormatter();
  
          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));        
      }
  
      @Override
      protected void calcMinMax(boolean fixedValues) {
          super.calcMinMax(fixedValues);
  
          // // if there is only one value in the chart
          // if (mOriginalData.getYValCount() == 1
          // || mOriginalData.getYValCount() <= mOriginalData.getDataSetCount()) {
          // mDeltaX = 1;
          // }
  
          if (mDeltaX == 0 && mData.getYValCount() > 0)
              mDeltaX = 1;
      }
  
      @Override
      protected void drawHighlights() {
  
          for (int i = 0; i < mIndicesToHightlight.length; i++) {
  
              LineDataSet set = mData.getDataSetByIndex(mIndicesToHightlight[i]
                      .getDataSetIndex());
  
              if (set == null)
                  continue;
  
              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
              };
  
              mTrans.pointValuesToPixel(pts);
              // draw the highlight lines
              mDrawCanvas.drawLines(pts, mHighlightPaint);
          }
      }
  
      /**
       * 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;
          }
      }
  
      /**
       * draws the given y values to the screen
       */
      @Override
      protected void drawData() {
  
          ArrayList<LineDataSet> dataSets = mData.getDataSets();
  
          for (int i = 0; i < mData.getDataSetCount(); i++) {
  
              LineDataSet dataSet = dataSets.get(i);
              ArrayList<Entry> entries = dataSet.getYVals();
  
              if (entries.size() < 1)
                  continue;
  
              mRenderPaint.setStrokeWidth(dataSet.getLineWidth());
              mRenderPaint.setPathEffect(dataSet.getDashPathEffect());
  
              // if drawing cubic lines is enabled
              if (dataSet.isDrawCubicEnabled()) {
  				drawCubic(dataSet, entries);
  
                  // draw normal (straight) lines
              } else {
  				drawLinear(dataSet, entries);
              }
  
              mRenderPaint.setPathEffect(null);
          }
      }
  
  	protected void drawCubic(LineDataSet dataSet, ArrayList<Entry> entries)
  	{
  		// get the color that is specified for this position from the
  		// DataSet
  		mRenderPaint.setColor(dataSet.getColor());
  
  		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() * 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()) {
  			drawCubicFill(dataSet, spline);
  		} else {
  			mRenderPaint.setStyle(Paint.Style.STROKE);
  		}
  
  		mTrans.pathValueToPixel(spline);
  
  		mDrawCanvas.drawPath(spline, mRenderPaint);
  	}
  
  	protected void drawCubicFill(LineDataSet dataSet, Path spline)
  	{
  		float fillMin = mFillFormatter
  				.getFillLinePosition(dataSet, mData, mYChartMax, mYChartMin);
  
  		spline.lineTo((mDeltaX + 1) * mPhaseX, fillMin);
  		spline.lineTo(0, fillMin);
  		spline.close();
  
  		mRenderPaint.setStyle(Paint.Style.FILL);
  	}
  
  	protected void drawLinear(LineDataSet dataSet, ArrayList<Entry> entries)
  	{
  		mRenderPaint.setStyle(Paint.Style.STROKE);
  
  		// more than 1 color
  		if (dataSet.getColors() == null || dataSet.getColors().size() > 1) {
  
  			float[] valuePoints = mTrans.generateTransformedValuesLineScatter(entries, mPhaseY);
  
  			for (int j = 0; j < (valuePoints.length - 2) * mPhaseX; 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;
  
  				// get the color that is set for this line-segment
  				mRenderPaint.setColor(dataSet.getColor(j / 2));
  
  				mDrawCanvas.drawLine(valuePoints[j], valuePoints[j + 1],
  						valuePoints[j + 2], valuePoints[j + 3], mRenderPaint);
  			}
  
  		} else { // only one color per dataset
  
  			mRenderPaint.setColor(dataSet.getColor());
  
  			Path line = generateLinePath(entries);
  			mTrans.pathValueToPixel(line);
  
  			mDrawCanvas.drawPath(line, mRenderPaint);
  		}
  
  		mRenderPaint.setPathEffect(null);
  
  		// if drawing filled is enabled
  		if (dataSet.isDrawFilledEnabled() && entries.size() > 0) {
  			drawLinearFill(dataSet, entries);
  		}
  	}
  
  	protected void drawLinearFill(LineDataSet dataSet, ArrayList<Entry> entries)
  	{
  		// 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());
  
  		// mRenderPaint.setShader(dataSet.getShader());
  
  		Path filled = generateFilledPath(entries,
  				mFillFormatter.getFillLinePosition(dataSet, mData, mYChartMax,
  						mYChartMin));
  
  		mTrans.pathValueToPixel(filled);
  
  		mDrawCanvas.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) {
  
          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;
      }
  
      /**
       * Generates the path that is used for drawing a single line.
       * 
       * @param entries
       * @return
       */
      private Path generateLinePath(ArrayList<Entry> entries) {
  
          Path line = new Path();
          line.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);
              line.lineTo(e.getXIndex(), e.getVal() * mPhaseY);
          }
  
          return line;
      }
  
      @Override
      protected void drawValues() {
  
          // if values are drawn
          if (mDrawYValues && mData.getYValCount() < mMaxVisibleCount * mTrans.getScaleX()) {
  
              ArrayList<LineDataSet> dataSets = mData.getDataSets();
  
              for (int i = 0; i < mData.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<Entry> entries = dataSet.getYVals();
  
                  float[] positions = mTrans.generateTransformedValuesLineScatter(entries, mPhaseY);
  
                  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(mValueFormatter.getFormattedValue(val) + mUnit,
                                  positions[j],
                                  positions[j + 1]
                                          - valOffset, mValuePaint);
                      } else {
  
                          mDrawCanvas.drawText(mValueFormatter.getFormattedValue(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 = mData.getDataSets();
  
          for (int i = 0; i < mData.getDataSetCount(); i++) {
  
              LineDataSet dataSet = dataSets.get(i);
  
              // if drawing circles is enabled for this dataset
              if (dataSet.isDrawCirclesEnabled()) {
  
                  ArrayList<Entry> entries = dataSet.getYVals();
  
                  float[] positions = mTrans.generateTransformedValuesLineScatter(entries, mPhaseY);
  
                  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() / 2f,
                              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;
      }
  
      /**
       * Sets a custom FillFormatter to the chart that handles the position of the
       * filled-line for each DataSet. Set this to null to use the default logic.
       * 
       * @param formatter
       */
      public void setFillFormatter(FillFormatter formatter) {
  
          if (formatter == null)
              formatter = new DefaultFillFormatter();
  
          mFillFormatter = formatter;
      }
  
      /**
       * Default formatter that calculates the position of the filled line.
       * 
       * @author Philipp Jahoda
       */
      private class DefaultFillFormatter implements FillFormatter {
  
          @Override
          public float getFillLinePosition(LineDataSet dataSet, LineData data,
                  float chartMaxY, float chartMinY) {
  
              float fillMin = 0f;
  
              if (dataSet.getYMax() > 0 && dataSet.getYMin() < 0) {
                  fillMin = 0f;
              } else {
  
                  if (!mStartAtZero) {
  
                      float max, min;
  
                      if (data.getYMax() > 0)
                          max = 0f;
                      else
                          max = chartMaxY;
                      if (data.getYMin() < 0)
                          min = 0f;
                      else
                          min = chartMinY;
  
                      fillMin = dataSet.getYMin() >= 0 ? min : max;
                  } else {
                      fillMin = 0f;
                  }
  
              }
  
              return fillMin;
          }
      }
  }
  