【问题标题】:Android - Fill the color between two lines using MPAndroidChartAndroid - 使用 MPAndroidChart 填充两行之间的颜色
【发布时间】:2017-09-06 15:27:33
【问题描述】:

我正在使用 setFillFormatter,但它对我没有帮助,并且 setfillColor() 越过了第二行(黑色),因为无法在 Y 值处停止第一行(黄色)第二行。 我想实现这样的东西:

dataSet.setFillFormatter(new IFillFormatter() {

            @Override
            public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
                return //return Y value of the second line for current X of line being filled;
            }
        });

有没有什么方法可以为第一行的每个 X 找到第二行的 Y 值?我看到 dataSetdataProvider 都会为 getFillLinePosition 的每次调用返回固定值。

【问题讨论】:

  • 我认为你不能使用库中提供的方法来做到这一点。您可能必须编写一个自定义渲染器才能做到这一点。看LineChartRenderer
  • 是否可以填充 CombinedChart 上两行之间的空间?请在stackoverflow.com/questions/49986048/…查看我的问题

标签: android linechart mpandroidchart


【解决方案1】:

我使用了 Amit 的 accepted answer,但修改了他的 MyLineLegendRenderer,以便您也可以在两个 水平贝塞尔曲线 线之间填充 - 例如,如果您使用的是 myDataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);

我还对代码进行了一些清理——例如,添加 cmets、删除冗余代码等。

所以这是我替换 Amit 的 MyLineLegendRenderer 类:

import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.renderer.LineChartRenderer;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.ViewPortHandler;
import java.util.List;

public class MyLineLegendRenderer extends LineChartRenderer {

    MyLineLegendRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
        super(chart, animator, viewPortHandler);
    }

    // This method is same as its parent implementation. (Required so our version of generateFilledPath() is called.)
    @Override
    protected void drawLinearFill(Canvas c, ILineDataSet dataSet, Transformer trans, XBounds bounds) {

        final Path filled = mGenerateFilledPathBuffer;

        final int startingIndex = bounds.min;
        final int endingIndex = bounds.range + bounds.min;
        final int indexInterval = 128;

        int currentStartIndex;
        int currentEndIndex;
        int iterations = 0;

        // Doing this iteratively in order to avoid OutOfMemory errors that can happen on large bounds sets.
        do {
            currentStartIndex = startingIndex + (iterations * indexInterval);

            currentEndIndex = currentStartIndex + indexInterval;
            currentEndIndex = currentEndIndex > endingIndex ? endingIndex : currentEndIndex;

            if (currentStartIndex <= currentEndIndex) {
                generateFilledPath(dataSet, currentStartIndex, currentEndIndex, filled);

                trans.pathValueToPixel(filled);

                final Drawable drawable = dataSet.getFillDrawable();
                if (drawable != null) {
                    drawFilledPath(c, filled, drawable);
                }
                else {
                    drawFilledPath(c, filled, dataSet.getFillColor(), dataSet.getFillAlpha());
                }
            }

            iterations++;

        } while (currentStartIndex <= currentEndIndex);
    }

    // This method defines the perimeter of the area to be filled for horizontal bezier data sets.
    @Override
    protected void drawCubicFill(Canvas c, ILineDataSet dataSet, Path spline, Transformer trans, XBounds bounds) {

        final float phaseY = mAnimator.getPhaseY();

        //Call the custom method to retrieve the dataset for other line
        final List<Entry> boundaryEntries = ((MyFillFormatter)dataSet.getFillFormatter()).getFillLineBoundary();

        // We are currently at top-last point, so draw down to the last boundary point
        Entry boundaryEntry = boundaryEntries.get(bounds.min + bounds.range);
        spline.lineTo(boundaryEntry.getX(), boundaryEntry.getY() * phaseY);

        // Draw a cubic line going back through all the previous boundary points
        Entry prev = dataSet.getEntryForIndex(bounds.min + bounds.range);
        Entry cur = prev;
        for (int x = bounds.min + bounds.range; x >= bounds.min; x--) {

            prev = cur;
            cur = boundaryEntries.get(x);

            final float cpx = (prev.getX()) + (cur.getX() - prev.getX()) / 2.0f;

            spline.cubicTo(
                    cpx, prev.getY() * phaseY,
                    cpx, cur.getY() * phaseY,
                    cur.getX(), cur.getY() * phaseY);
        }

        // Join up the perimeter
        spline.close();

        trans.pathValueToPixel(spline);

        final Drawable drawable = dataSet.getFillDrawable();
        if (drawable != null) {
            drawFilledPath(c, spline, drawable);
        }
        else {
            drawFilledPath(c, spline, dataSet.getFillColor(), dataSet.getFillAlpha());
        }

    }

    // This method defines the perimeter of the area to be filled for straight-line (default) data sets.
    private void generateFilledPath(final ILineDataSet dataSet, final int startIndex, final int endIndex, final Path outputPath) {

        final float phaseY = mAnimator.getPhaseY();
        final Path filled = outputPath; // Not sure if this is required, but this is done in the original code so preserving the same technique here.
        filled.reset();

        //Call the custom method to retrieve the dataset for other line
        final List<Entry> boundaryEntries = ((MyFillFormatter)dataSet.getFillFormatter()).getFillLineBoundary();

        final Entry entry = dataSet.getEntryForIndex(startIndex);
        final Entry boundaryEntry = boundaryEntries.get(startIndex);

        // Move down to boundary of first entry
        filled.moveTo(entry.getX(), boundaryEntry.getY() * phaseY);

        // Draw line up to value of first entry
        filled.lineTo(entry.getX(), entry.getY() * phaseY);

        // Draw line across to the values of the next entries
        Entry currentEntry;
        for (int x = startIndex + 1; x <= endIndex; x++) {
            currentEntry = dataSet.getEntryForIndex(x);
            filled.lineTo(currentEntry.getX(), currentEntry.getY() * phaseY);
        }

        // Draw down to the boundary value of the last entry, then back to the first boundary value
        Entry boundaryEntry1;
        for (int x = endIndex; x > startIndex; x--) {
            boundaryEntry1 = boundaryEntries.get(x);
            filled.lineTo(boundaryEntry1.getX(), boundaryEntry1.getY() * phaseY);
        }

        // Join up the perimeter
        filled.close();

    }

}

