MPAndroidChart column chart rounded corner problem solution

Background and Issues

A lot of charts are used in the project, so MPAndroidChart is used as the solution. I have to say that this library is very powerful. Most of the requirements in the project can be solved directly using the methods provided by the project, but there are also some requirements that require their own customization, such as the topic of this article: the problem of rounded corners of histograms.

solution

In order to better modify the source code to achieve the project requirements, I imported the project by importing the module, the details are not listed.

Our design lady is very thin. The designed column chart is like this, with right angles at the bottom and rounded corners at the top.

image.png

At first I found this solution for rounded corners: github.com/WW-Digital/…

The code here can solve the requirement that all four corners are rounded. The core is to modify the drawing method and change the drawing method of canvas from drawRect to drawRoundRect. If the requirement is that all four corners are rounded, this solution can be used, and the amount of code modification is not large.

The problem for me is that there is no way to just draw rounded corners on it, so this solution doesn't meet my needs. Continue to try other solutions, and found a solution for partial rounded corners under this question: stackoverflow.com/questions/3…According to the code provided by @duc tan , the requirement for partial rounded corners was successfully solved. The core is to convert Rect to is Path, and draws it through drawPath. By modifying the following method parameters, you can customize the rounded corners arbitrarily.

roundRect(RectF rect, float rx, float ry, boolean tl, boolean tr, boolean br, boolean bl) 
复制代码

The matter seems to be solved, but it is still square when it is highlighted, which is definitely not possible, so I rewrote the drawHighlightedmethod again, and made the highlighted histogram also partially rounded, and finally solved this problem, the code is as follows :

@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
    BarData barData = mChart.getBarData();

    for (Highlight high : indices) {

        IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex());

        if (set == null || !set.isHighlightEnabled()) {
            continue;
        }

        BarEntry e = set.getEntryForXValue(high.getX(), high.getY());

        if (!isInBoundsX(e, set)) {
            continue;
        }

        Transformer trans = mChart.getTransformer(set.getAxisDependency());

        mHighlightPaint.setColor(set.getHighLightColor());
        mHighlightPaint.setAlpha(set.getHighLightAlpha());

        boolean isStack = high.getStackIndex() >= 0 && e.isStacked();

        final float y1;
        final float y2;

        if (isStack) {

            if (mChart.isHighlightFullBarEnabled()) {

                y1 = e.getPositiveSum();
                y2 = -e.getNegativeSum();

            } else {

                Range range = e.getRanges()[high.getStackIndex()];

                y1 = range.from;
                y2 = range.to;
            }

        } else {
            y1 = e.getY();
            y2 = 0.f;
        }

        prepareBarHighlight(e.getX(), y1, y2, barData.getBarWidth() / 2f, trans);

        setHighlightDrawPos(high, mBarRect);

        Path path2 = roundRect(new RectF(mBarRect.left, mBarRect.top, mBarRect.right,
                mBarRect.bottom), mRadius, mRadius, true, true, false, false);

        c.drawPath(path2, mHighlightPaint);
    }
}
复制代码

Finally, post the final solution renderer class code and usage.

public class CustomBarChartRender extends BarChartRenderer {
    private final RectF mBarShadowRectBuffer = new RectF();

    private int mRadius;

    public CustomBarChartRender(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
        super(chart, animator, viewPortHandler);
    }

    public void setRadius(int mRadius) {
        this.mRadius = mRadius;
    }

