Skip to content

Commit

Permalink
Improve slice drawing
Browse files Browse the repository at this point in the history
  • Loading branch information
mahozad committed Aug 30, 2021
1 parent 8f2c3a9 commit 0d25f28
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 62 deletions.
Binary file modified piechart/src/androidTest/assets/screenshot-72.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -602,12 +602,19 @@ class ScreenshotTest {
}
}

@Test fun chartWithASingleSlice() {
@Test fun chartWithAFullSingleSlice() {
compareScreenshots("screenshot-72") {
slices = listOf(Slice(1f, Color.rgb(133, 77, 206)))
}
}

@Test fun chartWithAFullSingleSliceAndPointer() {
compareScreenshots("screenshot-73") {
slices = listOf(Slice(1f, Color.rgb(133, 77, 206)))
slicesPointer = PieChart.SlicePointer(20.dp, 30.dp, 0)
}
}

/**
* FIXME: the name of the function is misleading. It also works in a saving
* mode in that it just saves the screenshot on device and skips the comparison.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.withPrecision
import org.assertj.core.util.FloatComparator
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
Expand Down Expand Up @@ -1283,47 +1284,46 @@ class SizeUtilInstrumentedTest {

// region makeSlice

@Test fun makeSlice_WithNoPointer() {
val center = Coordinates(500f, 500f)
val pieEnclosingRect = RectF(0f, 0f, 1000f, 1000f)
val sliceStartAngle = -90f
val sliceFraction = 0.5f
val pointer: PieChart.SlicePointer? = null
val drawDirection = CLOCKWISE
@Nested inner class MakeSliceTest {

val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)
private var center = Coordinates(500f, 500f)
private var pieEnclosingRect = RectF(0f, 0f, 1000f, 1000f)
private var sliceStartAngle = -90f
private var sliceFraction = 0.5f
private var pointer: PieChart.SlicePointer? = null
private var drawDirection = CLOCKWISE

assertThat(PathMeasure(slice, false).length).isEqualTo(2570.638f)
}
@Test fun makeSlice_WithNoPointer() {
val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)

/**
* Path::reset should be called in the method to clear previous path object state.
*/
@Test fun makeSlice_ThePathShouldBeResetBeforeEachCall() {
val center = Coordinates(500f, 500f)
val pieEnclosingRect = RectF(0f, 0f, 1000f, 1000f)
val sliceStartAngle = -90f
val sliceFraction = 0.5f
val pointer: PieChart.SlicePointer? = null
val drawDirection = CLOCKWISE
assertThat(PathMeasure(slice, false).length).isEqualTo(2570.638f)
}

makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction + 100f, drawDirection, pointer)
val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)
/**
* Path::reset should be called in the method to clear previous path object state.
*/
@Test fun makeSlice_ThePathShouldBeResetBeforeEachCall() {
makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction + 100f, drawDirection, pointer)
val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)

assertThat(PathMeasure(slice, false).length).isEqualTo(2570.638f)
}
assertThat(PathMeasure(slice, false).length).isEqualTo(2570.638f)
}

@Test fun makeSlice_WithArbitraryPointer() {
val center = Coordinates(500f, 500f)
val pieEnclosingRect = RectF(0f, 0f, 1000f, 1000f)
val sliceStartAngle = -90f
val sliceFraction = 0.5f
val pointer = PieChart.SlicePointer(50.px, 40.px, 0)
val drawDirection = CLOCKWISE
@Test fun makeSlice_WithArbitraryPointer() {
pointer = PieChart.SlicePointer(50.px, 40.px, 0)

val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)
val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)

assertThat(PathMeasure(slice, false).length).isEqualTo(2381.9175f)
}

assertThat(PathMeasure(slice, false).length).isEqualTo(2381.9175f)
@Test fun makeSlice_aFullFraction() {
sliceFraction = 1f

val slice = makeSlice(center, pieEnclosingRect, sliceStartAngle, sliceFraction, drawDirection, pointer)

assertThat(PathMeasure(slice, false).length).isEqualTo(3141.2751f)
}
}

