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, 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>() 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?) {} }