Search code examples
androidkotlinandroid-layoutmpandroidchart

How can change bar view in bar chart in MPAndroidChart?


I need change bars view in barchart in MPAndroidChart like this... that's how it should be Is it possible to set custom bar drawable, or just smooth the corners, or something like this?

Now I have this

My code:

fun barChart(
    activity: Activity,
    chart: BarChart,
    entries: ArrayList<BarEntry>,
    columnsNames: ArrayList<String>,
    colors: ArrayList<Int>,
) {

    val colorsTemplate = intArrayOf(
        Color.rgb(74, 160, 150), Color.rgb(74, 160, 150), Color.rgb(74, 160, 150),
        Color.rgb(74, 160, 150), Color.rgb(74, 160, 150)
    )
    val colors: ArrayList<Int> = ArrayList()
    for (color in colorsTemplate) {
        colors.add(color)
    }
    val dataSet = BarDataSet(entries, "")

    val decimalFormat = DecimalFormat("0.##")
    dataSet.setValueFormatter(object : ValueFormatter() {
        override fun getFormattedValue(value: Float): String {
            return decimalFormat.format(value)
        }
    })

    dataSet.colors = colors
    val data = BarData(dataSet)
    data.barWidth = 0.5f
    data.setDrawValues(true)
    chart.setData(data)
    chart.setAutoScaleMinMaxEnabled(true)

    val xAxis = chart.getXAxis()
    xAxis.valueFormatter = IndexAxisValueFormatter(columnsNames)
    xAxis.position = (XAxis.XAxisPosition.BOTTOM)
    xAxis.setDrawGridLines(false)
    xAxis.setDrawAxisLine(false)
    xAxis.granularity = 1f
    xAxis.labelCount = columnsNames!!.size
    xAxis.labelRotationAngle = 360f

    chart.legend.isEnabled = false
    chart.description.isEnabled = false
    chart.axisRight.isEnabled = false

    chart.axisLeft.labelCount = 3
    chart.axisLeft.axisMinimum = 0f

    chart.animateY(1000)
    chart.invalidate()
}

