Android Open Source - customhellochartdemo Line Chart Renderer






From Project

Back to project page customhellochartdemo.

License

The source code is released under:

Apache License

If you think the Android project customhellochartdemo listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package lecho.lib.hellocharts.renderer;
/*  w w w . j av a 2  s  . c  om*/
import lecho.lib.hellocharts.ChartComputator;
import lecho.lib.hellocharts.model.Line;
import lecho.lib.hellocharts.model.LineChartData;
import lecho.lib.hellocharts.model.PointValue;
import lecho.lib.hellocharts.model.ValueShape;
import lecho.lib.hellocharts.provider.LineChartDataProvider;
import lecho.lib.hellocharts.util.Utils;
import lecho.lib.hellocharts.view.Chart;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;

/**
 * 
 * Renderer for line chart. Can draw lines, cubic lines, filled area chart and scattered chart.
 * 
 * @author Leszek Wach
 * 
 */
public class LineChartRenderer extends AbstractChartRenderer {
  private static final float LINE_SMOOTHNES = 0.16f;
  private static final int DEFAULT_LINE_STROKE_WIDTH_DP = 3;
  private static final int DEFAULT_TOUCH_TOLLERANCE_MARGIN_DP = 4;

  private static final int MODE_DRAW = 0;
  private static final int MODE_HIGHLIGHT = 1;

  private LineChartDataProvider dataProvider;

  private int checkPrecission;

  private float baseValue;

  private int touchTolleranceMargin;
  private Path path = new Path();
  private Paint linePaint = new Paint();
  private Paint pointPaint = new Paint();
  private float[] valuesBuff = new float[2];

  /**
   * Not hardware accelerated bitmap used to draw Path(smooth lines and filled area). Bitmap has size of contentRect
   * so it is usually smaller than the view so you should used relative coordinates to draw on it.
   */
  private Bitmap swBitmap;
  /**
   * Canvas to draw on secondBitmap.
   */
  private Canvas swCanvas = new Canvas();

  public LineChartRenderer(Context context, Chart chart, LineChartDataProvider dataProvider) {
    super(context, chart);
    this.dataProvider = dataProvider;

    touchTolleranceMargin = Utils.dp2px(density, DEFAULT_TOUCH_TOLLERANCE_MARGIN_DP);

    linePaint.setAntiAlias(true);
    linePaint.setStyle(Paint.Style.STROKE);
    linePaint.setStrokeCap(Cap.ROUND);
    linePaint.setStrokeWidth(Utils.dp2px(density, DEFAULT_LINE_STROKE_WIDTH_DP));

    pointPaint.setAntiAlias(true);
    pointPaint.setStyle(Paint.Style.FILL);

    checkPrecission = Utils.dp2px(density, 2);

  }

  @Override
  public void initMaxViewport() {
    if (isViewportCalculationEnabled) {
      calculateMaxViewport();
      chart.getChartComputator().setMaxViewport(tempMaxViewport);
    }
  }

  @Override
  public void initDataMeasuremetns() {
    final ChartComputator computator = chart.getChartComputator();

    computator.setInternalMargin(calculateContentAreaMargin());

    if (computator.getChartWidth() > 0 && computator.getChartHeight() > 0) {
      swBitmap = Bitmap.createBitmap(computator.getChartWidth(), computator.getChartHeight(),
          Bitmap.Config.ARGB_8888);
      swCanvas.setBitmap(swBitmap);
    }
  }

  @Override
  public void initDataAttributes() {
    super.initDataAttributes();

    LineChartData data = dataProvider.getLineChartData();

    // Set base value for this chart - default is 0.
    baseValue = data.getBaseValue();

  }