    @Override
    protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {

        Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
        mBarBorderPaint.setColor(dataSet.getBarBorderColor());
        mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth()));
        mShadowPaint.setColor(dataSet.getBarShadowColor());
        boolean drawBorder = dataSet.getBarBorderWidth() > 0f;

        float phaseX = mAnimator.getPhaseX();
        float phaseY = mAnimator.getPhaseY();

        if (mChart.isDrawBarShadowEnabled()) {
            mShadowPaint.setColor(dataSet.getBarShadowColor());

            BarData barData = mChart.getBarData();

            float barWidth = barData.getBarWidth();
            float barWidthHalf = barWidth / 2.0f;
            float x;

            int i = 0;
            double count = Math.min(Math.ceil((int) (double) ((float) dataSet.getEntryCount() * phaseX)), dataSet.getEntryCount());
            while (i < count) {

                BarEntry e = dataSet.getEntryForIndex(i);

                x = e.getX();

                mBarShadowRectBuffer.left = x - barWidthHalf;
                mBarShadowRectBuffer.right = x + barWidthHalf;

                trans.rectValueToPixel(mBarShadowRectBuffer);

                if (!mViewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right)) {
                    i++;
                    continue;
                }

                if (!mViewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left)) {
                    break;
                }

                mBarShadowRectBuffer.top = mViewPortHandler.contentTop();
                mBarShadowRectBuffer.bottom = mViewPortHandler.contentBottom();

                c.drawRoundRect(mBarRect, mRadius, mRadius, mShadowPaint);
                i++;
            }
        }

        // initialize the buffer
        BarBuffer buffer = mBarBuffers[index];
        buffer.setPhases(phaseX, phaseY);
        buffer.setDataSet(index);
        buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
        buffer.setBarWidth(mChart.getBarData().getBarWidth());

        buffer.feed(dataSet);

        trans.pointValuesToPixel(buffer.buffer);

        boolean isSingleColor = dataSet.getColors().size() == 1;

        if (isSingleColor) {
            mRenderPaint.setColor(dataSet.getColor());
        }

        int j = 0;
        while (j < buffer.size()) {

            if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
                j += 4;
                continue;
            }

            if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
                break;
            }

            if (!isSingleColor) {
                // Set the color for the currently drawn value. If the index
                // is out of bounds, reuse colors.
                mRenderPaint.setColor(dataSet.getColor(j / 4));
            }

            Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                    buffer.buffer[j + 3]), mRadius, mRadius, true, true, false, false);
            c.drawPath(path2, mRenderPaint);

            if (drawBorder) {
                Path path = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3]), mRadius, mRadius, true, true, false, false);
                c.drawPath(path, mBarBorderPaint);
            }
            j += 4;
        }

    }

    @Override
    public void drawHighlighted(Canvas c, Highlight[] indices) {
        BarData barData = mChart.getBarData();

        for (Highlight high : indices) {

            IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex());

            if (set == null || !set.isHighlightEnabled()) {
                continue;
            }

            BarEntry e = set.getEntryForXValue(high.getX(), high.getY());

            if (!isInBoundsX(e, set)) {
                continue;
            }

            Transformer trans = mChart.getTransformer(set.getAxisDependency());

            mHighlightPaint.setColor(set.getHighLightColor());
            mHighlightPaint.setAlpha(set.getHighLightAlpha());

            boolean isStack = high.getStackIndex() >= 0 && e.isStacked();

            final float y1;
            final float y2;

            if (isStack) {

                if (mChart.isHighlightFullBarEnabled()) {

                    y1 = e.getPositiveSum();
                    y2 = -e.getNegativeSum();

                } else {

                    Range range = e.getRanges()[high.getStackIndex()];

                    y1 = range.from;
                    y2 = range.to;
                }

            } else {
                y1 = e.getY();
                y2 = 0.f;
            }

            prepareBarHighlight(e.getX(), y1, y2, barData.getBarWidth() / 2f, trans);

            setHighlightDrawPos(high, mBarRect);

            Path path2 = roundRect(new RectF(mBarRect.left, mBarRect.top, mBarRect.right,
                    mBarRect.bottom), mRadius, mRadius, true, true, false, false);

            c.drawPath(path2, mHighlightPaint);
        }
    }

    private Path roundRect(RectF rect, float rx, float ry, boolean tl, boolean tr, boolean br, boolean bl) {
        float top = rect.top;
        float left = rect.left;
        float right = rect.right;
        float bottom = rect.bottom;
        Path path = new Path();
        if (rx < 0) {
            rx = 0;
        }
        if (ry < 0) {
            ry = 0;
        }
        float width = right - left;
        float height = bottom - top;
        if (rx > width / 2) {
            rx = width / 2;
        }
        if (ry > height / 2) {
            ry = height / 2;
        }
        float widthMinusCorners = (width - (2 * rx));
        float heightMinusCorners = (height - (2 * ry));

        path.moveTo(right, top + ry);
        if (tr) {
            //top-right corner
            path.rQuadTo(0, -ry, -rx, -ry);
        } else {
            path.rLineTo(0, -ry);
            path.rLineTo(-rx, 0);
        }
        path.rLineTo(-widthMinusCorners, 0);
        if (tl) {
            //top-left corner
            path.rQuadTo(-rx, 0, -rx, ry);
        } else {
            path.rLineTo(-rx, 0);
            path.rLineTo(0, ry);
        }
        path.rLineTo(0, heightMinusCorners);

        if (bl) {
            //bottom-left corner
            path.rQuadTo(0, ry, rx, ry);
        } else {
            path.rLineTo(0, ry);
            path.rLineTo(rx, 0);
        }

        path.rLineTo(widthMinusCorners, 0);
        if (br) {
            //bottom-right corner
            path.rQuadTo(rx, 0, rx, -ry);
        } else {
            path.rLineTo(rx, 0);
            path.rLineTo(0, -ry);
        }

        path.rLineTo(0, -heightMinusCorners);

        path.close();//Given close, last lineto can be removed.

        return path;
    }
}
复制代码

How to use (Kotlin):

val barChartRender = CustomBarChartRender(chart,chart.animator,chart.viewPortHandler)
barChartRender.setRadius(20)
barChart.renderer = barChartRender
复制代码

The real machine screenshot of the effect achieved is as follows:

image.png

Guess you like

Origin juejin.im/post/7100863388850503693