11

I am using MPAndroidChart and I want to show a custom drawable inside this CombinedChart like in below image:

a bar chart with star images inside the bars

If bar value is >= a goal value, say, 50, then I would like to add a star image inside the bar.

Can any one help me for customise the BarChart?

David Rawson
  • 20,912
  • 7
  • 88
  • 124
Sanjay Kakadiya
  • 1,596
  • 1
  • 15
  • 32

3 Answers3

14

To get the star image inside our bars, we will need to create a custom renderer. Because our bar chart uses BarChartRenderer we will subclass this first and add a parameter for our image:

public class ImageBarChartRenderer extends BarChartRenderer {

    private final Bitmap barImage;

    public ImageBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, Bitmap barImage) {
        super(chart, animator, viewPortHandler);
        this.barImage = barImage;
    }

If we inspect the source for BarChartRenderer we can see that it calls the method called drawData and then iterates through each DataSet and calls drawDataSet. drawDataSet is where the action is happening: it's drawing the shadows and the bars. It's an appropriate place to add logic to draw an extra like images, so let's add a call to a method to draw our images there:

    @Override
    protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
        super.drawDataSet(c, dataSet, index);
        drawBarImages(c, dataSet, index);
    }

We now need a method that will iterate through the DataSet and draw the star images. An appropriate method that will serve as a template is drawValues so let's copy that and change it so that is draws an image rather than text. The key to understanding this is seeing how BarBuffer works. BarBuffer holds the on-screen (pixel) co-ordinates for a bar for a given Entry at j, j + 1, j + 2, j + 3.

To clarify, j is the left x co-ordinate, j + 1 is the top y co-ordinate and so on through to the right x co-ordinate at j + 3. We'll extract these to variables to make it easier to understand:

    protected void drawBarImages(Canvas c, IBarDataSet dataSet, int index) {
        BarBuffer buffer = mBarBuffers[index];

        float left; //avoid allocation inside loop
        float right;
        float top;
        float bottom;

        for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
            left = buffer.buffer[j];
            right = buffer.buffer[j + 2];
            top = buffer.buffer[j + 1];
            bottom = buffer.buffer[j + 3];

            float x = (left + right) / 2f;

            if (!mViewPortHandler.isInBoundsRight(x))
                break;

            if (!mViewPortHandler.isInBoundsY(top)
                    || !mViewPortHandler.isInBoundsLeft(x))
                continue;

            BarEntry entry = dataSet.getEntryForIndex(j / 4);
            float val = entry.getY();

            if (val > 50) {
                drawStar(c, barImage, x, top);
            }
        }
    }

Here's how to consume the renderer:

    Bitmap starBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.star);
    mChart.setRenderer(new ImageBarChartRenderer(mChart, mChart.getAnimator(), mChart.getViewPortHandler(), starBitmap));

The final step to the renderer is to add logic to scale the bitmap and position it correctly. Here is the final proof-of-concept of the custom renderer:

package com.xxmassdeveloper.mpchartexample;

import android.graphics.Bitmap;
import android.graphics.Canvas;

import com.github.mikephil.charting.animation.ChartAnimator;
import com.github.mikephil.charting.buffer.BarBuffer;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.renderer.BarChartRenderer;
import com.github.mikephil.charting.utils.ViewPortHandler;

/**
 * Created by David on 29/12/2016.
 */

public class ImageBarChartRenderer extends BarChartRenderer {

    private final Bitmap barImage;

    public ImageBarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler, Bitmap barImage) {
        super(chart, animator, viewPortHandler);
        this.barImage = barImage;
    }

    @Override
    public void drawData(Canvas c) {
        super.drawData(c);
    }

    @Override
    protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
        super.drawDataSet(c, dataSet, index);
        drawBarImages(c, dataSet, index);
    }

    protected void drawBarImages(Canvas c, IBarDataSet dataSet, int index) {
        BarBuffer buffer = mBarBuffers[index];

        float left; //avoid allocation inside loop
        float right;
        float top;
        float bottom;

        final Bitmap scaledBarImage = scaleBarImage(buffer);

        int starWidth = scaledBarImage.getWidth();
        int starOffset = starWidth / 2;

        for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
            left = buffer.buffer[j];
            right = buffer.buffer[j + 2];
            top = buffer.buffer[j + 1];
            bottom = buffer.buffer[j + 3];

            float x = (left + right) / 2f;

            if (!mViewPortHandler.isInBoundsRight(x))
                break;

            if (!mViewPortHandler.isInBoundsY(top)
                    || !mViewPortHandler.isInBoundsLeft(x))
                continue;

            BarEntry entry = dataSet.getEntryForIndex(j / 4);
            float val = entry.getY();

            if (val > 50) {
                drawImage(c, scaledBarImage, x - starOffset, top);
            }
        }
    }

    private Bitmap scaleBarImage(BarBuffer buffer) {
        float firstLeft = buffer.buffer[0];
        float firstRight = buffer.buffer[2];
        int firstWidth = (int) Math.ceil(firstRight - firstLeft);
        return Bitmap.createScaledBitmap(barImage, firstWidth, firstWidth, false);
    }

    protected void drawImage(Canvas c, Bitmap image, float x, float y) {
        if (image != null) {
            c.drawBitmap(image, x, y, null);
        }
    }
}

Here's a screen shot - you can see that values over 50 have the star:

bar chart with custom image inside bars

David Rawson
  • 20,912
  • 7
  • 88
  • 124
0
    for (i in 0 until values.size) {
        if (values[i].y.toInt() >= 8000) {
            values[i].icon = ContextCompat.getDrawable(this, R.drawable.oval_check)
        }
    }
issamux
  • 1,336
  • 1
  • 19
  • 35
  • "values" type is ArrayList() – Mehmet Sabir Kahraman Oct 31 '20 at 23:22
  • 5
    While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. – janw Nov 01 '20 at 08:45
0

Instead of using a custom renderer, you can just use the iconsOffset property on BarDataSet

barDataSet.iconsOffset = MPPointF.getInstance(CHART_ICON_X_OFFSET, CHART_ICON_Y_OFFSET)
Harsh Shah
  • 368
  • 3
  • 17