  @Override
  public void draw(Canvas canvas) {
    final LineChartData data = dataProvider.getLineChartData();

    final Canvas drawCanvas;

    // swBitmap can be null if chart is rendered in layout editor. In that case use default canvas and not swCanvas.
    if (null != swBitmap) {
      drawCanvas = swCanvas;
      drawCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
    } else {
      drawCanvas = canvas;
    }

    for (Line line : data.getLines()) {
      if (line.hasLines()) {
        if (line.isCubic()) {
          drawSmoothPath(drawCanvas, line);
        } else {
          drawPath(drawCanvas, line);
        }
      }
    }

    if (null != swBitmap) {
      canvas.drawBitmap(swBitmap, 0, 0, null);
    }

  }

  @Override
  public void drawUnclipped(Canvas canvas) {
    final LineChartData data = dataProvider.getLineChartData();
    int lineIndex = 0;
    for (Line line : data.getLines()) {
      if (line.hasPoints()) {
        drawPoints(canvas, line, lineIndex, MODE_DRAW);
      }
      ++lineIndex;
    }
    if (isTouched()) {
      // Redraw touched point to bring it to the front
      highlightPoints(canvas);
    }
  }

  @Override
  public boolean checkTouch(float touchX, float touchY) {
    selectedValue.clear();
    final LineChartData data = dataProvider.getLineChartData();
    final ChartComputator computator = chart.getChartComputator();
    int lineIndex = 0;
    for (Line line : data.getLines()) {
      int pointRadius = Utils.dp2px(density, line.getPointRadius());
      int valueIndex = 0;
      for (PointValue pointValue : line.getValues()) {
        final float rawValueX = computator.computeRawX(pointValue.getX());
        final float rawValueY = computator.computeRawY(pointValue.getY());
        if (isInArea(rawValueX, rawValueY, touchX, touchY, pointRadius + touchTolleranceMargin)) {
          selectedValue.set(lineIndex, valueIndex, 0);
        }
        ++valueIndex;
      }
      ++lineIndex;
    }
    return isTouched();
  }

  private void calculateMaxViewport() {
    tempMaxViewport.set(Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MAX_VALUE);
    LineChartData data = dataProvider.getLineChartData();

    for (Line line : data.getLines()) {
      // Calculate max and min for viewport.
      for (PointValue pointValue : line.getValues()) {
        if (pointValue.getX() < tempMaxViewport.left) {
          tempMaxViewport.left = pointValue.getX();
        }
        if (pointValue.getX() > tempMaxViewport.right) {
          tempMaxViewport.right = pointValue.getX();
        }
        if (pointValue.getY() < tempMaxViewport.bottom) {
          tempMaxViewport.bottom = pointValue.getY();
        }
        if (pointValue.getY() > tempMaxViewport.top) {
          tempMaxViewport.top = pointValue.getY();
        }

      }
    }
  }

  private int calculateContentAreaMargin() {
    int contentAreaMargin = 0;
    final LineChartData data = dataProvider.getLineChartData();
    for (Line line : data.getLines()) {
      if (line.hasPoints()) {
        int margin = line.getPointRadius() + DEFAULT_TOUCH_TOLLERANCE_MARGIN_DP;
        if (margin > contentAreaMargin) {
          contentAreaMargin = margin;
        }
      }
    }
    return Utils.dp2px(density, contentAreaMargin);
  }

  /**
   * Draws lines, uses path for drawing filled area on secondCanvas. Line is drawn with canvas.drawLines() method.
   */
  private void drawPath(Canvas canvas, final Line line) {
    final ChartComputator computator = chart.getChartComputator();

    prepareLinePaint(line);

    int valueIndex = 0;
    for (PointValue pointValue : line.getValues()) {

      final float rawX = computator.computeRawX(pointValue.getX());
      final float rawY = computator.computeRawY(pointValue.getY());

      if (valueIndex == 0) {
        path.moveTo(rawX, rawY);
      } else {
        path.lineTo(rawX, rawY);
      }

      ++valueIndex;

    }

    canvas.drawPath(path, linePaint);

    if (line.isFilled()) {
      drawArea(canvas, line.getAreaTransparency());
    }

    path.reset();
  }

