4

Background

I'm using MPAndroidChart to show a relatively simple bar chart.

The problem

There are 2 things I need to set, that I can't figure out how to customize:

  1. Instead of simple values, I need to add text per each bar, which by itself is also styled.

  2. On top of each bar, I need to put various types of drawable that cover it in width (for example blue with height of 2dp in one bar, or yellow gradient with same height on another bar).

Here's a demonstration of what I need to do:

enter image description here

What I've found

  1. I've checked the docs, but only thing I've found so far is to put the values above the bar, using setDrawValueAboveBar : https://github.com/PhilJay/MPAndroidChart/wiki/Specific-Chart-Settings-&-Styling

  2. I know I can also add icons, by using setDrawIcons , but this doesn't seem to work for drawables that should take entire bar width.

The questions

As I wrote, I'd like to know if the above are possible, and how:

  1. How can I set a customized, styled value above each bar ?

  2. How can I set a different drawable to "sit" on top of each bar?

  3. If #2 is not possible (and maybe even if it is), is it possible to set a drawable to be the bar itself? For example, some bars would be a solid gray color, and some bars would have a gradient yellow drawable?

android developer
  • 114,585
  • 152
  • 739
  • 1,270

1 Answers1

6

You need custom bar BarChartRenderer to achieve this. I have provided a rough sample. Hope it helps.

Code for setting the barchart

    public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // custom colors that you want on top of the bars
        ArrayList<Integer> myColors = new ArrayList<>();
        myColors.add(Color.BLACK);
        myColors.add(Color.YELLOW);
        myColors.add(Color.BLUE);
        myColors.add(Color.DKGRAY);
        myColors.add(Color.GREEN);
        myColors.add(Color.GRAY);

        String[] myText = {"A Round", "B Round", "C Round", "D Round", "E Round", "F Round"};


        BarChart mChart = (BarChart) findViewById(R.id.barChart);
        mChart.setDrawBarShadow(false);
        mChart.getDescription().setEnabled(false);
        mChart.setDrawGridBackground(false);

        XAxis xaxis = mChart.getXAxis();
        xaxis.setDrawGridLines(false);
        xaxis.setPosition(XAxis.XAxisPosition.BOTTOM);

        xaxis.setDrawLabels(true);
        xaxis.setDrawAxisLine(false);

        YAxis yAxisLeft = mChart.getAxisLeft();
        yAxisLeft.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
        yAxisLeft.setDrawGridLines(false);
        yAxisLeft.setDrawAxisLine(false);
        yAxisLeft.setEnabled(false);

        mChart.getAxisRight().setEnabled(false);
        // set your custom renderer
        mChart.setRenderer(new BarChartCustomRenderer(mChart, mChart.getAnimator(), mChart.getViewPortHandler(), myColors));
        mChart.setDrawValueAboveBar(true);

        Legend legend = mChart.getLegend();
        legend.setEnabled(false);

        ArrayList<BarEntry> valueSet1 = new ArrayList<BarEntry>();

        for (int i = 0; i < 6; ++i) {
            BarEntry entry = new BarEntry(i, (i + 1) * 10);
            valueSet1.add(entry);
        }


        List<IBarDataSet> dataSets = new ArrayList<>();
        BarDataSet barDataSet = new BarDataSet(valueSet1, " ");
        barDataSet.setValueFormatter(new MyFormatter(myText));
        barDataSet.setColor(Color.CYAN);
        dataSets.add(barDataSet);

        BarData data = new BarData(dataSets);
        mChart.setData(data);
    }


    public class MyFormatter implements IValueFormatter {

        String[] text;

        public MyFormatter(String[] text) {
            this.text = text;
        }

        @Override
        public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
            return String.valueOf((int)value)+"M" + ", " + text[(int) entry.getX()];
        }
    }


}