您应该将此类与Amit's answer 中的其他代码一起使用。

【讨论】:

    【解决方案2】:

    感谢 David Rawson 将我指向LineChartRenderer。我可以为两条线之间的区域着色。

    我们需要做出两个重大改变。

    1. 实现自定义FillFormator 以返回另一行的数据集。

      public class MyFillFormatter implements IFillFormatter {
      private ILineDataSet boundaryDataSet;
      
      public MyFillFormatter() {
          this(null);
      }
      //Pass the dataset of other line in the Constructor 
      public MyFillFormatter(ILineDataSet boundaryDataSet) {
          this.boundaryDataSet = boundaryDataSet;
      }
      
      @Override
      public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
          return 0;
      }
      
      //Define a new method which is used in the LineChartRenderer
      public List<Entry> getFillLineBoundary() {
          if(boundaryDataSet != null) {
              return ((LineDataSet) boundaryDataSet).getValues();
          }
          return null;
      }}
      
    2. 实现自定义LineChartRenderer 来绘制和填充封闭路径。

      public class MyLineLegendRenderer extends LineChartRenderer {
      
      public MyLineLegendRenderer(LineDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
          super(chart, animator, viewPortHandler);
      }
      
      //This method is same as it's parent implemntation
      @Override
      protected void drawLinearFill(Canvas c, ILineDataSet dataSet, Transformer trans, XBounds bounds) {
          final Path filled = mGenerateFilledPathBuffer;
      
          final int startingIndex = bounds.min;
          final int endingIndex = bounds.range + bounds.min;
          final int indexInterval = 128;
      
          int currentStartIndex = 0;
          int currentEndIndex = indexInterval;
          int iterations = 0;
      
          // Doing this iteratively in order to avoid OutOfMemory errors that can happen on large bounds sets.
          do {
              currentStartIndex = startingIndex + (iterations * indexInterval);
              currentEndIndex = currentStartIndex + indexInterval;
              currentEndIndex = currentEndIndex > endingIndex ? endingIndex : currentEndIndex;
      
              if (currentStartIndex <= currentEndIndex) {
                  generateFilledPath(dataSet, currentStartIndex, currentEndIndex, filled);
      
                  trans.pathValueToPixel(filled);
      
                  final Drawable drawable = dataSet.getFillDrawable();
                  if (drawable != null) {
      
                      drawFilledPath(c, filled, drawable);
                  } else {
      
                      drawFilledPath(c, filled, dataSet.getFillColor(), dataSet.getFillAlpha());
                  }
              }
      
              iterations++;
      
          } while (currentStartIndex <= currentEndIndex);
      }
      
      //This is where we define the area to be filled.
      private void generateFilledPath(final ILineDataSet dataSet, final int startIndex, final int endIndex, final Path outputPath) {
      
          //Call the custom method to retrieve the dataset for other line
          final List<Entry> boundaryEntry = ((MyFillFormatter)dataSet.getFillFormatter()).getFillLineBoundary();
      
          final float phaseY = mAnimator.getPhaseY();    
          final Path filled = outputPath;
          filled.reset();
      
          final Entry entry = dataSet.getEntryForIndex(startIndex);
      
          filled.moveTo(entry.getX(), boundaryEntry.get(0).getY());
          filled.lineTo(entry.getX(), entry.getY() * phaseY);
      
          // create a new path
          Entry currentEntry = null;
          Entry previousEntry = null;
          for (int x = startIndex + 1; x <= endIndex; x++) {
      
              currentEntry = dataSet.getEntryForIndex(x);
              filled.lineTo(currentEntry.getX(), currentEntry.getY() * phaseY);
      
          }
      
          // close up
          if (currentEntry != null && previousEntry!= null) {
              filled.lineTo(currentEntry.getX(), previousEntry.getY());
          }
      
          //Draw the path towards the other line 
          for (int x = endIndex ; x > startIndex; x--) {
              previousEntry = boundaryEntry.get(x);
              filled.lineTo(previousEntry.getX(), previousEntry.getY() * phaseY);
          }
      
          filled.close();
      }}
      
    3. 活动结束时

      MyFillFormatter 设置为LineDataSet 之一,传递另一个LineDataSet 作为参数。

      lineDataSet2.setFillFormatter(new MyFillFormatter(LineDataSet1));
      
      mChart.setRenderer(new MyLineLegendRenderer(mChart, mChart.getAnimator(), mChart.getViewPortHandler()));
      

    【讨论】:

    • 您能否扩展您的答案以覆盖drawCubicFill(),因为正在调用该方法而不是drawLinearFill()。 (这是因为我打电话给dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER)。)
    • 我现在已经解决了。请参阅我对MyLineLegendRenderer - stackoverflow.com/a/50118570/1617737 扩展版本的回答
    • 正如它所写的那样,如果图表上有其他线条,则会收到 ClassCastException - DefaultFillFormatter 无法转换为 MyFillFormatter,因此我建议您在渲染器中添加一些检查,如果 FillFormatter是 instanceOf DefaultFillFormatter 使用超级方法而不是 MyFillFormatter 中的方法
    • 干得好!你知道如何根据 LineDataSet 条件自定义填充颜色吗?例如。如果我的线在 getFillLineBoundary() 上方,我想要蓝色,而绿色是我的线在该线下方
    • @Manuela 是的..您只需要在创建 lineDataSet 时设置这两个属性: -LineDataSet lineDataSet = new LineDataSet(projectionEntries, "label"); lineDataSet.setColor(ContextCompat.getColor(context, color1)); lineDataSet.setFillColor(ContextCompat.getColor(context, color2));
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-23
    • 2012-04-24
    • 1970-01-01
    • 2015-03-15
    • 1970-01-01
    相关资源
    最近更新 更多