Android - Fill the color between two lines using MPAndroidChart

后端 未结 2 1315
我在风中等你
我在风中等你 2020-12-06 14:54

I am using setFillFormatter, but it\'s not helping me and, setfillColor() crosses the second line(black) as there is no way to stop the first line(

相关标签:
2条回答
  • 2020-12-06 15:15

    Thanks David Rawson for pointing me towards LineChartRenderer. I am able to color the area between two lines.

    We need to make two major changes.

    1. Implement a custom FillFormator to return the dataset of another line.

      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. Implement a custom LineChartRenderer to draw and fill the enclosed path.

      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. At the end of the activity

      Set the MyFillFormatter to one of the LineDataSet passing another LineDataSet as argument.

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

    0 讨论(0)
  • 2020-12-06 15:17

    I have used Amit's accepted answer, but have modified his MyLineLegendRenderer so that you can also fill between two horizontal bezier lines - e.g., if you are using myDataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);

    I've also cleaned up the code a little bit - e.g, added comments, removed redundant code, etc.

    So here is my replacement for Amit's MyLineLegendRenderer class:

    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();
    
        }
    
    }
    

    You should use this class along with the other code in Amit's answer.

    0 讨论(0)
提交回复
热议问题