  /**
   * Draws Besier's curve. Uses path so drawing has to be done on secondCanvas to avoid problem with hardware
   * acceleration.
   */
  private void drawSmoothPath(Canvas canvas, final Line line) {
    final ChartComputator computator = chart.getChartComputator();

    prepareLinePaint(line);

    final int lineSize = line.getValues().size();
    float prepreviousPointX = Float.NaN;
    float prepreviousPointY = Float.NaN;
    float previousPointX = Float.NaN;
    float previousPointY = Float.NaN;
    float currentPointX = Float.NaN;
    float currentPointY = Float.NaN;
    float nextPointX = Float.NaN;
    float nextPointY = Float.NaN;
    for (int valueIndex = 0; valueIndex < lineSize; ++valueIndex) {
      if (Float.isNaN(currentPointX)) {
        PointValue linePoint = line.getValues().get(valueIndex);
        currentPointX = computator.computeRawX(linePoint.getX());
        currentPointY = computator.computeRawY(linePoint.getY());
      }
      if (Float.isNaN(previousPointX)) {
        if (valueIndex > 0) {
          PointValue linePoint = line.getValues().get(valueIndex - 1);
          previousPointX = computator.computeRawX(linePoint.getX());
          previousPointY = computator.computeRawY(linePoint.getY());
        } else {
          previousPointX = currentPointX;
          previousPointY = currentPointY;
        }
      }

      if (Float.isNaN(prepreviousPointX)) {
        if (valueIndex > 1) {
          PointValue linePoint = line.getValues().get(valueIndex - 2);
          prepreviousPointX = computator.computeRawX(linePoint.getX());
          prepreviousPointY = computator.computeRawY(linePoint.getY());
        } else {
          prepreviousPointX = previousPointX;
          prepreviousPointY = previousPointY;
        }
      }

      // nextPoint is always new one or it is equal currentPoint.
      if (valueIndex < lineSize - 1) {
        PointValue linePoint = line.getValues().get(valueIndex + 1);
        nextPointX = computator.computeRawX(linePoint.getX());
        nextPointY = computator.computeRawY(linePoint.getY());
      } else {
        nextPointX = currentPointX;
        nextPointY = currentPointY;
      }

      // Calculate control points.
      final float firstDiffX = (currentPointX - prepreviousPointX);
      final float firstDiffY = (currentPointY - prepreviousPointY);
      final float secondDiffX = (nextPointX - previousPointX);
      final float secondDiffY = (nextPointY - previousPointY);
      final float firstControlPointX = previousPointX + (LINE_SMOOTHNES * firstDiffX);
      final float firstControlPointY = previousPointY + (LINE_SMOOTHNES * firstDiffY);
      final float secondControlPointX = currentPointX - (LINE_SMOOTHNES * secondDiffX);
      final float secondControlPointY = currentPointY - (LINE_SMOOTHNES * secondDiffY);

      if (valueIndex == 0) {
        // Move to start point.
        path.moveTo(currentPointX, currentPointY);
      } else {
        path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,
            currentPointX, currentPointY);
      }

      // Shift values by one back to prevent recalculation of values that have
      // been already calculated.
      prepreviousPointX = previousPointX;
      prepreviousPointY = previousPointY;
      previousPointX = currentPointX;
      previousPointY = currentPointY;
      currentPointX = nextPointX;
      currentPointY = nextPointY;
    }

