You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MPAndroidChart堆叠柱状图实现整体圆角效果的问题咨询

MPAndroidChart 堆叠柱状图整体圆角实现方案

问题的核心原因是原有逻辑会为每个柱形分段单独绘制圆角,只需调整绘制逻辑,将同一横坐标下的所有堆叠段归为一组,统一判断每个分段的位置后再决定圆角位置即可。

实现逻辑说明

  1. 先对buffer中的数据按横坐标分组,相同左右x坐标的属于同一根柱子的堆叠分段
  2. 计算每组柱子的整体顶部和底部边界
  3. 遍历组内每个分段:
    • 仅顶部段添加上圆角
    • 仅底部段添加下圆角
    • 中间段直接绘制直角矩形

调整后的代码

val trans: Transformer = mChart.getTransformer(dataSet.axisDependency)
mShadowPaint.color = dataSet.barShadowColor
val phaseX = mAnimator.phaseX
val phaseY = mAnimator.phaseY

// 初始化buffer
val buffer = mBarBuffers[index]
buffer.setPhases(phaseX, phaseY)
buffer.setDataSet(index)
buffer.setBarWidth(mChart.barData.barWidth)
buffer.setInverted(mChart.isInverted(dataSet.axisDependency))
buffer.feed(dataSet)
trans.pointValuesToPixel(buffer.buffer)

// 先对buffer数据按柱子分组,相同x左右坐标的为同一根柱子
val barGroups = mutableListOf<MutableList<FloatArray>>()
var currentXLeft = -1f
var j = 0
while (j < buffer.size()) {
    val xLeft = buffer.buffer[j]
    val yTop = buffer.buffer[j + 1]
    val xRight = buffer.buffer[j + 2]
    val yBottom = buffer.buffer[j + 3]
    if (xLeft != currentXLeft) {
        barGroups.add(mutableListOf())
        currentXLeft = xLeft
    }
    barGroups.last().add(floatArrayOf(xLeft, yTop, xRight, yBottom))
    j += 4
}

if (dataSet.colors.size > 1) {
    var colorIndex = 0
    barGroups.forEachIndexed { groupIdx, segments ->
        val firstSeg = segments.first()
        val xLeft = firstSeg[0]
        val xRight = firstSeg[2]
        if (!mViewPortHandler.isInBoundsLeft(xRight)) return@forEachIndexed
        if (!mViewPortHandler.isInBoundsRight(xLeft)) return@forEachIndexed

        // 绘制阴影
        if (mChart.isDrawBarShadowEnabled) {
            if (mRadius > 0) c.drawRoundRect(
                RectF(
                    xLeft, mViewPortHandler.contentTop(),
                    xRight, mViewPortHandler.contentBottom()
                ), mRadius, mRadius, mShadowPaint
            ) else c.drawRect(
                xLeft, mViewPortHandler.contentTop(),
                xRight, mViewPortHandler.contentBottom(), mShadowPaint
            )
        }

        // 计算整根柱子的上下边界
        val allTops = segments.map { it[1] }
        val allBottoms = segments.map { it[3] }
        val barTop = allTops.minOrNull() ?: 0f
        val barBottom = allBottoms.maxOrNull() ?: 0f

        segments.forEach { seg ->
            val segTop = seg[1]
            val segBottom = seg[3]
            mRenderPaint.color = dataSet.getColor(colorIndex % dataSet.colors.size)
            colorIndex++

            if (mRadius <= 0) {
                c.drawRect(xLeft, segTop, xRight, segBottom, mRenderPaint)
                return@forEach
            }

            // 判断当前段的位置,设置对应圆角
            val corners = FloatArray(8) { 0f }
            // 是顶部段,加上圆角
            if (segTop == barTop) {
                corners[0] = mRadius
                corners[1] = mRadius
                corners[2] = mRadius
                corners[3] = mRadius
            }
            // 是底部段,加下圆角
            if (segBottom == barBottom) {
                corners[4] = mRadius
                corners[5] = mRadius
                corners[6] = mRadius
                corners[7] = mRadius
            }
            val path = Path()
            path.addRoundRect(RectF(xLeft, segTop, xRight, segBottom), corners, Path.Direction.CW)
            c.drawPath(path, mRenderPaint)
        }
    }
} else {
    mRenderPaint.color = dataSet.color
    barGroups.forEach { segments ->
        val firstSeg = segments.first()
        val xLeft = firstSeg[0]
        val xRight = firstSeg[2]
        if (!mViewPortHandler.isInBoundsLeft(xRight)) return@forEach
        if (!mViewPortHandler.isInBoundsRight(xLeft)) return@forEach

        // 绘制阴影
        if (mChart.isDrawBarShadowEnabled) {
            if (mRadius > 0) c.drawRoundRect(
                RectF(
                    xLeft, mViewPortHandler.contentTop(),
                    xRight, mViewPortHandler.contentBottom()
                ), mRadius, mRadius, mShadowPaint
            ) else c.drawRect(
                xLeft, firstSeg[1], xRight, firstSeg[3], mShadowPaint
            )
        }

        // 计算整根柱子的上下边界
        val allTops = segments.map { it[1] }
        val allBottoms = segments.map { it[3] }
        val barTop = allTops.minOrNull() ?: 0f
        val barBottom = allBottoms.maxOrNull() ?: 0f

        segments.forEach { seg ->
            val segTop = seg[1]
            val segBottom = seg[3]

            if (mRadius <= 0) {
                c.drawRect(xLeft, segTop, xRight, segBottom, mRenderPaint)
                return@forEach
            }

            // 判断当前段的位置,设置对应圆角
            val corners = FloatArray(8) { 0f }
            // 是顶部段,加上圆角
            if (segTop == barTop) {
                corners[0] = mRadius
                corners[1] = mRadius
                corners[2] = mRadius
                corners[3] = mRadius
            }
            // 是底部段,加下圆角
            if (segBottom == barBottom) {
                corners[4] = mRadius
                corners[5] = mRadius
                corners[6] = mRadius
                corners[7] = mRadius
            }
            val path = Path()
            path.addRoundRect(RectF(xLeft, segTop, xRight, segBottom), corners, Path.Direction.CW)
            c.drawPath(path, mRenderPaint)
        }
    }
}

兼容说明

该逻辑同时兼容堆叠和非堆叠模式:非堆叠模式下每个分组只有一个分段,会同时添加上下圆角,和原有效果保持一致。

内容的提问来源于stack exchange,提问作者Jack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 23:39:04