To get a star image inside our bars, we need to create our own renderer. Since our histogram uses BarChartRenderer , we 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 check the source for BarChartRenderer , we see that it calls a method called drawData , and then drawData through each DataSet and calls drawDataSet . drawDataSet is where the action takes place: it draws shadows and stripes. This is a good place to add logic to draw additional images, so add a method call to draw our images:
@Override protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) { super.drawDataSet(c, dataSet, index); drawBarImages(c, dataSet, index); }
Now we need a method that will go through the DataSet and draw star-shaped images. The appropriate method that will serve as the template is drawValues , so copy it and change it so that it draws an image, not text. The key to understanding this is how BarBuffer works. BarBuffer holds the coordinates of the screen (pixel) for the strip for a given input in j , j + 1 , j + 2 , j + 3 .
To clarify, j is the x coordinate on the left, j + 1 is the upper y coordinate, etc. to the right coordinate x at j + 3 . We will extract them for the 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 rendering is to add logic to scale the bitmap and the correct position. Here is the final proof of the concept of custom rendering:
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; 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;
Here's a screenshot - you can see that values ββover 50 have a star:
