MPAndroidChart:在条形图内添加自定义图片

11
我正在使用MPAndroidChart,并希望在CombinedChart中展示自定义的可绘制对象,就像下面这张图片一样:

一个柱状图内有星形图案

如果柱子的值大于等于目标值50,那么我想在柱子内添加一个星形图案。

有没有人可以帮我自定义这个BarChart?

3个回答

14
为了让星星图像显示在我们的条形图内部,我们需要创建一个自定义的渲染器。因为我们的条形图使用了BarChartRenderer,所以我们将首先创建一个它的子类,并添加一个参数来处理我们的图像:
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;
    }

如果我们检查BarChartRenderer的源代码,我们可以看到它调用了名为drawData的方法,然后遍历每个数据集并调用drawDataSetdrawDataSet是发生动作的地方:它绘制阴影和条形图。这是添加逻辑以绘制额外内容(如图像)的适当位置,因此让我们在那里添加一个调用绘制图像的方法:

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

现在我们需要一种方法来遍历 DataSet 并绘制星形图像。一个适当的方法可以用作模板,叫做 drawValues,让我们复制它并将其更改为绘制图像而不是文本。理解这一点的关键是看 BarBuffer 如何工作。BarBuffer 保存给定 Entry 的条形的屏幕(像素)坐标,分别为 jj + 1j + 2j + 3

为了澄清,j 是左侧 x 坐标,j + 1 是顶部 y 坐标,依此类推,直到右侧 x 坐标为 j + 3。我们将提取这些变量以使理解更容易:

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

这里是如何使用渲染器的说明:

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

渲染器的最后一步是添加逻辑以缩放位图并正确定位它。这是自定义渲染器的最终概念验证:

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

这是一张屏幕截图 - 您可以看到超过50的值带有星号:

在柱形图内部使用自定义图片的条形图


感谢您的回答。 - Sanjay Kakadiya
你在这里使用了哪个版本的mpandroid图表? - Amalo
@Amalo 版本 3.0.1 - David Rawson
这个问题涉及编写自定义渲染器的更多信息,请参考:https://dev59.com/CKDia4cB1Zd3GeqPL_Sp - David Rawson
如何针对折线图进行此操作,并仅在最后一项上显示图像? - Morteza Rastgoo

0

不必使用自定义渲染器,您可以直接在BarDataSet上使用iconsOffset属性

barDataSet.iconsOffset = MPPointF.getInstance(CHART_ICON_X_OFFSET, CHART_ICON_Y_OFFSET)

0
    for (i in 0 until values.size) {
        if (values[i].y.toInt() >= 8000) {
            values[i].icon = ContextCompat.getDrawable(this, R.drawable.oval_check)
        }
    }

"values" 类型是 ArrayList<BarEntry>()。 - Mehmet Sabir Kahraman
5
尽管这段代码可能解决了问题,但是包括一个解释来说明如何以及为什么解决了这个问题将有助于提高你的帖子质量,并且可能会获得更多赞同票。请记住,你正在回答未来读者的问题,而不仅仅是现在提问的人。请[编辑]你的答案,添加解释并指出适用的限制和假设。 - janw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接