lebonprix.apk/java/io/xdrm/lebonprix/anim/BarChart.kt

183 lines
5.8 KiB
Kotlin

package io.xdrm.lebonprix.anim
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.support.v4.content.res.ResourcesCompat
import android.util.Log
import android.view.animation.DecelerateInterpolator
import io.xdrm.lebonprix.R
import kotlin.math.*
class BarChart(
data : Array<Int>,
private var originalWidth : Int,
private val originalHeight : Int,
val ctx: Context
) : Drawable() {
// PARAMS
private val relativeMinBarSize = 0.01F
private val relativeSideMargin = 0.1F // only 1 side
private val relativeSpaceWidth = 1F // relative to bar size
// fixed/processed values
private val minReadableSlotSize = 90F
private val textDistance = 120F
private val fontSize = 32F
private val sideMargin = originalWidth * 0.5F * relativeSideMargin
private val width = originalWidth - 2F*sideMargin
private val minBarSize = relativeMinBarSize * width
private val minSlotSize = minBarSize * (1F + relativeSpaceWidth)
// available width / (size of minimum bar + space) - ending space
// private val maxSlotCount = width / (minBarSize * (1F+relativeSpaceWidth) - minBarSize*relativeSpaceWidth)
// calculated on the run
private var barSize: Float
private var slotSize: Float
// animation
private val animationDuration = 500L
private var animatedHeight = 0
private val animator = ValueAnimator.ofInt(0, originalHeight)
// price -> count
private val distributions = arrayListOf<Pair<Float,Int>>()
init{
require( data.isNotEmpty() )
// get maximum number of bars
val barCount = data.distinct().size
val minPrice = data.min()!!.toFloat()
val maxPrice = data.max()!!.toFloat()
// defaults
slotSize = max(width / barCount, minSlotSize )
var slotCount = ( width / slotSize ).toInt()
// avail width / (elemts + 1 + 1 end space)
slotSize = width / (slotCount+1F-0.5F)
barSize = slotSize / (1F+relativeSpaceWidth)
var step = ( maxPrice - minPrice ) / slotCount.toFloat()
// move between min and max incrementing by 'step' every time
var slot = minPrice
var sum = 0
while( slot <= maxPrice ){
val min = slot - step*0.5F
val max = slot + step*0.5F
data.forEach {
// first slot -> inclusive min
if( slot <= minPrice && it >= min && it <= max )
sum++
else if( it > min && it <= max )
sum++
}
distributions.add( Pair(slot, sum) )
sum = 0
slot += step
}
/**
* SETUP ANIMATION
*/
animator.duration = animationDuration
animator.interpolator = DecelerateInterpolator()
animator.addUpdateListener {
animatedHeight = it.animatedValue as Int
invalidateSelf()
}
animator.start()
}
override fun draw(canvas: Canvas) {
//create base color (roboto light with gradient)
val gradientPaint = Paint()
gradientPaint.shader = LinearGradient(0F,0F, originalWidth.toFloat(), animatedHeight.toFloat(), 0xff71f0b5.toInt(), 0xff3e91e3.toInt(), Shader.TileMode.CLAMP)
val textColor = Paint()
textColor.typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
textColor.textSize = this.fontSize
textColor.textAlign = Paint.Align.CENTER
textColor.color = Color.WHITE
textColor.flags = Paint.ANTI_ALIAS_FLAG
// text background
val bg = Paint()
bg.color = ResourcesCompat.getColor(ctx.resources, R.color.colorBackground, null)
bg.flags = Paint.ANTI_ALIAS_FLAG
canvas.drawRoundRect(
RectF(-50F, originalHeight-textDistance+10F, originalWidth+50F, originalHeight+textDistance+1F),
100F, 100F,
bg
)
drawText(canvas, textColor)
drawBars(canvas, gradientPaint)
setBounds(0,0,originalWidth,animatedHeight)
}
private fun drawText(canvas: Canvas, paint: Paint){
distributions.forEachIndexed { i, value ->
// get mod where text is printed 1 over mod times (exceptions: start, end)
val mod = max(1, (minReadableSlotSize / slotSize).toInt())
if( i == 0 || i == distributions.size-1 || i % mod == 0 ){
val x = sideMargin + i.toFloat() * slotSize + barSize/2F
val y = originalHeight.toFloat()
var text = "${ round(value.first) }"
if( text == "${value.first.toInt()}.0" )
text = "${ value.first.toInt() }"
//save canvas, rotate, translate , draw, then restore to previous state
canvas.save()
canvas.rotate(-45F,x,y- textDistance*0.4F)
canvas.drawText(text,x,y - textDistance*0.4F, paint)
canvas.restore()
}
}
}
private fun drawBars(canvas: Canvas, paint: Paint){
val maxCount = distributions.flatMap { listOf(it.second.toFloat()) }.max()!!
val maxAnimatedHeight = animatedHeight - textDistance
distributions.forEachIndexed { i, d ->
// process coordinates
val x = sideMargin + i.toFloat() * slotSize
val height = d.second.toFloat() / maxCount
val y = maxAnimatedHeight * (1 - height)
canvas.drawRoundRect(
RectF(x, y, x+barSize, originalHeight.toFloat()-textDistance),
barSize, barSize,
paint)
}
}
override fun setAlpha(alpha: Int) {}
override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}
override fun setColorFilter(colorFilter: ColorFilter?) {}
}