Reputation: 579
I'm trying to implement a pie chart as shown in pictures below, where corners of the arc should be rounded.
I've tried to use CornerPathEffect()
, but it seems to work only on the intersection of two lines (path.lineTo()
). Nothing changes if I use this method for arc (path.arcTo()
).
Upvotes: 1
Views: 1299
Reputation: 1
I know it's too late for this answer but here is my solution.
PieSlice.kt
data class PieSlice(
val name: String,
var value: Double,
var startAngle: Float,
var sweepAngle: Float,
var indicatorCircleLocation: PointF,
val paint: Paint
)
Function to make round corner arc
private fun drawCurvedArc(canvas: Canvas?, pieItem: PieSlice) {
val path = Path()
path.moveTo(originX, originY)
val angleStart = pieItem.startAngle
val angleEnd = (pieItem.startAngle - pieItem.sweepAngle)
val arcOffset = pieItem.sweepAngle.coerceAtMost(7f)
val lineOffset = if (pieItem.sweepAngle < 7f) 0f else 25f
// line from origin to top
val line1x = getPointX(angleStart, lineOffset)
val line1y = getPointY(angleStart, lineOffset)
path.lineTo(line1x, line1y)
//Curve corner from line top to arc start
val arcStartx = getPointX(angleStart - arcOffset)
val arcStarty = getPointY(angleStart - arcOffset)
joinLineAndArc(path, line1x, line1y, arcStartx, arcStarty)
//Arc
path.arcTo(
outerRect, (pieItem.startAngle - arcOffset),
(-pieItem.sweepAngle + 2 * arcOffset), true
)
val line2x = getPointX(angleEnd, lineOffset)
val line2y = getPointY(angleEnd, lineOffset)
val arcEndx = getPointX(angleEnd + arcOffset)
val arcEndy = getPointY(angleEnd + arcOffset)
//Curve corner from arc end to bottom line
joinLineAndArc(path, arcEndx, arcEndy, line2x, line2y)
// Bottom line
path.lineTo(originX, originY)
val borderPaint = Paint()
borderPaint.strokeJoin = Paint.Join.ROUND
borderPaint.strokeCap = Paint.Cap.ROUND
borderPaint.color = pieItem.paint.color
borderPaint.style = Paint.Style.FILL
borderPaint.strokeWidth = 0f
canvas?.drawPath(path, borderPaint)
}
/**
* Join line and arc with a curve
*
* vector = (x1-x2,y1-y2)
*
* pVector perpendicular to vector
* pVector = (y1-y2,x2-x1)
*
* midX = (x1+x2)/2
* midY = (y1+y2)/2
*
* (px,py) = (midX,midY) ± (D/√((y1−y2)^2,(x2−x1)^2))*(y1-y2,x2-x1)
*/
private fun joinLineAndArc(
path: Path,
x1: Float,
y1: Float,
x2: Float,
y2: Float
) {
val midX: Float = (x2 + x1) / 2f
val midY: Float = (y2 + y1) / 2f
val x2_x1 = (x2 - x1).toDouble()
val y1_y2 = (y1 - y2).toDouble()
val powY = y1_y2.pow(2.0)
val powX = x2_x1.pow(2.0)
val den = sqrt(powY + powX)
val len = 20.0
// perpendicular1
val p1x = (midX + ((len * y1_y2) / den)).toFloat()
val p1y = (midY + ((len * x2_x1) / den)).toFloat()
// perpendicular2
val p2x = (midX - ((len * y1_y2) / den)).toFloat()
val p2y = (midY - ((len * x2_x1) / den)).toFloat()
val len1 = Math.sqrt(
Math.pow((originX - p1x).toDouble(), 2.0)
+ Math.pow((originY - p1y).toDouble(), 2.0)
)
val len2 = Math.sqrt(
Math.pow((originX - p2x).toDouble(), 2.0)
+ Math.pow((originY - p2y).toDouble(), 2.0)
)
//Make a curve to the point which is far from origin
if (len1 > len2) {
path.cubicTo(x1, y1, p1x, p1y, x2, y2)
} else {
path.cubicTo(x1, y1, p2x, p2y, x2, y2)
}
}
/**
* Get the x coordinate on a circle
* formula for x pos: (radius) * cos(angle) + (distance from left edge of screen)
* @param angle angle of point from origin
* @param offset to make shorter line than actual radius
*/
private fun getPointX(angle: Float, offset: Float = 0f): Float {
return ((radius - offset)
* cos(Math.toRadians((angle.toDouble())))
+ originX).toFloat()
}
/**
* Get the y coordinate on a circle
* formula for y pos: (radius) * sin(angle) + (distance from top edge of screen)
*
* @param angle angle of point from origin
* @param offset to make shorter line than actual radius
*/
private fun getPointY(angle: Float, offset: Float = 0f): Float {
return ((radius - offset)
* sin(Math.toRadians((angle.toDouble())))
+ originY).toFloat()
}
Upvotes: 0
Reputation: 85
Try to set Stroke Cap of paint.
mPaint.setStrokeCap(Paint.Cap.ROUND);
Upvotes: 2