Skip to content

Commit 68d8326

Browse files
authored
Merge pull request #70 from ansehoon1999/fix-pie-onclick
�Fix uncorrect incorrect chart section detected in pie chart
2 parents fd62c27 + 529abf1 commit 68d8326

File tree

4 files changed

+104
-7
lines changed

4 files changed

+104
-7
lines changed

compose-charts/src/commonMain/kotlin/ir/ehsannarmani/compose_charts/PieChart.kt

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.runtime.remember
1616
import androidx.compose.runtime.rememberCoroutineScope
1717
import androidx.compose.runtime.setValue
1818
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.geometry.Offset
1920
import androidx.compose.ui.geometry.Rect
2021
import androidx.compose.ui.geometry.isUnspecified
2122
import androidx.compose.ui.graphics.Color
@@ -24,9 +25,11 @@ import androidx.compose.ui.graphics.PathMeasure
2425
import androidx.compose.ui.graphics.drawscope.Fill
2526
import androidx.compose.ui.graphics.drawscope.Stroke
2627
import androidx.compose.ui.input.pointer.pointerInput
28+
import ir.ehsannarmani.compose_charts.extensions.getAngleInDegree
29+
import ir.ehsannarmani.compose_charts.extensions.isDegreeBetween
30+
import ir.ehsannarmani.compose_charts.extensions.isInsideCircle
2731
import ir.ehsannarmani.compose_charts.models.Pie
2832
import kotlinx.coroutines.launch
29-
import kotlin.math.atan2
3033
import kotlin.random.Random
3134

3235
@Composable
@@ -52,11 +55,15 @@ fun PieChart(
5255

5356
val scope = rememberCoroutineScope()
5457

58+
var pieChartCenter by remember {
59+
mutableStateOf(Offset.Zero)
60+
}
61+
5562
var details by remember {
5663
mutableStateOf(emptyList<PieDetails>())
5764
}
5865
val pieces = remember {
59-
mutableListOf<Pair<String, Rect>>()
66+
mutableListOf<PiePiece>()
6067
}
6168

6269
val pathMeasure = remember {
@@ -132,8 +139,14 @@ fun PieChart(
132139
Canvas(modifier = modifier
133140
.pointerInput(Unit) {
134141
detectTapGestures { offset ->
135-
pieces
136-
.firstOrNull { it.second.contains(offset) }
142+
val angleInDegree = getAngleInDegree(
143+
touchTapOffset = offset,
144+
pieceOffset = pieChartCenter
145+
)
146+
147+
pieces.firstOrNull { piece ->
148+
isDegreeBetween(angleInDegree, piece.startFromDegree, piece.endToDegree)
149+
&& isInsideCircle(offset, pieChartCenter, piece.radius) }
137150
?.let {
138151
val (id, _) = it
139152
details.find { it.id == id }
@@ -144,6 +157,8 @@ fun PieChart(
144157
}
145158
}
146159
) {
160+
pieChartCenter = center
161+
147162
val radius: Float = when (style) {
148163
is Pie.Style.Fill -> {
149164
(minOf(size.width, size.height) / 2)
@@ -165,6 +180,15 @@ fun PieChart(
165180
val piecePath = if (degree >= 360.0) {
166181
// draw circle instead of arc
167182

183+
pieces.add(
184+
PiePiece(
185+
id = detail.id,
186+
radius = radius * detail.scale.value,
187+
startFromDegree = 0f,
188+
endToDegree = 360f
189+
)
190+
)
191+
168192
Path().apply {
169193
addOval(
170194
oval = Rect(
@@ -206,11 +230,18 @@ fun PieChart(
206230
(size.height / 2)
207231
)
208232
}
233+
234+
pieces.add(
235+
PiePiece(
236+
id = detail.id,
237+
radius = radius * detail.scale.value,
238+
startFromDegree = arcStart,
239+
endToDegree = if (arcStart + arcSweep >= 360f) 360f else arcStart + arcSweep,
240+
)
241+
)
209242
piecePath
210243
}
211-
val rect = piecePath.getBounds()
212244

213-
pieces.add(detail.id to rect)
214245
drawPath(
215246
path = piecePath,
216247
color = detail.color.value,
@@ -227,4 +258,11 @@ private data class PieDetails(
227258
val color: Animatable<Color, AnimationVector4D> = Animatable(pie.color),
228259
val scale: Animatable<Float, AnimationVector1D> = Animatable(1f),
229260
val space: Animatable<Float, AnimationVector1D> = Animatable(0f)
230-
)
261+
)
262+
263+
private data class PiePiece(
264+
val id: String,
265+
val radius: Float,
266+
val startFromDegree: Float,
267+
val endToDegree: Float,
268+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package ir.ehsannarmani.compose_charts.extensions
2+
3+
import androidx.compose.ui.geometry.Offset
4+
import kotlin.math.pow
5+
6+
fun isInsideCircle(touchTapOffset: Offset, pieceOffset: Offset, radius: Float): Boolean {
7+
// Calculate the squared distance between the touch point and the circle's center
8+
val distanceSquared =
9+
(touchTapOffset.x - pieceOffset.x).pow(2) + (touchTapOffset.y - pieceOffset.y).pow(2)
10+
11+
// Compare the squared distance with the squared radius to avoid using sqrt (more efficient)
12+
return distanceSquared <= radius.pow(2)
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ir.ehsannarmani.compose_charts.extensions
2+
3+
import androidx.compose.ui.geometry.Offset
4+
import ir.ehsannarmani.compose_charts.models.Vector
5+
import kotlin.math.PI
6+
import kotlin.math.acos
7+
import kotlin.math.pow
8+
import kotlin.math.sqrt
9+
10+
fun getAngleInDegree(touchTapOffset: Offset, pieceOffset: Offset): Float {
11+
12+
// Create vectors: u (from piece to tap point) and v (reference vector along x-axis)
13+
val u = Vector(touchTapOffset.x - pieceOffset.x, touchTapOffset.y - pieceOffset.y)
14+
15+
// Reference vector along the positive x-axis
16+
val v = Vector(1f, 0f)
17+
18+
// Compute the dot product of vectors u and v
19+
val dotProduct = u.x * v.x + u.y * v.y
20+
21+
// Compute the magnitudes of vectors u and v
22+
val magnitudeU = sqrt(u.x.pow(2) + u.y.pow(2))
23+
val magnitudeV = sqrt(v.x.pow(2) + v.y.pow(2))
24+
25+
// Calculate the angle in radians using the dot product formula
26+
val angleInRadians = acos(dotProduct / (magnitudeU * magnitudeV))
27+
28+
// Convert the angle from radians to degrees
29+
// Adjust the angle based on the y-direction to get the correct orientation
30+
val angleInDegrees = if ((touchTapOffset.y - pieceOffset.y) > 0) {
31+
angleInRadians * (180 / PI)
32+
} else {
33+
360 - angleInRadians * (180 / PI)
34+
}
35+
return angleInDegrees.toFloat()
36+
}
37+
38+
fun isDegreeBetween(target: Float, start: Float, end: Float): Boolean {
39+
return target in start..end
40+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package ir.ehsannarmani.compose_charts.models
2+
3+
data class Vector(
4+
val x: Float,
5+
val y: Float
6+
)

0 commit comments

Comments
 (0)