diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedBarChartRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedBarChartRenderer.kt index e81259c46..92defd38a 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedBarChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedBarChartRenderer.kt @@ -11,357 +11,273 @@ import info.appdev.charting.interfaces.dataprovider.BarDataProvider import info.appdev.charting.interfaces.datasets.IBarDataSet import info.appdev.charting.utils.ViewPortHandler import info.appdev.charting.utils.convertDpToPixel +import kotlin.math.abs import kotlin.math.min +@Suppress("unused") class RoundedBarChartRenderer( dataProvider: BarDataProvider, animator: ChartAnimator, viewPortHandler: ViewPortHandler ) : BarChartRenderer(dataProvider, animator, viewPortHandler) { - private val mBarShadowRectBuffer = RectF() - private val radius = 20f - var roundedShadowRadius = 0f + + private val shadowRect = RectF() + private val tmpPts = FloatArray(4) + private val ovalPath = Path() + + private val defaultRadius = 20f + + private var roundedShadowRadius = 0f var roundedPositiveDataSetRadius = 0f var roundedNegativeDataSetRadius = 0f + /** If true, corner radii = half the bar’s screen‐pixel width at draw‐time. */ + var useAutoFullRadius = false override fun drawDataSet(canvas: Canvas, dataSet: IBarDataSet, index: Int) { initBuffers() - val trans = dataProvider.getTransformer(dataSet.axisDependency) - barBorderPaint.color = dataSet.barBorderColor - barBorderPaint.strokeWidth = dataSet.barBorderWidth.convertDpToPixel() - shadowPaint.color = dataSet.barShadowColor + val transformer = dataProvider.getTransformer(dataSet.axisDependency) ?: return + val handler = viewPortHandler + val phaseX = animator.phaseX val phaseY = animator.phaseY - if (dataProvider.isDrawBarShadowEnabled) { - shadowPaint.color = dataSet.barShadowColor - dataProvider.barData?.let { barData -> - val barWidth = barData.barWidth - val barWidthHalf = barWidth / 2.0f - var x: Float - var i = 0 - val count = min((dataSet.entryCount.toFloat() * phaseX).toDouble().toInt().toDouble(), dataSet.entryCount.toDouble()) - while (i < count) { - dataSet.getEntryForIndex(i)?.let { barEntry -> - x = barEntry.x - mBarShadowRectBuffer.left = x - barWidthHalf - mBarShadowRectBuffer.right = x + barWidthHalf - } - trans!!.rectValueToPixel(mBarShadowRectBuffer) - if (!viewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right)) { - i++ - continue - } - if (!viewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left)) { - break - } - mBarShadowRectBuffer.top = viewPortHandler.contentTop() - mBarShadowRectBuffer.bottom = viewPortHandler.contentBottom() + dataProvider.barData?.let { barData -> + // 1) auto‐radius? + if (useAutoFullRadius) { + val halfVal = barData.barWidth / 2f + tmpPts[0] = 0f; tmpPts[1] = 0f + tmpPts[2] = halfVal; tmpPts[3] = 0f + transformer.pointValuesToPixel(tmpPts) + val pxHalf = abs(tmpPts[2] - tmpPts[0]) + roundedShadowRadius = pxHalf + roundedPositiveDataSetRadius = pxHalf + roundedNegativeDataSetRadius = pxHalf + } + // 2) prep paints + barBorderPaint.color = dataSet.barBorderColor + barBorderPaint.strokeWidth = dataSet.barBorderWidth.convertDpToPixel() + shadowPaint.color = dataSet.barShadowColor - if (roundedShadowRadius > 0) { - canvas.drawRoundRect(barRect, roundedShadowRadius, roundedShadowRadius, shadowPaint) - } else { - canvas.drawRect(mBarShadowRectBuffer, shadowPaint) + // 3) draw shadows + if (dataProvider.isDrawBarShadowEnabled) { + val barWidth = barData.barWidth + val half = barWidth / 2f + val count = min((dataSet.entryCount * phaseX).toInt(), dataSet.entryCount) + for (i in 0 until count) { + dataSet.getEntryForIndex(i)?.let { e -> + val x = e.x + shadowRect.left = x - half + shadowRect.right = x + half + transformer.rectValueToPixel(shadowRect) + + if (!handler.isInBoundsLeft(shadowRect.right) || + !handler.isInBoundsRight(shadowRect.left) + ) return@let + + shadowRect.top = handler.contentTop() + shadowRect.bottom = handler.contentBottom() + + if (roundedShadowRadius > 0f) { + canvas.drawRoundRect(shadowRect, roundedShadowRadius, roundedShadowRadius, shadowPaint) + } else { + canvas.drawRect(shadowRect, shadowPaint) + } } - i++ } - } - val buffer = barBuffers[index]!! + // 4) feed & transform + val buffer = barBuffers[index] ?: return buffer.setPhases(phaseX, phaseY) buffer.setDataSet(index) buffer.inverted = dataProvider.isInverted(dataSet.axisDependency) - dataProvider.barData?.let { buffer.barWidth = it.barWidth } + buffer.barWidth = barData.barWidth buffer.feed(dataSet) - trans!!.pointValuesToPixel(buffer.buffer) - - // if multiple colors has been assigned to Bar Chart - dataSet.colors.let { - if (it.size > 1) { - var j = 0 - while (j < buffer.size()) { - if (!viewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) { - j += 4 - continue - } - - if (!viewPortHandler.isInBoundsRight(buffer.buffer[j])) { - break - } + transformer.pointValuesToPixel(buffer.buffer) - if (dataProvider.isDrawBarShadowEnabled) { - if (roundedShadowRadius > 0) { - canvas.drawRoundRect( - RectF( - buffer.buffer[j], viewPortHandler.contentTop(), - buffer.buffer[j + 2], - viewPortHandler.contentBottom() - ), roundedShadowRadius, roundedShadowRadius, shadowPaint - ) - } else { - canvas.drawRect( - buffer.buffer[j], viewPortHandler.contentTop(), - buffer.buffer[j + 2], - viewPortHandler.contentBottom(), shadowPaint - ) - } - } + val singleColor = dataSet.colors.size == 1 - // Set the color for the currently drawn value. If the index - paintRender.color = dataSet.getColorByIndex(j / 4) + // 5a) multi‐color bars + if (!singleColor) { + var j = 0 + while (j < buffer.size()) { + val left = buffer.buffer[j] + val top = buffer.buffer[j + 1] + val right = buffer.buffer[j + 2] + val bottom = buffer.buffer[j + 3] - if (roundedPositiveDataSetRadius > 0) { - canvas.drawRoundRect( - RectF( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3] - ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, paintRender - ) - } else { - canvas.drawRect( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3], paintRender - ) - } - j += 4 + if (!handler.isInBoundsLeft(right)) { + j += 4; continue + } + // if bar is off‐right, we're past visible, so stop + if (!handler.isInBoundsRight(left)) break + + // shadow + if (dataProvider.isDrawBarShadowEnabled && roundedShadowRadius > 0f) { + ovalPath.reset() + ovalPath.addRoundRect( + RectF(left, handler.contentTop(), right, handler.contentBottom()), + roundedShadowRadius, roundedShadowRadius, Path.Direction.CW + ) + canvas.drawPath(ovalPath, shadowPaint) } - } else { - paintRender.color = dataSet.color - - var j = 0 - while (j < buffer.size()) { - if (!viewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) { - j += 4 - continue - } - - if (!viewPortHandler.isInBoundsRight(buffer.buffer[j])) { - break - } - - if (dataProvider.isDrawBarShadowEnabled) { - if (roundedShadowRadius > 0) { - canvas.drawRoundRect( - RectF( - buffer.buffer[j], viewPortHandler.contentTop(), - buffer.buffer[j + 2], - viewPortHandler.contentBottom() - ), roundedShadowRadius, roundedShadowRadius, shadowPaint - ) - } else { - canvas.drawRect( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3], paintRender - ) - } - } - if (roundedPositiveDataSetRadius > 0) { - canvas.drawRoundRect( - RectF( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3] - ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, paintRender - ) - } else { - canvas.drawRect( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3], paintRender - ) - } - j += 4 + paintRender.color = dataSet.getColorByIndex(j / 4) + if (roundedPositiveDataSetRadius > 0f) { + ovalPath.reset() + ovalPath.addRoundRect( + RectF(left, top, right, bottom), + roundedPositiveDataSetRadius, + roundedPositiveDataSetRadius, + Path.Direction.CW + ) + canvas.drawPath(ovalPath, paintRender) + } else { + canvas.drawRect(left, top, right, bottom, paintRender) } + j += 4 } } + // 5b) single‐color bars + else { + paintRender.color = dataSet.color + var j = 0 + while (j < buffer.size()) { + val left = buffer.buffer[j] + val top = buffer.buffer[j + 1] + val right = buffer.buffer[j + 2] + val bottom = buffer.buffer[j + 3] + + if (!handler.isInBoundsLeft(right)) { + j += 4; continue + } + if (!handler.isInBoundsRight(left)) break - val isSingleColor = dataSet.colors.size == 1 - if (isSingleColor) { - paintRender.color = dataSet.getColorByIndex(index) - } + if (dataProvider.isDrawBarShadowEnabled && roundedShadowRadius > 0f) { + ovalPath.reset() + ovalPath.addRoundRect( + RectF(left, handler.contentTop(), right, handler.contentBottom()), + roundedShadowRadius, roundedShadowRadius, Path.Direction.CW + ) + canvas.drawPath(ovalPath, shadowPaint) + } - var j = 0 - while (j < buffer.size()) { - if (!viewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) { + if (roundedPositiveDataSetRadius > 0f) { + ovalPath.reset() + ovalPath.addRoundRect( + RectF(left, top, right, bottom), + roundedPositiveDataSetRadius, + roundedPositiveDataSetRadius, + Path.Direction.CW + ) + canvas.drawPath(ovalPath, paintRender) + } else { + canvas.drawRect(left, top, right, bottom, paintRender) + } j += 4 - continue } + } - if (!viewPortHandler.isInBoundsRight(buffer.buffer[j])) { - break - } + // 6) gradient overlay + var j = 0 + if (singleColor) paintRender.color = dataSet.getColorByIndex(index) + while (j < buffer.size()) { + val left = buffer.buffer[j] + val top = buffer.buffer[j + 1] + val right = buffer.buffer[j + 2] + val bottom = buffer.buffer[j + 3] - if (!isSingleColor) { - paintRender.color = dataSet.getColorByIndex(j / 4) + if (!handler.isInBoundsLeft(right)) { + j += 4; continue } + if (!handler.isInBoundsRight(left)) break + if (!singleColor) paintRender.color = dataSet.getColorByIndex(j / 4) paintRender.shader = LinearGradient( - buffer.buffer[j], - buffer.buffer[j + 3], - buffer.buffer[j], - buffer.buffer[j + 1], - dataSet.getColorByIndex(j / 4), - dataSet.getColorByIndex(j / 4), + left, bottom, left, top, + paintRender.color, paintRender.color, Shader.TileMode.MIRROR ) - paintRender.shader = LinearGradient( - buffer.buffer[j], - buffer.buffer[j + 3], - buffer.buffer[j], - buffer.buffer[j + 1], - dataSet.getColorByIndex(j / 4), - dataSet.getColorByIndex(j / 4), - Shader.TileMode.MIRROR - ) - - dataSet.getEntryForIndex(j / 4)?.let { barEntry -> - - if ((barEntry.y < 0 && roundedNegativeDataSetRadius > 0)) { - val path2 = roundRect( - RectF( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3] - ), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, tl = true, tr = true, br = true, bl = true - ) - canvas.drawPath(path2, paintRender) - } else if ((barEntry.y > 0 && roundedPositiveDataSetRadius > 0)) { - val path2 = roundRect( - RectF( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3] - ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, tl = true, tr = true, br = true, bl = true - ) - canvas.drawPath(path2, paintRender) - } else { - canvas.drawRect( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3], paintRender - ) - } + val entryY = dataSet.getEntryForIndex(j / 4)?.y ?: 0f + val radius = if (entryY < 0f) roundedNegativeDataSetRadius else roundedPositiveDataSetRadius + if (radius > 0f) { + ovalPath.reset() + ovalPath.addRoundRect( + RectF(left, top, right, bottom), + radius, radius, Path.Direction.CW + ) + canvas.drawPath(ovalPath, paintRender) + } else { + canvas.drawRect(left, top, right, bottom, paintRender) } j += 4 } } + paintRender.shader = null } override fun drawHighlighted(canvas: Canvas, indices: Array) { - dataProvider.barData?.let { barData -> - - for (high in indices) { - val set = barData.getDataSetByIndex(high.dataSetIndex) - - if (set == null || !set.isHighlightEnabled) - continue - - - set.getEntryForXValue(high.x, high.y)?.let { barEntry -> - - if (!isInBoundsX(barEntry, set)) { - continue - } - - val trans = dataProvider.getTransformer(set.axisDependency) - - paintHighlight.color = set.highLightColor - paintHighlight.alpha = set.highLightAlpha - - val isStack = high.stackIndex >= 0 && barEntry.isStacked - - val y1: Float - val y2: Float - - if (isStack) { - if (dataProvider.isHighlightFullBarEnabled) { - y1 = barEntry.positiveSum - y2 = -barEntry.negativeSum - } else { - val range = barEntry.ranges[high.stackIndex] - - y1 = range.from - y2 = range.to - } - } else { - y1 = barEntry.y - y2 = 0f - } - - prepareBarHighlight(barEntry.x, y1, y2, barData.barWidth / 2f, trans!!) - - setHighlightDrawPos(high, barRect) - - val path2 = roundRect( - RectF( - barRect.left, barRect.top, barRect.right, - barRect.bottom - ), radius, radius, tl = true, tr = true, br = true, bl = true - ) - - canvas.drawPath(path2, paintHighlight) + // 1) early exits + val handler = viewPortHandler + val barData = dataProvider.barData + + for (h in indices) { + // 2) only highlight enabled sets + val set = barData?.getDataSetByIndex(h.dataSetIndex) ?: continue + if (!set.isHighlightEnabled) continue + + // 3) find the matching Entry + val e = set.getEntryForXValue(h.x, h.y) ?: continue + if (!isInBoundsX(e, set)) continue + + // 4) compute the y‐range of the highlight (stack vs. normal) + val isStack = h.stackIndex >= 0 && e.isStacked + val (y1, y2) = if (isStack) { + if (dataProvider.isHighlightFullBarEnabled) { + e.positiveSum to -e.negativeSum + } else { + val range = e.ranges[h.stackIndex] + range.from to range.to } + } else { + e.y to 0f } - } - } - @Suppress("SameParameterValue") - 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) + // 5) transform values to pixel‐rect + val trans = dataProvider.getTransformer(set.axisDependency) ?: continue + prepareBarHighlight( + e.x, + y1, + y2, + barData.barWidth / 2f, + trans + ) + + // 5b) record the center/top into the Highlight object so markers can be drawn + setHighlightDrawPos(h, barRect) + + // 6) clip any highlights that are fully off‐screen + if (!handler.isInBoundsLeft(barRect.right) || + !handler.isInBoundsRight(barRect.left) || + !handler.isInBoundsTop(barRect.bottom) || + !handler.isInBoundsBottom(barRect.top) + ) { + continue + } - if (bl) { - path.rQuadTo(0f, ry, rx, ry) //bottom-left corner - } else { - path.rLineTo(0f, ry) - path.rLineTo(rx, 0f) - } + // 7) choose corner radius + val radius = if (useAutoFullRadius) { + abs((barRect.right - barRect.left) / 2f) + } else { + defaultRadius + } - path.rLineTo(widthMinusCorners, 0f) - if (br) path.rQuadTo(rx, 0f, rx, -ry) //bottom-right corner - else { - path.rLineTo(rx, 0f) - path.rLineTo(0f, -ry) + // 8) draw your rounded highlight + paintHighlight.color = set.highLightColor + paintHighlight.alpha = set.highLightAlpha + canvas.drawRoundRect(barRect, radius, radius, paintHighlight) } - - path.rLineTo(0f, -heightMinusCorners) - path.close() - return path } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedHorizontalBarChartRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedHorizontalBarChartRenderer.kt index c3a2ed380..5a7433522 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedHorizontalBarChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/RoundedHorizontalBarChartRenderer.kt @@ -27,7 +27,7 @@ class RoundedHorizontalBarChartRenderer( override fun drawDataSet(canvas: Canvas, dataSet: IBarDataSet, index: Int) { initBuffers() - val trans = dataProvider.getTransformer(dataSet.axisDependency) + val transformer = dataProvider.getTransformer(dataSet.axisDependency) barBorderPaint.color = dataSet.barBorderColor barBorderPaint.strokeWidth = dataSet.barBorderWidth.convertDpToPixel() shadowPaint.color = dataSet.barShadowColor @@ -48,7 +48,7 @@ class RoundedHorizontalBarChartRenderer( mBarShadowRectBuffer.top = x - barWidthHalf mBarShadowRectBuffer.bottom = x + barWidthHalf } - trans!!.rectValueToPixel(mBarShadowRectBuffer) + transformer!!.rectValueToPixel(mBarShadowRectBuffer) if (!viewPortHandler.isInBoundsTop(mBarShadowRectBuffer.bottom)) { i++ continue @@ -74,7 +74,7 @@ class RoundedHorizontalBarChartRenderer( buffer.inverted = dataProvider.isInverted(dataSet.axisDependency) dataProvider.barData?.let { buffer.barWidth = it.barWidth } buffer.feed(dataSet) - trans!!.pointValuesToPixel(buffer.buffer) + transformer!!.pointValuesToPixel(buffer.buffer) // if multiple colors has been assigned to Bar Chart if (dataSet.colors.size > 1) {