    canvas.drawPath(path, linePaint);
    if (line.isFilled()) {
      drawArea(canvas, line.getAreaTransparency());
    }
    path.reset();
  }

  private void prepareLinePaint(final Line line) {
    linePaint.setStrokeWidth(Utils.dp2px(density, line.getStrokeWidth()));
    linePaint.setColor(line.getColor());
    PathEffect pathEffect = line.getPathEffect();
    if (null != pathEffect) {
      linePaint.setPathEffect(pathEffect);
    }
  }

  // TODO Drawing points can be done in the same loop as drawing lines but it
  // may cause problems in the future with
  // implementing point styles.
  private void drawPoints(Canvas canvas, Line line, int lineIndex, int mode) {
    final ChartComputator computator = chart.getChartComputator();
    pointPaint.setColor(line.getColor());
    int valueIndex = 0;
    for (PointValue pointValue : line.getValues()) {
      int pointRadius = Utils.dp2px(density, line.getPointRadius());
      final float rawX = computator.computeRawX(pointValue.getX());
      final float rawY = computator.computeRawY(pointValue.getY());
      if (computator.isWithinContentRect(rawX, rawY, checkPrecission)) {
        // Draw points only if they are within contentRect, using contentRect instead of viewport to avoid some
        // float rounding problems.
        if (MODE_DRAW == mode) {
          drawPoint(canvas, line, pointValue, rawX, rawY, pointRadius);
          if (line.hasLabels()) {
            drawLabel(canvas, line, pointValue, rawX, rawY, pointRadius + labelOffset);
          }
        } else if (MODE_HIGHLIGHT == mode) {
          highlightPoint(canvas, line, pointValue, rawX, rawY, lineIndex, valueIndex);
        } else {
          throw new IllegalStateException("Cannot process points in mode: " + mode);
        }
      }
      ++valueIndex;
    }
  }

  private void drawPoint(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY, float pointRadius) {
    if (ValueShape.SQUARE.equals(line.getShape())) {
      canvas.drawRect(rawX - pointRadius, rawY - pointRadius, rawX + pointRadius, rawY + pointRadius, pointPaint);
    } else if (ValueShape.CIRCLE.equals(line.getShape())) {
      canvas.drawCircle(rawX, rawY, pointRadius, pointPaint);
    } else {
      throw new IllegalArgumentException("Invalid point shape: " + line.getShape());
    }
  }

  private void highlightPoints(Canvas canvas) {
    int lineIndex = selectedValue.getFirstIndex();
    Line line = dataProvider.getLineChartData().getLines().get(lineIndex);
    drawPoints(canvas, line, lineIndex, MODE_HIGHLIGHT);
  }

  private void highlightPoint(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY, int lineIndex,
      int valueIndex) {
    if (selectedValue.getFirstIndex() == lineIndex && selectedValue.getSecondIndex() == valueIndex) {
      int pointRadius = Utils.dp2px(density, line.getPointRadius());
      pointPaint.setColor(line.getDarkenColor());
      drawPoint(canvas, line, pointValue, rawX, rawY, pointRadius + touchTolleranceMargin);
      if (line.hasLabels() || line.hasLabelsOnlyForSelected()) {
        drawLabel(canvas, line, pointValue, rawX, rawY, pointRadius + labelOffset);
      }
    }
  }

  private void drawLabel(Canvas canvas, Line line, PointValue pointValue, float rawX, float rawY, float offset) {
    final ChartComputator computator = chart.getChartComputator();
    final Rect contentRect = computator.getContentRect();
    valuesBuff[0] = pointValue.getX();
    valuesBuff[1] = pointValue.getY();

    final int numChars = line.getFormatter().formatValue(labelBuffer, valuesBuff, pointValue.getLabel());

    if (numChars == 0) {
      // No need to draw empty label
      return;
    }

    final float labelWidth = labelPaint.measureText(labelBuffer, labelBuffer.length - numChars, numChars);
    final int labelHeight = Math.abs(fontMetrics.ascent);
    float left = rawX - labelWidth / 2 - labelMargin;
    float right = rawX + labelWidth / 2 + labelMargin;

    float top;
    float bottom;

    if (pointValue.getY() >= baseValue) {
      top = rawY - offset - labelHeight - labelMargin * 2;
      bottom = rawY - offset;
    } else {
      top = rawY + offset;
      bottom = rawY + offset + labelHeight + labelMargin * 2;
    }

    if (top < contentRect.top) {
      top = rawY + offset;
      bottom = rawY + offset + labelHeight + labelMargin * 2;
    }
    if (bottom > contentRect.bottom) {
      top = rawY - offset - labelHeight - labelMargin * 2;
      bottom = rawY - offset;
    }
    if (left < contentRect.left) {
      left = rawX;
      right = rawX + labelWidth + labelMargin * 2;
    }
    if (right > contentRect.right) {
      left = rawX - labelWidth - labelMargin * 2;
      right = rawX;
    }

    labelBackgroundRect.set(left, top, right, bottom);
    drawLabelTextAndBackground(canvas, labelBuffer, labelBuffer.length - numChars, numChars, line.getDarkenColor());
  }

  private void drawArea(Canvas canvas, int transparency) {
    final ChartComputator computator = chart.getChartComputator();
    final Rect contentRect = computator.getContentRect();

    float baseRawValue = computator.computeRawY(baseValue);
    baseRawValue = Math.min(contentRect.bottom, Math.max(baseRawValue, contentRect.top));

    path.lineTo(contentRect.right, baseRawValue);
    path.lineTo(contentRect.left, baseRawValue);
    path.close();
    linePaint.setStyle(Paint.Style.FILL);
    linePaint.setAlpha(transparency);
    canvas.drawPath(path, linePaint);
    linePaint.setStyle(Paint.Style.STROKE);
  }

  private boolean isInArea(float x, float y, float touchX, float touchY, float radius) {
    float diffX = touchX - x;
    float diffY = touchY - y;
    return Math.pow(diffX, 2) + Math.pow(diffY, 2) <= 2 * Math.pow(radius, 2);
  }

}




