Skip to content

Commit e756245

Browse files
committed
Added fill animation and PieChart instance
1 parent 02e2580 commit e756245

File tree

4 files changed

+161
-81
lines changed

4 files changed

+161
-81
lines changed
Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,49 @@
11
package com.faskn.clickablepiechart
22

33
import android.os.Bundle
4+
import android.util.Log
45
import androidx.appcompat.app.AppCompatActivity
56
import com.faskn.lib.PieChart
67
import com.faskn.lib.Slice
78
import kotlinx.android.synthetic.main.activity_main.*
9+
import kotlin.random.Random
810

911
class MainActivity : AppCompatActivity() {
1012
override fun onCreate(savedInstanceState: Bundle?) {
1113
super.onCreate(savedInstanceState)
1214
setContentView(R.layout.activity_main)
1315
chart.setCenterColor(R.color.white)
14-
/* chart.setStartPoint(-90f)*/
15-
chart.setSliceWidth(250f)
16-
chart.setListener { data, index ->
17-
/* Toast.makeText(this, index.toString(), Toast.LENGTH_SHORT).show()*/
18-
}
1916

2017
// Example
21-
var pieChart = PieChart.Builder(arrayOf(
22-
Slice(30f, R.color.brown700),
23-
Slice(60f, R.color.materialRed700),
24-
Slice(120f, R.color.materialIndigo600),
25-
Slice(150f, R.color.materialRed400)
26-
))
18+
val pieChart0 = PieChart.Builder(
19+
arrayOf(
20+
Slice(30f, R.color.brown700),
21+
Slice(60f, R.color.materialRed700),
22+
Slice(120f, R.color.materialIndigo600),
23+
Slice(150f, R.color.materialRed400)
24+
)
25+
)
26+
.setClickListener { string, float ->
27+
Log.d("ses", "s " + string)
28+
Log.d("ses", "f " + float.toString())
29+
}
30+
.build()
2731

28-
chart.setSliceColor(
29-
intArrayOf(
30-
R.color.brown700,
31-
R.color.materialRed700,
32-
R.color.materialIndigo600,
33-
R.color.materialRed400
32+
// Example 2
33+
val pieChart1 = PieChart.Builder(
34+
arrayOf(
35+
Slice(Random.nextInt(0, 100).toFloat(), R.color.brown700),
36+
Slice(Random.nextInt(0, 100).toFloat(), R.color.materialRed700),
37+
Slice(Random.nextInt(0, 100).toFloat(), R.color.materialIndigo600),
38+
Slice(Random.nextInt(0, 100).toFloat(), R.color.materialRed400)
3439
)
3540
)
36-
chart.setDataPoints(floatArrayOf(30f, 60f, 120f, 150f))
41+
.setClickListener { string, float ->
42+
Log.d("ses", "s " + string)
43+
Log.d("ses", "f " + float.toString())
44+
}
45+
.build()
46+
47+
chart.setPieChart(pieChart0)
3748
}
3849
}

lib/src/main/java/com/faskn/lib/ClickablePieChart.kt

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package com.faskn.lib
44
* Created by Furkan on 6.08.2020
55
*/
66

7-
import android.annotation.SuppressLint
7+
import android.animation.ValueAnimator
88
import android.content.Context
99
import android.content.res.ColorStateList
1010
import android.graphics.Canvas
@@ -16,6 +16,7 @@ import android.view.Gravity
1616
import android.view.LayoutInflater
1717
import android.view.MotionEvent
1818
import android.view.View
19+
import android.view.animation.LinearInterpolator
1920
import android.widget.*
2021
import androidx.core.content.ContextCompat
2122
import androidx.core.view.doOnPreDraw
@@ -27,34 +28,43 @@ import kotlin.math.sin
2728

2829
class ClickablePieChart @JvmOverloads constructor(
2930
context: Context,
30-
attrs: AttributeSet? = null,
31+
private val attrs: AttributeSet? = null,
3132
defStyleAttr: Int = 0
3233
) : View(context, attrs, defStyleAttr) {
3334

3435
private var slicePaint: Paint = Paint()
3536
private var centerPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
36-
private var sliceColors: IntArray = intArrayOf(Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW)
3737
private var rectF: RectF? = null
38-
private var dataPoints: FloatArray = floatArrayOf()
39-
private var sliceStartPoint = 0F
38+
private var sliceStartPoint = 0F // FIXME: 16-Aug-20 remove if unnecessary
4039
private var sliceWidth = 80f
4140
private var touchX = 0f
4241
private var touchY = 0f
43-
private var clickListener: ((String, Float) -> Unit)? = null
44-
private var pointsArray = arrayListOf<Pair<Float, Float>>()
42+
43+
// PieChart variables
44+
private var pieChart: PieChart? = null
45+
private lateinit var slices: List<Slice>
46+
47+
// Animation variables
48+
private var animator: ValueAnimator? = null
49+
private var currentSweepAngle = 0
4550

4651
// Attributes
4752
private lateinit var popupText: String
4853

4954
init {
55+
initAttributes(attrs)
56+
}
57+
58+
private fun init() {
5059
slicePaint.isAntiAlias = true
5160
slicePaint.isDither = true
5261
slicePaint.style = Paint.Style.FILL
5362

5463
centerPaint.color = Color.WHITE
5564
centerPaint.style = Paint.Style.FILL
5665

57-
initAttributes(attrs)
66+
initSlices()
67+
startAnimation()
5868
}
5969

6070
private fun initAttributes(attrs: AttributeSet?) {
@@ -68,43 +78,76 @@ class ClickablePieChart @JvmOverloads constructor(
6878
}
6979
}
7080

71-
private fun scale(): FloatArray {
72-
val scaledValues = FloatArray(dataPoints.size)
73-
for (i in dataPoints.indices) {
74-
scaledValues.fill((dataPoints[i] / getTotal()) * 360, i, dataPoints.size)
81+
private fun initSlices() {
82+
slices = pieChart?.slices?.toList()!!
83+
}
84+
85+
private fun startAnimation() {
86+
animator?.cancel()
87+
animator = ValueAnimator.ofInt(0, 360).apply {
88+
duration = 1000
89+
interpolator = LinearInterpolator()
90+
addUpdateListener { valueAnimator ->
91+
currentSweepAngle = valueAnimator.animatedValue as Int
92+
invalidate()
93+
}
7594
}
76-
return scaledValues
95+
animator?.start()
7796
}
7897

79-
@SuppressLint("DrawAllocation")
80-
override fun onDraw(canvas: Canvas?) {
81-
super.onDraw(canvas)
98+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
99+
super.onSizeChanged(w, h, oldw, oldh)
82100

83101
rectF = RectF(
84102
0f,
85103
0f,
86104
width.coerceAtMost(height).toFloat(),
87105
width.coerceAtMost(height).toFloat()
88106
)
107+
}
89108

90-
val scaledValues = scale()
109+
override fun onDraw(canvas: Canvas?) {
110+
super.onDraw(canvas)
91111

92-
for (i in scaledValues.indices) {
93-
slicePaint.color = ContextCompat.getColor(context, sliceColors[i])
94-
canvas!!.drawArc(rectF!!, sliceStartPoint, scaledValues[i], true, slicePaint)
95-
pointsArray.add(Pair(sliceStartPoint, sliceStartPoint + scaledValues[i]))
96-
sliceStartPoint += scaledValues[i]
97-
}
112+
if (pieChart != null) {
113+
slices.forEach { slice ->
114+
val arc = slice.arc!!
115+
if (currentSweepAngle > arc.startAngle + arc.sweepAngle) {
116+
slicePaint.color = ContextCompat.getColor(context, slice.color)
117+
canvas?.drawArc(
118+
rectF!!,
119+
pieChart?.sliceStartPoint!! + arc.startAngle,
120+
arc.sweepAngle,
121+
true,
122+
slicePaint
123+
)
124+
} else {
125+
if (currentSweepAngle > arc.startAngle) {
126+
slicePaint.color = ContextCompat.getColor(context, slice.color)
127+
canvas?.drawArc(
128+
rectF!!,
129+
pieChart?.sliceStartPoint!! + arc.startAngle,
130+
currentSweepAngle - arc.startAngle,
131+
true,
132+
slicePaint
133+
)
134+
}
135+
}
136+
}
98137

99-
val centerX = (measuredWidth / 2).toFloat()
100-
val centerY = (measuredHeight / 2).toFloat()
101-
val radius = centerX.coerceAtMost(centerY)
138+
val centerX = (measuredWidth / 2).toFloat()
139+
val centerY = (measuredHeight / 2).toFloat()
140+
val radius = centerX.coerceAtMost(centerY)
102141

103-
canvas!!.drawCircle(rectF!!.centerX(), rectF!!.centerY(), radius - sliceWidth, centerPaint)
142+
canvas!!.drawCircle(
143+
rectF!!.centerX(),
144+
rectF!!.centerY(),
145+
radius - pieChart?.sliceWidth!!,
146+
centerPaint
147+
)
148+
}
104149
}
105150

106-
private fun getTotal(): Float = dataPoints.sum()
107-
108151
override fun onTouchEvent(event: MotionEvent?): Boolean {
109152
return when (event?.action) {
110153
MotionEvent.ACTION_DOWN -> {
@@ -120,7 +163,8 @@ class ClickablePieChart @JvmOverloads constructor(
120163
)
121164
)
122165

123-
touchAngle -= sliceStartPoint
166+
// FIXME: 16-Aug-20 Remove subtraction if unnecessary. On runtime sliceStartPoint is always 0f.
167+
// touchAngle -= sliceStartPoint
124168
touchAngle %= 360
125169

126170
if (touchAngle < 0) {
@@ -129,10 +173,10 @@ class ClickablePieChart @JvmOverloads constructor(
129173

130174
var total = 0.0f
131175
var forEachStopper = false // what a idiot stuff
132-
dataPoints.forEachIndexed { index, data ->
133-
total += data % 360f
176+
slices.forEachIndexed { index, slice ->
177+
total += slice.dataPoint % 360f
134178
if (touchAngle <= total && !forEachStopper) {
135-
clickListener?.invoke(touchAngle.toString(), index.toFloat())
179+
pieChart?.clickListener?.invoke(touchAngle.toString(), index.toFloat())
136180
forEachStopper = true
137181
showInfoPopup(index)
138182
}
@@ -150,13 +194,14 @@ class ClickablePieChart @JvmOverloads constructor(
150194
val width = LinearLayout.LayoutParams.WRAP_CONTENT
151195
val height = LinearLayout.LayoutParams.WRAP_CONTENT
152196
val popupWindow = PopupWindow(popupView, width, height, true)
153-
var center = pointsArray[index].toList().average()
197+
var center = slices[index].arc?.average()!!
154198
val halfRadius = rectF!!.centerX()
155199

156-
popupView.findViewById<TextView>(R.id.textViewPopupText).text = "${center.toInt()} $popupText"
200+
popupView.findViewById<TextView>(R.id.textViewPopupText).text =
201+
"${center.toInt()} $popupText"
157202
ImageViewCompat.setImageTintList(
158203
popupView.findViewById<ImageView>(R.id.imageViewPopupCircleIndicator),
159-
ColorStateList.valueOf(ContextCompat.getColor(context, sliceColors[index]))
204+
ColorStateList.valueOf(ContextCompat.getColor(context, slices[index].color))
160205
)
161206

162207
val calculatedX =
@@ -167,7 +212,7 @@ class ClickablePieChart @JvmOverloads constructor(
167212
val currentViewLocation = IntArray(2)
168213
this.getLocationOnScreen(currentViewLocation)
169214

170-
val halfOfSliceWidth = (sliceWidth / 2).toInt()
215+
val halfOfSliceWidth = (pieChart?.sliceWidth?.p2d(context)!! / 2).toInt()
171216
val popupWindowX =
172217
(currentViewLocation[0] + halfRadius.toInt()) + calculatedX -
173218
(if (calculatedX < 0) -halfOfSliceWidth else halfOfSliceWidth)
@@ -189,38 +234,18 @@ class ClickablePieChart @JvmOverloads constructor(
189234
popupWindow.height
190235
)
191236
}
192-
193-
val currentData = dataPoints[index]
194-
195-
}
196-
197-
fun setSliceWidth(width: Float) {
198-
sliceWidth = width.p2d(context)
199237
}
200238

201-
fun setListener(listener: (String, Float) -> (Unit)) {
202-
clickListener = listener
203-
}
204-
205-
fun setStartPoint(point: Float) {
206-
sliceStartPoint = point
207-
}
208-
209-
fun setDataPoints(data: FloatArray) {
210-
dataPoints = data
211-
invalidateAndRequestLayout()
239+
fun setPieChart(pieChart: PieChart) {
240+
this.pieChart = pieChart
241+
init()
212242
}
213243

214244
fun setCenterColor(colorId: Int) {
215245
centerPaint.color = ContextCompat.getColor(context, colorId)
216246
invalidateAndRequestLayout()
217247
}
218248

219-
fun setSliceColor(colors: IntArray) {
220-
sliceColors = colors
221-
invalidateAndRequestLayout()
222-
}
223-
224249
private fun invalidateAndRequestLayout() {
225250
invalidate()
226251
requestLayout()

lib/src/main/java/com/faskn/lib/PieChart.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@ package com.faskn.lib
44
* Created by turkergoksu on 12-Aug-20
55
*/
66

7-
class PieChart private constructor() {
7+
class PieChart private constructor(
8+
var slices: Array<Slice>,
9+
var clickListener: ((String, Float) -> Unit)? = null,
10+
var sliceStartPoint: Float,
11+
var sliceWidth: Float
12+
) {
813
data class Builder(
914
private var slices: Array<Slice>,
1015
private var clickListener: ((String, Float) -> Unit)? = null,
1116
private var sliceStartPoint: Float? = 0f,
1217
private var sliceWidth: Float? = 80f
1318
) {
19+
init {
20+
initScaledArcs()
21+
}
22+
1423
fun setSlices(slices: Array<Slice>) = apply { this.slices = slices }
1524
fun setClickListener(clickListener: ((String, Float) -> Unit)) =
1625
apply { this.clickListener = clickListener }
@@ -21,5 +30,31 @@ class PieChart private constructor() {
2130
fun setSliceWidth(sliceWidth: Float) = apply { this.sliceWidth = sliceWidth }
2231

2332
fun getSlices() = slices
33+
34+
fun build(): PieChart =
35+
PieChart(
36+
slices,
37+
clickListener,
38+
sliceStartPoint!!,
39+
sliceWidth!!
40+
)
41+
42+
private fun initScaledArcs() {
43+
slices.forEachIndexed { i, slice ->
44+
val scaledValue = (slice.dataPoint / getSumOfDataPoints()) * 360
45+
if (i != 0) {
46+
slice.arc = Arc(
47+
slices[i - 1].arc?.sweepAngle!!,
48+
slices[i - 1].arc?.sweepAngle!!.plus(scaledValue)
49+
)
50+
} else {
51+
slice.arc = Arc(0f, scaledValue)
52+
}
53+
}
54+
}
55+
56+
private fun getSumOfDataPoints(): Float {
57+
return slices.sumByDouble { slice -> slice.dataPoint.toDouble() }.toFloat()
58+
}
2459
}
2560
}

0 commit comments

Comments
 (0)