Custom Renderer

    public class BarChartCustomRenderer extends BarChartRenderer {

    private Paint myPaint;
    private ArrayList<Integer> myColors;

    public BarChartCustomRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, ArrayList<Integer> myColors) {
        super(chart, animator, viewPortHandler);
        this.myPaint = new Paint();
        this.myColors = myColors;
    }

    @Override
    public void drawValues(Canvas c) {
        super.drawValues(c);
        // you can modify the original method
        // so that everything is drawn on the canvas inside a single loop
        // also you can add logic here to meet your requirements
        int colorIndex = 0;
        for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) {
            BarBuffer buffer = mBarBuffers[i];
            float left, right, top, bottom;
            for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
                myPaint.setColor(myColors.get(colorIndex++));
                left = buffer.buffer[j];
                right = buffer.buffer[j + 2];
                top = buffer.buffer[j + 1];
                bottom = buffer.buffer[j + 3];
//                myPaint.setShader(new LinearGradient(left,top,right,bottom, Color.CYAN, myColors.get(colorIndex++), Shader.TileMode.MIRROR ));
                c.drawRect(left, top, right, top+5f, myPaint);
            }
        }
    }

    @Override
    public void drawValue(Canvas c, IValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) {
        String text = formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler);
        String[] splitText;
        if(text.contains(",")){
            splitText = text.split(",");
            Paint paintStyleOne = new Paint(mValuePaint);
            Paint paintStyleTwo = new Paint(mValuePaint);
            paintStyleOne.setColor(Color.BLACK);
            paintStyleTwo.setColor(Color.BLUE);
            c.drawText(splitText[0], x, y-20f, paintStyleOne);
            c.drawText(splitText[1], x, y, paintStyleTwo);
        }
        //else{
//            super.drawValue(c, formatter, value, entry, dataSetIndex, x, y, color);
        //}
    }
}

RESULT

enter image description here

you can also do a gradient effect for the entire bar by slightly modifying the custom renderer :

myPaint.setShader(new LinearGradient(left,top,right,bottom, Color.CYAN, myColors.get(colorIndex++), Shader.TileMode.MIRROR ));
c.drawRect(left, top, right, bottom, myPaint);

you can similarly draw and style your text using the custom renderer. Check this to learn more about custom renderers.

Update for using drawables instead of colors

//get bitmap from a drawable
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.myDrawable);

after than you can create a list of bitmaps and pass in onto the renderer instead of the list of colors.

if you want to draw on just the top of the bar you can use this :

c.drawBitmap(bitmap.get(index++), null, new RectF(left, top, right, top+5f), null);

or if you want to cover the entire bar, you can do so by using the bitmap like this:

c.drawBitmap(bitmap.get(index++), null, new RectF(left, top, right, bottom), null);
sauvik
  • 2,224
  • 1
  • 17
  • 25
  • Will check it out. What about the text (above the bars) though? – android developer Mar 16 '18 at 12:40
  • OK I've tested it now. Seems to work well. How can I set the drawable for the upper part (I've asked about drawable, not just color, even on screenshot...) ? I've noticed you used only colors there (in `myColors`) . You call `setDrawValueAboveBar` twice. Once with false, and once with true. Also, BTW, I think that for `drawValue`, you should avoid creating new objects. You could use `Paint` instances from a field. – android developer Mar 18 '18 at 08:21
  • yes `setDrawValueAboveBar` should be called only once with true. I also agree with your suggestion of not creating new paint objects. As for using drawables instead of colors, you can pass a List of bitmaps instead of colors and use them as follows : `c.drawBitmap(bitmap.get(index++), null, new RectF(left, top, right, top+5f), null);` – sauvik Mar 19 '18 at 04:59
  • but if you are using only solid colors or gradients, wouldn't it be easier to just use colors and not drawables. That way you don't have to worry about scaling issues – sauvik Mar 19 '18 at 05:03
  • The scaling shouldn't be an issue. It should be quite small anyway. Is it possible to set a drawable instead? – android developer Mar 19 '18 at 07:34
  • yes, it's possible. You have get the bitmap from the drawable and then you can pass it to the renderer. I have updated the answer. – sauvik Mar 19 '18 at 09:11
  • The drawable isn't a bitmap. It's a gradient. – android developer Mar 19 '18 at 09:19
  • Ok, so if I understand correctly you are using shape drawbles to create the gradient via XML. If that's the case, the only option I see is to pass in the colors and create the gradient via code and draw it on the canvas. – sauvik Mar 19 '18 at 09:54
  • I've convinced the designer to use a solid color instead. Could still be nice to know how to use gradient. I will accept the answer, because you've helped a lot, but please if you know how to put a drawable instead, post about it. – android developer Mar 19 '18 at 11:15
  • drawables are fine as long as bitmaps are available. Check [this](https://stackoverflow.com/questions/45114123/mpchart-draw-icons-as-labels-in-xaxis-of-bar-chart/45143200#45143200). I will tinker around and try to do the same with drawables created via xml, will let you know if I find anything – sauvik Mar 19 '18 at 14:32
  • The gradient is not a bitmap. – android developer Mar 19 '18 at 15:00