// endregion
Expand Down
31 changes: 18 additions & 13 deletions piechart/src/main/kotlin/ir/mahozad/android/SizeUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -686,38 +686,43 @@ private fun k(θ: Float, w: Float, h: Float): Float {

internal fun makeSlice(
center: Coordinates,
pieEnclosingRect: RectF,
sliceStartAngle: Float,
sliceFraction: Float,
enclosingRect: RectF,
startAngle: Float,
fraction: Float,
drawDirection: DrawDirection,
pointer: SlicePointer?
): Path {
val sliceSweep = calculateSweep(sliceFraction, drawDirection)
val sweepAngle = calculateSweep(fraction, drawDirection)
path.reset()
path.moveTo(center.x, center.y)
if (pointer == null) {
path.arcTo(pieEnclosingRect, sliceStartAngle, sliceSweep)
// See https://stackoverflow.com/q/19383842
if (fraction == 1f) {
path.addCircle(center.x, center.y, enclosingRect.width() / 2, Path.Direction.CW)
} else {
path.arcTo(enclosingRect, startAngle, sweepAngle)
}
} else {
val radiusReduction = pointer.length.px
val newEnclosingRect = RectF(
pieEnclosingRect.left + radiusReduction,
pieEnclosingRect.top + radiusReduction,
pieEnclosingRect.right - radiusReduction,
pieEnclosingRect.bottom - radiusReduction
enclosingRect.left + radiusReduction,
enclosingRect.top + radiusReduction,
enclosingRect.right - radiusReduction,
enclosingRect.bottom - radiusReduction
)
val sliceMiddleAngle = calculateMiddleAngle(sliceStartAngle, sliceFraction, drawDirection)
val sliceMiddleAngle = calculateMiddleAngle(startAngle, fraction, drawDirection)
val newRadius = newEnclosingRect.width() / 2f
val pointerFraction = pointer.width.px / (2 * PI * newRadius).toFloat()
val stop1Angle = calculateMiddleAngle(sliceMiddleAngle, -pointerFraction, drawDirection)
val stop2Angle = calculateMiddleAngle(sliceMiddleAngle, pointerFraction, drawDirection)
val stopsSweepAngle = calculateAnglesDistance(sliceStartAngle, stop1Angle, drawDirection)
val stopsSweepAngle = calculateAnglesDistance(startAngle, stop1Angle, drawDirection)
val stop2Coordinates = calculateCoordinatesOnCircumference(stop2Angle, center, newRadius)
val (tipX, tipY) = calculateCoordinatesOnCircumference(sliceMiddleAngle, center, newRadius + pointer.length.px)
path.arcTo(newEnclosingRect, sliceStartAngle, stopsSweepAngle)
path.arcTo(newEnclosingRect, startAngle, stopsSweepAngle)
path.lineTo(tipX, tipY)
path.lineTo(stop2Coordinates.x, stop2Coordinates.y)
path.arcTo(newEnclosingRect, stop2Angle, stopsSweepAngle)
}
path.close()
path.close() // This call is not necessary regarding the shape; but semantically necessary
return path
}
18 changes: 3 additions & 15 deletions piechart/src/main/kotlin/ir/mahozad/android/component/Pie.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,7 @@ internal open class Pie(
mainPaint.shader = gradient
mainPaint.alpha = 255

val slicePath = if (slice.fraction == 1f) {
Path().apply { addCircle(center.x, center.y, radius, Path.Direction.CW) }
} else {
makeSlice(center, pieEnclosingRect, currentAngle, slice.fraction, pieDrawDirection, slice.pointer ?: slicesPointer)
}
val slicePath = makeSlice(center, pieEnclosingRect, currentAngle, slice.fraction, pieDrawDirection, slice.pointer ?: slicesPointer)
canvas.withClip(clip) {
canvas.drawPath(slicePath, mainPaint)
}
Expand Down Expand Up @@ -330,11 +326,7 @@ internal class AnimatedPie(
mainPaint.shader = gradient
mainPaint.alpha = 255

val slicePath = if (slice.fraction == 1f) {
Path().apply { addCircle(center.x, center.y, radius, Path.Direction.CW) }
} else {
makeSlice(center, pieEnclosingRect, currentAngle, slice.fraction * animationFraction, pieDrawDirection, slice.pointer ?: slicesPointer)
}
val slicePath = makeSlice(center, pieEnclosingRect, currentAngle, slice.fraction * animationFraction, pieDrawDirection, slice.pointer ?: slicesPointer)
canvas.withClip(clip) {
canvas.drawPath(slicePath, mainPaint)
}
Expand Down Expand Up @@ -410,11 +402,7 @@ internal class AnimatedPie2(
mainPaint.shader = gradient
mainPaint.alpha = 255

val slicePath = if (slice.fraction == 1f) {
Path().apply { addCircle(center.x, center.y, radius, Path.Direction.CW) }
} else {
makeSlice(center, pieEnclosingRect, currentAngle, slice.fraction * animationFraction, pieDrawDirection, slice.pointer ?: slicesPointer)
}
val slicePath = makeSlice(center, pieEnclosingRect, currentAngle, slice.fraction * animationFraction, pieDrawDirection, slice.pointer ?: slicesPointer)
canvas.withClip(clip) {
canvas.drawPath(slicePath, mainPaint)
}
Expand Down

0 comments on commit 0d25f28

Please sign in to comment.