Skip to content

Instantly share code, notes, and snippets.

@patrickmichalik
Created April 28, 2024 13:28
Show Gist options
  • Save patrickmichalik/b2fc0a480d901f4c072357a74fbed7ce to your computer and use it in GitHub Desktop.
Save patrickmichalik/b2fc0a480d901f4c072357a74fbed7ce to your computer and use it in GitHub Desktop.
import android.graphics.Typeface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLineComponent
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStartAxis
import com.patrykandpatrick.vico.compose.cartesian.fullWidth
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberColumnCartesianLayer
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoScrollState
import com.patrykandpatrick.vico.compose.common.of
import com.patrykandpatrick.vico.core.cartesian.CartesianDrawContext
import com.patrykandpatrick.vico.core.cartesian.HorizontalLayout
import com.patrykandpatrick.vico.core.cartesian.axis.AxisPosition
import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.common.Dimensions
import com.patrykandpatrick.vico.core.common.VerticalPosition
import com.patrykandpatrick.vico.core.common.component.LineComponent
import com.patrykandpatrick.vico.core.common.component.TextComponent
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import kotlin.random.Random
private class BottomAxis(
label: TextComponent,
private val labelPredicate: CartesianDrawContext.(Float) -> Boolean,
line: LineComponent,
) : HorizontalAxis<AxisPosition.Horizontal.Bottom>(AxisPosition.Horizontal.Bottom) {
init {
this.label = label
valueFormatter = Companion.valueFormatter
axisLine = line
guideline
}
override fun drawBehindChart(context: CartesianDrawContext) {
with(context) {
val baseCanvasX =
(if (isLtr) bounds.left else bounds.right) - horizontalScroll +
horizontalDimensions.startPadding * layoutDirectionMultiplier
val maxLabelHeight = (bounds.height() - axisThickness / 2).toInt()
val labelCanvasY = bounds.top + axisThickness
val labelX = buildList {
var x = chartValues.minX
while (x <= chartValues.maxX) {
if (labelPredicate(x)) add(x)
x += chartValues.xStep
}
}
labelX.forEachIndexed { index, x ->
val text = valueFormatter.format(value = x, chartValues = chartValues, verticalAxisPosition = null)
var canvasX =
baseCanvasX +
(x - chartValues.minX) / chartValues.xStep *
horizontalDimensions.xSpacing *
layoutDirectionMultiplier
if (index == 0 || index == labelX.lastIndex) {
val width = label?.getWidth(context = this, text = text, height = maxLabelHeight) ?: 0f
canvasX =
if (isLtr && index == 0 || !isLtr && index == labelX.lastIndex) {
canvasX.coerceAtLeast(bounds.left + width / 2)
} else {
canvasX.coerceAtMost(bounds.right - width / 2)
}
}
label?.drawText(
context = this,
text = text,
textX = canvasX,
textY = labelCanvasY,
verticalPosition = VerticalPosition.Bottom,
maxTextHeight = maxLabelHeight,
rotationDegrees = labelRotationDegrees,
)
}
axisLine?.drawHorizontal(
context = context,
left = chartBounds.left,
right = chartBounds.right,
centerY = bounds.top + axisThickness / 2,
)
}
}
private companion object {
val dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
val valueFormatter = CartesianValueFormatter { x, _, _ -> dateTimeFormatter.format(LocalTime.of(x.toInt(), 0)) }
}
}
@Composable
private fun rememberBottomAxis(labelPredicate: CartesianDrawContext.(Float) -> Boolean): BottomAxis {
val label = rememberAxisLabelComponent(padding = Dimensions.of(vertical = 2.dp), typeface = Typeface.DEFAULT)
val line = rememberAxisLineComponent()
return remember(label, line) { BottomAxis(label, labelPredicate, line) }
}
@Preview(showBackground = true)
@Composable
private fun Example() {
val modelProducer = remember {
CartesianChartModelProducer.build {
columnSeries { series(x = List(48) { it / 2f }, y = List(48) { (Random.nextFloat() + 1) / 2 }) }
}
}
CartesianChartHost(
chart =
rememberCartesianChart(
rememberColumnCartesianLayer(spacing = 2.dp),
startAxis =
rememberStartAxis(label = rememberAxisLabelComponent(typeface = Typeface.DEFAULT), tick = null),
bottomAxis = rememberBottomAxis { it % 6f == 0f || it == 23f },
),
modelProducer = modelProducer,
scrollState = rememberVicoScrollState(scrollEnabled = false),
horizontalLayout = HorizontalLayout.fullWidth(scalableStartPadding = 2.dp, scalableEndPadding = 2.dp),
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment