Skip to content

Instantly share code, notes, and snippets.

@joshfriend
Last active January 13, 2022 08:39
Show Gist options
  • Save joshfriend/90494fbe68e6696965fd to your computer and use it in GitHub Desktop.
Save joshfriend/90494fbe68e6696965fd to your computer and use it in GitHub Desktop.
Inverted fill line chart

How I hacked together a line chart with an inverted fill.

Looks like this:

example

package com.example.ui.charting.charts;
import android.content.Context;
import android.util.AttributeSet;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.utils.FillFormatter;
import com.example.ui.charting.renderer.InvertedFillLineChartRenderer;
public class InvertedFillLineChart extends LineChart {
public InvertedFillLineChart(Context context) {
super(context);
}
public InvertedFillLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public InvertedFillLineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
mRenderer = new InvertedFillLineChartRenderer(this, mAnimator, mViewPortHandler);
setFillFormatter(new DefaultFillFormatter());
}
// Copied from MPAndroidChart because it is not accessible publicly
protected 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 (!getAxis(dataSet.getAxisDependency()).isStartAtZeroEnabled()) {
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;
}
}
}
package com.example.ui.charting.renderer;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.interfaces.LineDataProvider;
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 InvertedFillLineChartRenderer extends LineChartRenderer {
public InvertedFillLineChartRenderer(LineDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
// Mostly copied verbatim except for line 35.
@Override
protected void drawLinearFill(Canvas c, LineDataSet dataSet, List<Entry> entries, int minx, int maxx,
Transformer trans) {
mRenderPaint.setStyle(Paint.Style.FILL);
mRenderPaint.setColor(dataSet.getFillColor());
// filled is drawn with less alpha
mRenderPaint.setAlpha(dataSet.getFillAlpha());
// Use highest point on chart for background fill limit
Path filled = generateFilledPath(
entries,
mChart.getYChartMax(),
minx,
maxx
);
trans.pathValueToPixel(filled);
c.drawPath(filled, mRenderPaint);
// restore alpha
mRenderPaint.setAlpha(255);
}
/**
* Copied verbatim from MPAndroidChart because method is private
*/
private Path generateFilledPath(List<Entry> entries, float fillMin, int from, int to) {
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
Path filled = new Path();
filled.moveTo(entries.get(from).getXIndex(), fillMin);
filled.lineTo(entries.get(from).getXIndex(), entries.get(from).getVal() * phaseY);
// create a new path
for (int x = from + 1, count = (int) Math.ceil((to - from) * phaseX + from); x < count; x++) {
Entry e = entries.get(x);
filled.lineTo(e.getXIndex(), e.getVal() * phaseY);
}
// close up
filled.lineTo(
entries.get(
Math.max(
Math.min((int) Math.ceil((to - from) * phaseX + from) - 1,
entries.size() - 1), 0)).getXIndex(), fillMin);
filled.close();
return filled;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment