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