Solution

  • Some time after the found solution I am writing an answer to my own question, maybe it will be useful to someone. Thanks gioravered for the tip

    This is code for your chart:

    val barChartRender =
                RoundedBarChartRender(chart, chart.animator, chart.viewPortHandler)
            barChartRender.initBuffers()
            barChartRender.setRadius(20)
            chart.renderer = barChartRender
    

    And this is the code of a new sub class, which render the rounding:

    class RoundedBarChartRender(
        chart: BarDataProvider?,
        animator: ChartAnimator?,
        viewPortHandler: ViewPortHandler?
    ) : BarChartRenderer(chart, animator, viewPortHandler) {
        private val mBarShadowRectBuffer = RectF()
        private var mRadius = 0
        fun setRadius(mRadius: Int) {
            this.mRadius = mRadius
        }
    
        override fun drawDataSet(c: Canvas, dataSet: IBarDataSet, index: Int) {
            val trans = mChart.getTransformer(dataSet.axisDependency)
            mBarBorderPaint.color = dataSet.barBorderColor
            mBarBorderPaint.strokeWidth = Utils.convertDpToPixel(dataSet.barBorderWidth)
            mShadowPaint.color = dataSet.barShadowColor
            val drawBorder = dataSet.barBorderWidth > 0f
            val phaseX = mAnimator.phaseX
            val phaseY = mAnimator.phaseY
            if (mChart.isDrawBarShadowEnabled) {
                mShadowPaint.color = dataSet.barShadowColor
                val barData = mChart.barData
                val barWidth = barData.barWidth
                val barWidthHalf = barWidth / 2.0f
                var x: Float
                var i = 0
                val count = Math.min(
                    Math.ceil(
                        (dataSet.entryCount.toFloat() * phaseX).toDouble().toInt().toDouble()
                    ), dataSet.entryCount.toDouble()
                )
                while (i < count) {
                    val e = dataSet.getEntryForIndex(i)
                    x = e.x
                    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.toFloat(), mRadius.toFloat(), mShadowPaint)
                    i++
                }
            }
    
            // initialize the buffer
            val buffer = mBarBuffers[index]
            buffer.setPhases(phaseX, phaseY)
            buffer.setDataSet(index)
            buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
            buffer.setBarWidth(mChart.barData.barWidth)
            buffer.feed(dataSet)
            trans.pointValuesToPixel(buffer.buffer)
            val isSingleColor = dataSet.colors.size == 1
            if (isSingleColor) {
                mRenderPaint.color = dataSet.color
            }
            var 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.color = dataSet.getColor(j / 4)
                }
                if (dataSet.gradientColor != null) {
                    val gradientColor = dataSet.gradientColor
                    mRenderPaint.shader = LinearGradient(
                        buffer.buffer[j],
                        buffer.buffer[j + 3],
                        buffer.buffer[j],
                        buffer.buffer[j + 1],
                        gradientColor.startColor,
                        gradientColor.endColor,
                        Shader.TileMode.MIRROR
                    )
                }
                if (dataSet.gradientColors != null) {
                    mRenderPaint.shader = LinearGradient(
                        buffer.buffer[j],
                        buffer.buffer[j + 3],
                        buffer.buffer[j],
                        buffer.buffer[j + 1],
                        dataSet.getGradientColor(j / 4).startColor,
                        dataSet.getGradientColor(j / 4).endColor,
                        Shader.TileMode.MIRROR
                    )
                }
                val path2 = roundRect(
                    RectF(
                        buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3]
                    ), mRadius.toFloat(), mRadius.toFloat(), true, true, false, false
                )
                c.drawPath(path2, mRenderPaint)
                if (drawBorder) {
                    val path = roundRect(
                        RectF(
                            buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                            buffer.buffer[j + 3]
                        ), mRadius.toFloat(), mRadius.toFloat(), true, true, false, false
                    )
                    c.drawPath(path, mBarBorderPaint)
                }
                j += 4
            }
        }
    
        private fun roundRect(
            rect: RectF,
            rx: Float,
            ry: Float,
            tl: Boolean,
            tr: Boolean,
            br: Boolean,
            bl: Boolean
        ): Path {
            var rx = rx
            var ry = ry
            val top = rect.top
            val left = rect.left
            val right = rect.right
            val bottom = rect.bottom
            val path = Path()
            if (rx < 0) rx = 0f
            if (ry < 0) ry = 0f
            val width = right - left
            val height = bottom - top
            if (rx > width / 2) rx = width / 2
            if (ry > height / 2) ry = height / 2
            val widthMinusCorners = width - 2 * rx
            val heightMinusCorners = height - 2 * ry
            path.moveTo(right, top + ry)
            if (tr) path.rQuadTo(0f, -ry, -rx, -ry) //top-right corner
            else {
                path.rLineTo(0f, -ry)
                path.rLineTo(-rx, 0f)
            }
            path.rLineTo(-widthMinusCorners, 0f)
            if (tl) path.rQuadTo(-rx, 0f, -rx, ry) //top-left corner
            else {
                path.rLineTo(-rx, 0f)
                path.rLineTo(0f, ry)
            }
            path.rLineTo(0f, heightMinusCorners)
            if (bl) path.rQuadTo(0f, ry, rx, ry) //bottom-left corner
            else {
                path.rLineTo(0f, ry)
                path.rLineTo(rx, 0f)
            }
            path.rLineTo(widthMinusCorners, 0f)
            if (br) path.rQuadTo(rx, 0f, rx, -ry) //bottom-right corner
            else {
                path.rLineTo(rx, 0f)
                path.rLineTo(0f, -ry)
            }
            path.rLineTo(0f, -heightMinusCorners)
            path.close() //Given close, last lineto can be removed.
            return path
        }
    }