Java Source Code List

lecho.lib.hellocharts.ChartComputator.java
lecho.lib.hellocharts.DummyChartAnimationListener.java
lecho.lib.hellocharts.DummyVieportChangeListener.java
lecho.lib.hellocharts.PreviewChartComputator.java
lecho.lib.hellocharts.ViewportChangeListener.java
lecho.lib.hellocharts.animation.ChartAnimationListener.java
lecho.lib.hellocharts.animation.ChartDataAnimatorV14.java
lecho.lib.hellocharts.animation.ChartDataAnimatorV8.java
lecho.lib.hellocharts.animation.ChartDataAnimator.java
lecho.lib.hellocharts.animation.ChartViewportAnimatorV14.java
lecho.lib.hellocharts.animation.ChartViewportAnimatorV8.java
lecho.lib.hellocharts.animation.ChartViewportAnimator.java
lecho.lib.hellocharts.animation.PieChartRotationAnimatorV14.java
lecho.lib.hellocharts.animation.PieChartRotationAnimatorV8.java
lecho.lib.hellocharts.animation.PieChartRotationAnimator.java
lecho.lib.hellocharts.gesture.ChartScroller.java
lecho.lib.hellocharts.gesture.ChartTouchHandler.java
lecho.lib.hellocharts.gesture.ChartZoomer.java
lecho.lib.hellocharts.gesture.ContainerScrollType.java
lecho.lib.hellocharts.gesture.PieChartTouchHandler.java
lecho.lib.hellocharts.gesture.PreviewChartTouchHandler.java
lecho.lib.hellocharts.gesture.ZoomType.java
lecho.lib.hellocharts.gesture.ZoomerCompat.java
lecho.lib.hellocharts.model.AbstractChartData.java
lecho.lib.hellocharts.model.ArcValue.java
lecho.lib.hellocharts.model.AxisValue.java
lecho.lib.hellocharts.model.Axis.java
lecho.lib.hellocharts.model.BubbleChartData.java
lecho.lib.hellocharts.model.BubbleValue.java
lecho.lib.hellocharts.model.ChartData.java
lecho.lib.hellocharts.model.ColumnChartData.java
lecho.lib.hellocharts.model.ColumnValue.java
lecho.lib.hellocharts.model.Column.java
lecho.lib.hellocharts.model.ComboLineColumnChartData.java
lecho.lib.hellocharts.model.LineChartData.java
lecho.lib.hellocharts.model.Line.java
lecho.lib.hellocharts.model.PieChartData.java
lecho.lib.hellocharts.model.PointValue.java
lecho.lib.hellocharts.model.SelectedValue.java
lecho.lib.hellocharts.model.SimpleValueFormatter.java
lecho.lib.hellocharts.model.ValueFormatter.java
lecho.lib.hellocharts.model.ValueShape.java
lecho.lib.hellocharts.model.Viewport.java
lecho.lib.hellocharts.provider.BubbleChartDataProvider.java
lecho.lib.hellocharts.provider.ColumnChartDataProvider.java
lecho.lib.hellocharts.provider.ComboLineColumnChartDataProvider.java
lecho.lib.hellocharts.provider.LineChartDataProvider.java
lecho.lib.hellocharts.provider.PieChartDataProvider.java
lecho.lib.hellocharts.renderer.AbstractChartRenderer.java
lecho.lib.hellocharts.renderer.AxesRenderer.java
lecho.lib.hellocharts.renderer.BubbleChartRenderer.java
lecho.lib.hellocharts.renderer.ChartRenderer.java
lecho.lib.hellocharts.renderer.ColumnChartRenderer.java
lecho.lib.hellocharts.renderer.ComboLineColumnChartRenderer.java
lecho.lib.hellocharts.renderer.LineChartRenderer.java
lecho.lib.hellocharts.renderer.PieChartRenderer.java
lecho.lib.hellocharts.renderer.PreviewColumnChartRenderer.java
lecho.lib.hellocharts.renderer.PreviewLineChartRenderer.java
lecho.lib.hellocharts.samples.AboutActivity.java
lecho.lib.hellocharts.samples.BrokenLineChartActivity.java
lecho.lib.hellocharts.samples.BrokenLineView.java
lecho.lib.hellocharts.samples.BubbleChartActivity.java
lecho.lib.hellocharts.samples.ColumnChartActivity.java
lecho.lib.hellocharts.samples.ComboLineColumnChartActivity.java
lecho.lib.hellocharts.samples.GoodBadChartActivity.java
lecho.lib.hellocharts.samples.LineChartActivity.java
lecho.lib.hellocharts.samples.LineColumnDependencyActivity.java
lecho.lib.hellocharts.samples.MainActivity.java
lecho.lib.hellocharts.samples.PieChartActivity.java
lecho.lib.hellocharts.samples.PreviewColumnChartActivity.java
lecho.lib.hellocharts.samples.PreviewLineChartActivity.java
lecho.lib.hellocharts.samples.SpeedChartActivity.java
lecho.lib.hellocharts.samples.TempoChartActivity.java
lecho.lib.hellocharts.samples.ViewPagerChartsActivity.java
lecho.lib.hellocharts.util.AxisAutoValues.java
lecho.lib.hellocharts.util.Utils.java
lecho.lib.hellocharts.view.AbstractChartView.java
lecho.lib.hellocharts.view.BubbleChartView.java
lecho.lib.hellocharts.view.Chart.java
lecho.lib.hellocharts.view.ColumnChartView.java
lecho.lib.hellocharts.view.ComboLineColumnChartView.java
lecho.lib.hellocharts.view.LineChartView.java
lecho.lib.hellocharts.view.PieChartView.java
lecho.lib.hellocharts.view.PreviewColumnChartView.java
lecho.lib.hellocharts.view.PreviewLineChartView.java
lecho.lib.hellocharts.view.hack.HackyDrawerLayout.java
lecho.lib.hellocharts.view.hack.HackyViewPager.java