|
|
|
@ -25,9 +25,12 @@ import com.google.android.material.color.MaterialColors
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
|
|
|
import org.koitharu.kotatsu.parsers.util.replaceWith
|
|
|
|
import org.koitharu.kotatsu.parsers.util.replaceWith
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toIntUp
|
|
|
|
import kotlin.math.absoluteValue
|
|
|
|
import kotlin.math.absoluteValue
|
|
|
|
import kotlin.math.max
|
|
|
|
import kotlin.math.max
|
|
|
|
|
|
|
|
import kotlin.math.roundToInt
|
|
|
|
import kotlin.math.sqrt
|
|
|
|
import kotlin.math.sqrt
|
|
|
|
|
|
|
|
import kotlin.random.Random
|
|
|
|
import com.google.android.material.R as materialR
|
|
|
|
import com.google.android.material.R as materialR
|
|
|
|
|
|
|
|
|
|
|
|
class BarChartView @JvmOverloads constructor(
|
|
|
|
class BarChartView @JvmOverloads constructor(
|
|
|
|
@ -35,10 +38,12 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
) : View(context, attrs, defStyleAttr) {
|
|
|
|
) : View(context, attrs, defStyleAttr) {
|
|
|
|
|
|
|
|
|
|
|
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
|
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
|
|
|
|
|
|
private val rawData = ArrayList<Bar>()
|
|
|
|
private val bars = ArrayList<Bar>()
|
|
|
|
private val bars = ArrayList<Bar>()
|
|
|
|
private var maxValue: Int = 0
|
|
|
|
private var maxValue: Int = 0
|
|
|
|
private var spacing = context.resources.resolveDp(2f)
|
|
|
|
private val minBarSpacing = context.resources.resolveDp(12f)
|
|
|
|
private val minSpace = context.resources.resolveDp(20f)
|
|
|
|
private val minSpace = context.resources.resolveDp(20f)
|
|
|
|
|
|
|
|
private val barWidth = context.resources.resolveDp(12f)
|
|
|
|
private val outlineColor = context.getThemeColor(materialR.attr.colorOutline)
|
|
|
|
private val outlineColor = context.getThemeColor(materialR.attr.colorOutline)
|
|
|
|
private val dottedEffect = DashPathEffect(
|
|
|
|
private val dottedEffect = DashPathEffect(
|
|
|
|
floatArrayOf(
|
|
|
|
floatArrayOf(
|
|
|
|
@ -50,7 +55,7 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
private val chartBounds = RectF()
|
|
|
|
private val chartBounds = RectF()
|
|
|
|
|
|
|
|
|
|
|
|
@ColorInt
|
|
|
|
@ColorInt
|
|
|
|
var barColor: Int = Color.MAGENTA
|
|
|
|
var barColor: Int = context.getThemeColor(materialR.attr.colorAccent)
|
|
|
|
set(value) {
|
|
|
|
set(value) {
|
|
|
|
field = value
|
|
|
|
field = value
|
|
|
|
invalidate()
|
|
|
|
invalidate()
|
|
|
|
@ -58,6 +63,16 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
|
|
|
|
|
|
|
|
init {
|
|
|
|
init {
|
|
|
|
paint.strokeWidth = context.resources.resolveDp(1f)
|
|
|
|
paint.strokeWidth = context.resources.resolveDp(1f)
|
|
|
|
|
|
|
|
if (isInEditMode) {
|
|
|
|
|
|
|
|
setData(
|
|
|
|
|
|
|
|
List(Random.nextInt(20, 60)) {
|
|
|
|
|
|
|
|
Bar(
|
|
|
|
|
|
|
|
value = Random.nextInt(-20, 400).coerceAtLeast(0),
|
|
|
|
|
|
|
|
label = it.toString(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onDraw(canvas: Canvas) {
|
|
|
|
override fun onDraw(canvas: Canvas) {
|
|
|
|
@ -65,7 +80,7 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
if (bars.isEmpty() || chartBounds.isEmpty) {
|
|
|
|
if (bars.isEmpty() || chartBounds.isEmpty) {
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val barWidth = ((chartBounds.width() + spacing) / bars.size.toFloat() - spacing)
|
|
|
|
val spacing = (chartBounds.width() - (barWidth * bars.size.toFloat())) / (bars.size + 1).toFloat()
|
|
|
|
// dashed horizontal lines
|
|
|
|
// dashed horizontal lines
|
|
|
|
paint.color = outlineColor
|
|
|
|
paint.color = outlineColor
|
|
|
|
paint.style = Paint.Style.STROKE
|
|
|
|
paint.style = Paint.Style.STROKE
|
|
|
|
@ -75,19 +90,23 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
val y = chartBounds.top + (chartBounds.height() * i / maxValue.toFloat())
|
|
|
|
val y = chartBounds.top + (chartBounds.height() * i / maxValue.toFloat())
|
|
|
|
canvas.drawLine(paddingLeft.toFloat(), y, (width - paddingLeft - paddingRight).toFloat(), y, paint)
|
|
|
|
canvas.drawLine(paddingLeft.toFloat(), y, (width - paddingLeft - paddingRight).toFloat(), y, paint)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// bottom line
|
|
|
|
|
|
|
|
paint.color = outlineColor
|
|
|
|
|
|
|
|
paint.style = Paint.Style.STROKE
|
|
|
|
|
|
|
|
canvas.drawLine(chartBounds.left, chartBounds.bottom, chartBounds.right, chartBounds.bottom, paint)
|
|
|
|
// bars
|
|
|
|
// bars
|
|
|
|
paint.style = Paint.Style.FILL
|
|
|
|
paint.style = Paint.Style.FILL
|
|
|
|
paint.color = barColor
|
|
|
|
paint.color = barColor
|
|
|
|
paint.pathEffect = null
|
|
|
|
paint.pathEffect = null
|
|
|
|
|
|
|
|
val corner = barWidth / 2f
|
|
|
|
for ((i, bar) in bars.withIndex()) {
|
|
|
|
for ((i, bar) in bars.withIndex()) {
|
|
|
|
val h = chartBounds.height() * bar.value / maxValue.toFloat()
|
|
|
|
if (bar.value == 0) {
|
|
|
|
val x = i * (barWidth + spacing) + paddingLeft
|
|
|
|
continue
|
|
|
|
canvas.drawRect(x, chartBounds.bottom - h, x + barWidth, chartBounds.bottom, paint)
|
|
|
|
}
|
|
|
|
|
|
|
|
val h = (chartBounds.height() * bar.value / maxValue.toFloat()).coerceAtLeast(barWidth)
|
|
|
|
|
|
|
|
val x = spacing + i * (barWidth + spacing) + paddingLeft
|
|
|
|
|
|
|
|
canvas.drawRoundRect(x, chartBounds.bottom - h, x + barWidth, chartBounds.bottom, corner, corner, paint)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// bottom line
|
|
|
|
|
|
|
|
paint.color = outlineColor
|
|
|
|
|
|
|
|
paint.style = Paint.Style.STROKE
|
|
|
|
|
|
|
|
canvas.drawLine(chartBounds.left, chartBounds.bottom, chartBounds.right, chartBounds.bottom, paint)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
|
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
|
|
@ -96,11 +115,25 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fun setData(value: List<Bar>) {
|
|
|
|
fun setData(value: List<Bar>) {
|
|
|
|
bars.replaceWith(value)
|
|
|
|
rawData.replaceWith(value)
|
|
|
|
maxValue = if (value.isEmpty()) 0 else value.maxOf { it.value }
|
|
|
|
compressBars()
|
|
|
|
invalidate()
|
|
|
|
invalidate()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun compressBars() {
|
|
|
|
|
|
|
|
if (rawData.isEmpty() || width <= 0) {
|
|
|
|
|
|
|
|
maxValue = 0
|
|
|
|
|
|
|
|
bars.clear()
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
var fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing
|
|
|
|
|
|
|
|
val windowSize = (fullWidth / width.toFloat()).toIntUp()
|
|
|
|
|
|
|
|
bars.replaceWith(
|
|
|
|
|
|
|
|
rawData.chunked(windowSize) { it.average() },
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
maxValue = bars.maxOf { it.value }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun computeValueStep(): Int {
|
|
|
|
private fun computeValueStep(): Int {
|
|
|
|
val h = chartBounds.height()
|
|
|
|
val h = chartBounds.height()
|
|
|
|
var step = 1
|
|
|
|
var step = 1
|
|
|
|
@ -118,6 +151,18 @@ class BarChartView @JvmOverloads constructor(
|
|
|
|
(width - paddingLeft - paddingRight).toFloat() - inset,
|
|
|
|
(width - paddingLeft - paddingRight).toFloat() - inset,
|
|
|
|
(height - paddingTop - paddingBottom).toFloat() - inset,
|
|
|
|
(height - paddingTop - paddingBottom).toFloat() - inset,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
compressBars()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun Collection<Bar>.average(): Bar {
|
|
|
|
|
|
|
|
return when (size) {
|
|
|
|
|
|
|
|
0 -> Bar(0, "")
|
|
|
|
|
|
|
|
1 -> first()
|
|
|
|
|
|
|
|
else -> Bar(
|
|
|
|
|
|
|
|
value = (sumOf { it.value } / size.toFloat()).roundToInt(),
|
|
|
|
|
|
|
|
label = "%s - %s".format(first().label, last().label),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class Bar(
|
|
|
|
class Bar(
|
|
|
|
|