feat: add GrowthLineChart composable
This commit is contained in:
parent
b2d0620219
commit
75b5397932
|
|
@ -0,0 +1,165 @@
|
||||||
|
package com.syaroful.agrilinkvocpro.growth_recipe_feature.core.component
|
||||||
|
|
||||||
|
import android.graphics.Paint
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.asAndroidPath
|
||||||
|
import androidx.compose.ui.graphics.asComposePath
|
||||||
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
|
import androidx.compose.ui.graphics.nativeCanvas
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.syaroful.agrilinkvocpro.presentation.theme.MainGreen
|
||||||
|
import kotlin.math.round
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GrowthLineChart(
|
||||||
|
dates: List<Int> = emptyList(),
|
||||||
|
values: List<Double> = emptyList(),
|
||||||
|
horizontalValue: Double? = null,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
graphColor: Color = MainGreen
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = Color.Transparent,
|
||||||
|
shape = RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
.padding(top = 32.dp)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
val spacing = 80f
|
||||||
|
val transparentGraphColor = remember {
|
||||||
|
graphColor.copy(alpha = 0.5f)
|
||||||
|
}
|
||||||
|
val upperValue = remember(values) {
|
||||||
|
(values.maxOrNull()?.plus(1))?.roundToInt() ?: 0
|
||||||
|
}
|
||||||
|
val lowerValue = remember(values) {
|
||||||
|
values.minOrNull()?.toInt() ?: 0
|
||||||
|
}
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val isDarkMode = isSystemInDarkTheme()
|
||||||
|
val textPaint = remember(density) {
|
||||||
|
Paint().apply {
|
||||||
|
color =
|
||||||
|
if (isDarkMode) android.graphics.Color.WHITE else android.graphics.Color.BLACK
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
textSize = density.run { 12.sp.toPx() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas(modifier = modifier) {
|
||||||
|
val spacePerDate = (size.width - spacing) / values.size
|
||||||
|
|
||||||
|
dates.forEachIndexed { i, date ->
|
||||||
|
drawContext.canvas.nativeCanvas.drawText(
|
||||||
|
date.toString(),
|
||||||
|
spacing + i * spacePerDate,
|
||||||
|
size.height - 5,
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val step = (upperValue - lowerValue) / 5f
|
||||||
|
(0..4).forEach { i ->
|
||||||
|
drawContext.canvas.nativeCanvas.drawText(
|
||||||
|
round(lowerValue + step * i).toString(),
|
||||||
|
30f,
|
||||||
|
size.height - spacing - i * size.height / 5f,
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastX = 0f
|
||||||
|
val strokePath = Path().apply {
|
||||||
|
val height = size.height
|
||||||
|
for (i in values.indices) {
|
||||||
|
val value = values[i]
|
||||||
|
val nextValue = values.getOrNull(i + 1) ?: values.last()
|
||||||
|
val leftRatio = (value - lowerValue) / (upperValue - lowerValue)
|
||||||
|
val rightRatio = (nextValue - lowerValue) / (upperValue - lowerValue)
|
||||||
|
|
||||||
|
val x1 = spacing + i * spacePerDate
|
||||||
|
val y1 = height - spacing - (leftRatio * height).toFloat()
|
||||||
|
val x2 = spacing + (i + 1) * spacePerDate
|
||||||
|
val y2 = height - spacing - (rightRatio * height).toFloat()
|
||||||
|
if (i == 0) {
|
||||||
|
moveTo(x1, y1)
|
||||||
|
}
|
||||||
|
lastX = (x1 + x2) / 2f
|
||||||
|
quadraticTo(x1, y1, lastX, (y1 + y2) / 2f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val fillPath = android.graphics.Path(strokePath.asAndroidPath())
|
||||||
|
.asComposePath()
|
||||||
|
.apply {
|
||||||
|
lineTo(lastX, size.height - spacing)
|
||||||
|
lineTo(spacing, size.height - spacing)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
|
||||||
|
drawPath(
|
||||||
|
path = fillPath,
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(
|
||||||
|
transparentGraphColor,
|
||||||
|
Color.Transparent
|
||||||
|
),
|
||||||
|
endY = size.height - spacing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawPath(
|
||||||
|
path = strokePath,
|
||||||
|
color = graphColor,
|
||||||
|
style = Stroke(
|
||||||
|
width = 3.dp.toPx(),
|
||||||
|
cap = StrokeCap.Round
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val labelPadding = 12.dp.toPx()
|
||||||
|
drawContext.canvas.nativeCanvas.drawText(
|
||||||
|
"ppm",
|
||||||
|
spacing / 2f,
|
||||||
|
labelPadding - 100,
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
drawContext.canvas.nativeCanvas.drawText(
|
||||||
|
"date",
|
||||||
|
size.width - size.width,
|
||||||
|
size.height - 4,
|
||||||
|
textPaint
|
||||||
|
)
|
||||||
|
|
||||||
|
horizontalValue?.let { value ->
|
||||||
|
val ratio = (value - lowerValue) / (upperValue - lowerValue)
|
||||||
|
val y = size.height - spacing - (ratio * size.height).toFloat()
|
||||||
|
drawLine(
|
||||||
|
color = Color.Red,
|
||||||
|
start = Offset(spacing, y),
|
||||||
|
end = Offset(size.width, y),
|
||||||
|
strokeWidth = 2.dp.toPx(),
|
||||||
|
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user