patrykandpatrick / vico

A light and extensible chart library for Android.
https://patrykandpatrick.com/vico/wiki
Apache License 2.0
2.07k stars 125 forks source link

java.lang.IllegalArgumentException: x must be < bitmap.width() #830

Closed diego-francisco-jt closed 1 month ago

diego-francisco-jt commented 1 month ago

Hey guys,

First of all, thank you a lot for doing this library, you've been doing such an amazing job šŸ™‡

How to reproduce

Just running the app with the following code throws the exception. We've been trying modifying our code here and there without any luck.

So sorry for the code being so dirty, we're in the middle of a migration from the 2.0.0-alpha.14 version.

package your.package

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.view.HapticFeedbackConstantsCompat
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottomAxis
import com.patrykandpatrick.vico.compose.cartesian.fullWidth
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLine
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLayer
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoScrollState
import com.patrykandpatrick.vico.compose.common.ProvideVicoTheme
import com.patrykandpatrick.vico.compose.common.component.rememberShapeComponent
import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent
import com.patrykandpatrick.vico.compose.common.fill
import com.patrykandpatrick.vico.compose.common.of
import com.patrykandpatrick.vico.compose.common.rememberHorizontalLegend
import com.patrykandpatrick.vico.compose.common.rememberLegendItem
import com.patrykandpatrick.vico.compose.common.vicoTheme
import com.patrykandpatrick.vico.core.cartesian.CartesianDrawContext
import com.patrykandpatrick.vico.core.cartesian.CartesianMeasureContext
import com.patrykandpatrick.vico.core.cartesian.HorizontalLayout
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.LineCartesianLayerModel
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener
import com.patrykandpatrick.vico.core.cartesian.marker.DefaultCartesianMarker
import com.patrykandpatrick.vico.core.cartesian.marker.LineCartesianLayerMarkerTarget
import com.patrykandpatrick.vico.core.common.Dimensions
import com.patrykandpatrick.vico.core.common.HorizontalLegend
import com.patrykandpatrick.vico.core.common.component.TextComponent
import com.patrykandpatrick.vico.core.common.shape.Shape
import java.math.BigDecimal
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime

@Suppress("MagicNumber")
@Composable
fun AttendanceChart(
    chartModelProducer: CartesianChartModelProducer,
    chartKpis: AttendanceChartIntervalAnalytics.DailyKpis,
    selectedDay: LocalDate,
    currentTime: LocalDateTime,
    modifier: Modifier = Modifier,
    enableInitialAnimation: Boolean = true,
    onChartTimeMarkerVisible: (LocalTime) -> Unit = { },
    onChartTimeMarkerHidden: () -> Unit = { },
) {
    val view = LocalView.current
    val xAxisTimeValueList = remember { List(24) { it } }
    val timeFormatter = rememberTimeFormatter(xAxisTimeValueList)
    // val marker = rememberMarker(AttendanceChartDataEntry.Actual())
    // val markerWithoutGuideline = rememberMarker(AttendanceChartDataEntry.Actual(), showGuideline = false)
    var isChartMarkerShown by remember { mutableStateOf(false) }
    //val currentTimeMarker = remember(currentTime, isChartMarkerShown) {
    //    if (!isChartMarkerShown && selectedDay == currentTime.toLocalDate()) {
    //        mapOf(
    //            currentTime.hour.toFloat() to markerWithoutGuideline,
    //        )
    //    } else {
    //        emptyMap()
    //    }
    //}
    val chartDataEntries = remember(selectedDay, currentTime, chartKpis) {
        when {
            selectedDay.isAfter(currentTime.toLocalDate()) -> listOf(
                AttendanceChartDataEntry.Planned(),
                AttendanceChartDataEntry.Actual(isActive = false),
            )

            chartKpis.isVirtualShiftAnalytics -> listOf(
                AttendanceChartDataEntry.Planned(isActive = false),
                AttendanceChartDataEntry.Actual(),
            )

            else -> listOf(AttendanceChartDataEntry.Planned(), AttendanceChartDataEntry.Actual())
        }.sortedBy { it.index }
    }
    val markerVisibilityListener = remember {
        object : CartesianMarkerVisibilityListener {
            override fun onHidden(marker: CartesianMarker) {
                isChartMarkerShown = false
                onChartTimeMarkerHidden()
            }

            override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
                val target = targets.first() as LineCartesianLayerMarkerTarget
                val markerTimeIndex = target.points.lastOrNull()?.entry?.x?.toInt()

                if (markerTimeIndex != null) {
                    isChartMarkerShown = true
                    view.performHapticFeedback(HapticFeedbackConstantsCompat.CLOCK_TICK)
                    onChartTimeMarkerVisible(LocalTime.of(markerTimeIndex, 0))
                }
            }
        }
    }

    AttendanceChartTheme(chartColors = { chartDataEntriesColors(chartDataEntries = chartDataEntries) }) {
        CartesianChartHost(
            chart = rememberCartesianChart(
                rememberLineCartesianLayer(
                    lineProvider = rememberLineSpecifications(chartDataEntries = chartDataEntries),
                    pointSpacing = 32.dp,
                ),
                bottomAxis = rememberBottomAxis(
                    label = rememberAxisLabelStyle(),
                    tick = null,
                    guideline = null,
                    valueFormatter = timeFormatter,
                    itemPlacer = remember {
                        HorizontalAxis.ItemPlacer.default(
                            spacing = 4,
                            addExtremeLabelPadding = true,
                        )
                    },
                ),
                legend = rememberLineChartLegend(chartDataEntries = chartDataEntries),
                // persistentMarkers = currentTimeMarker,
                marker = DefaultCartesianMarker(
                    label = TextComponent(),
                ), // TODO: replace
                markerVisibilityListener = markerVisibilityListener,
                horizontalLayout = HorizontalLayout.fullWidth(),
            ),
            modelProducer = chartModelProducer,
            modifier = modifier.fillMaxWidth(),
            runInitialAnimation = enableInitialAnimation,
            scrollState = rememberVicoScrollState(scrollEnabled = false),
        )
    }
}

@Composable
private fun rememberTimeFormatter(xAxisTimeValueList: List<Int>): CartesianValueFormatter {
    return remember {
        CartesianValueFormatter { value, _, _ ->
            LocalTime.of(xAxisTimeValueList[value.toInt()], 0).toString()
        }
    }
}

@Preview
@Composable
internal fun AttendanceChartPreview() {
    Box(
        modifier = Modifier.background(
            color = Color.Blue,
            shape = RoundedCornerShape(10.dp),
        ),
    ) {
        AttendanceChart(
            chartModelProducer = chartModelProducer,
            chartKpis = sampleCharDailyKpis,
            selectedDay = LocalDate.of(2024, 7, 2),
            currentTime = LocalDate.of(2024, 7, 2).atTime(12, 0),
            enableInitialAnimation = false,
        )
    }
}

data class AttendanceChartIntervalAnalytics(
    val dayIntervalsData: List<DayIntervalData>,
    val dailyKpis: DailyKpis,
) {
    data class DayIntervalData(
        val startDateTime: LocalDateTime,
        val endDateTime: LocalDateTime,
        val workersCountersData: WorkersData,
    ) {
        data class WorkersData(
            val requestedWorkersCounter: Int,
            val plannedWorkersAttendedCounter: Int,
            val unplannedWorkersAttendedCounter: Int,
        )
    }

    data class DailyKpis(
        val attendanceRate: BigDecimal,
        val workersAssigned: Int,
        val workersAttended: Int,
        val noShowWorkers: Int,
    ) {
        val isVirtualShiftAnalytics = workersAssigned == 0 && workersAttended > 0
    }
}

internal val sampleCharDailyKpis = AttendanceChartIntervalAnalytics.DailyKpis(
    attendanceRate = BigDecimal.valueOf(.75),
    workersAssigned = 40,
    workersAttended = 30,
    noShowWorkers = 10,
)

@Composable
internal fun AttendanceChartTheme(
    chartColors: @Composable () -> List<Color>,
    content: @Composable () -> Unit,
) {
    val axisColor = Color.White

    ProvideVicoTheme(
        theme = vicoTheme.copy(
            lineCartesianLayerColors = chartColors(),
            textColor = axisColor,
        ),
        content = content,
    )
}

@Composable
internal fun chartDataEntriesColors(chartDataEntries: List<AttendanceChartDataEntry>): List<Color> =
    chartDataEntries.map {
        when (it) {
            is AttendanceChartDataEntry.Planned -> Color.Red
            is AttendanceChartDataEntry.Actual -> Color.Blue
        }
    }

@Composable
internal fun chartDataEntryColor(chartDataEntry: AttendanceChartDataEntry): Color =
    when (chartDataEntry) {
        is AttendanceChartDataEntry.Actual -> Color.Blue
        is AttendanceChartDataEntry.Planned -> Color.Red
    }

@Composable
internal fun rememberAxisLabelStyle(): TextComponent = rememberAxisLabelComponent(
    textSize = 10.sp,
    margins = Dimensions.of(horizontal = 0.dp, vertical = 8.dp),
)

@Composable
internal fun rememberLineSpecifications(chartDataEntries: List<AttendanceChartDataEntry>): LineCartesianLayer.LineProvider =
    LineCartesianLayer.LineProvider.series(
        chartDataEntries.map {
            val chartDataEntryColor = chartDataEntryColor(chartDataEntry = it)
            rememberLine(
                remember { LineCartesianLayer.LineFill.single(fill(chartDataEntryColor)) },
                thickness = 3.dp,
            )
        },
    )

@Composable
internal fun rememberLineChartLegend(
    chartDataEntries: List<AttendanceChartDataEntry>
): HorizontalLegend<CartesianMeasureContext, CartesianDrawContext> {
    val activeChartDataEntries = chartDataEntries.filter { it.isEnabled }

    return rememberHorizontalLegend(
        items = activeChartDataEntries.map {
            rememberLegendItem(
                icon = rememberShapeComponent(
                    shape = Shape.Pill,
                    color = chartDataEntryColor(chartDataEntry = it)
                ),
                labelComponent = rememberTextComponent(
                    color = Color.White,
                    textSize = 12.sp,
                ),
                label = parseChartDataEntryLegendLabel(it),
            )
        },
        iconSize = 8.dp,
        iconPadding = 8.dp,
        spacing = 20.dp,
        padding = Dimensions.of(vertical = 16.dp),
    )
}

@Composable
internal fun parseChartDataEntryLegendLabel(chartDataEntry: AttendanceChartDataEntry): CharSequence =
    when (chartDataEntry) {
        is AttendanceChartDataEntry.Planned -> "Planned"
        is AttendanceChartDataEntry.Actual -> "Actual"
    }

internal sealed class AttendanceChartDataEntry(val index: Int, val isEnabled: Boolean = true) {
    data class Planned(val isActive: Boolean = true) :
        AttendanceChartDataEntry(index = 0, isEnabled = isActive)

    data class Actual(val isActive: Boolean = true) :
        AttendanceChartDataEntry(index = 1, isEnabled = isActive)
}

internal data class ChartDataEntry(
    val time: LocalTime,
    val requestedWorkers: Int,
    val plannedWorkersAttended: Int,
    val unplannedWorkersAttended: Int,
)

internal val chartDataEntries = (0..23).map { hour ->
    val requested = 50 + hour % 5 * 2
    val plannedAttended = requested - hour % 8 - 10
    val unplannedAttended = hour % 2

    ChartDataEntry(
        time = LocalTime.of(hour, 0),
        requestedWorkers = requested,
        plannedWorkersAttended = plannedAttended,
        unplannedWorkersAttended = unplannedAttended,
    )
}.sortedBy { it.time }

private val chartModelProducerEntries = Pair(
    chartDataEntries.map { it.requestedWorkers },
    chartDataEntries.map { it.plannedWorkersAttended + it.unplannedWorkersAttended },
)

internal val chartDayRangePoints = (0..23).toList()
private val plannedChartModelValues = chartModelProducerEntries.first
private val currentChartModelValues = chartModelProducerEntries.second

internal val chartModelProducer = CartesianChartModelProducer.build().also {
    it.tryRunTransaction {
        add(
            LineCartesianLayerModel.partial {
                series(
                    x = chartDayRangePoints,
                    y = plannedChartModelValues,
                )
                series(
                    x = chartDayRangePoints,
                    y = currentChartModelValues,
                )
            },
        )
    }
}

Observed behavior

This is the thrown exception:

Process: com.jobandtalent.companies.staging, PID: 9602
                                                                                                    java.lang.IllegalArgumentException: x must be < bitmap.width()
                                                                                                        at android.graphics.Bitmap.checkPixelAccess(Bitmap.java:1989)
                                                                                                        at android.graphics.Bitmap.getPixel(Bitmap.java:1897)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.updateMarkerTargets(LineCartesianLayer.kt:392)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.drawInternal$lambda$3$lambda$2$lambda$1(LineCartesianLayer.kt:361)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.$r8$lambda$8NKgdkPG9ensLPvcQbSTw-p-wH8(Unknown Source:0)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer$$ExternalSyntheticLambda1.invoke(D8$$SyntheticClass:0)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.forEachPointInBounds(LineCartesianLayer.kt:548)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.drawInternal(LineCartesianLayer.kt:360)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.drawInternal(LineCartesianLayer.kt:76)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.layer.BaseCartesianLayer.draw(BaseCartesianLayer.kt:59)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.CartesianChart$drawingModelAndLayerConsumer$1.invoke(CartesianChart.kt:96)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.CartesianChart.forEachWithLayer(CartesianChart.kt:420)
                                                                                                        at com.patrykandpatrick.vico.core.cartesian.CartesianChart.draw-43zNVmo(CartesianChart.kt:247)
                                                                                                        at com.patrykandpatrick.vico.compose.cartesian.CartesianChartHostKt.CartesianChartHostImpl$lambda$19(CartesianChartHost.kt:228)
                                                                                                        at com.patrykandpatrick.vico.compose.cartesian.CartesianChartHostKt.$r8$lambda$-dawOEQ8SDGJZi3SqLyNfpT3eUg(Unknown Source:0)
                                                                                                        at com.patrykandpatrick.vico.compose.cartesian.CartesianChartHostKt$$ExternalSyntheticLambda5.invoke(D8$$SyntheticClass:0)
                                                                                                        at androidx.compose.ui.draw.DrawBackgroundModifier.draw(DrawModifier.kt:116)
                                                                                                        at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105)
                                                                                                        at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:364)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353)
                                                                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353)
                                                                                                        at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:926)
                                                                                                        at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:174)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353)
                                                                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353)
                                                                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353)
                                                                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353)
                                                                                                        at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361)
2024-08-05 17:00:23.124  9602-9602  AndroidRuntime          com.jobandtalent.companies.staging   E      at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:54) (Ask Gemini)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:383)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:382)
                                                                                                        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2303)
                                                                                                        at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:500)
                                                                                                        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:256)
                                                                                                        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:382)
                                                                                                        at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:380)
                                                                                                        at androidx.compose.ui.platform.RenderNodeApi29.record(RenderNodeApi29.android.kt:209)
                                                                                                        at androidx.compose.ui.platform.RenderNodeLayer.updateDisplayList(RenderNodeLayer.android.kt:335)
                                                                                                        at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:1236)
                                                                                                        at android.view.View.draw(View.java:22817)
                                                                                                        at android.view.View.updateDisplayListIfDirty(View.java:21668)
                                                                                                        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                        at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                        at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                        at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                        at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4588)
                                                                                                        at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4561)
                                                                                                        at android.view.View.updateDisplayListIfDirty(View.java:21625)
                                                                                                        at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534)
                                                                                                        at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:540)
                                                                                                        at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:620)
                                                                                                        at android.view.ViewRootImpl.draw(ViewRootImpl.java:4743)
                                                                                                        at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4447)
                                                                                                        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3529)
                                                                                                        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2305)
                                                                                                        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9133)
                                                                                                        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1232)
                                                                                                        at android.view.Choreographer.doCallbacks(Choreographer.java:1029)
                                                                                                        at android.view.Choreographer.doFrame(Choreographer.java:934)
                                                                                                        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1217)
                                                                                                        at android.os.Handler.handleCallback(Handler.java:938)
                                                                                                        at android.os.Handler.dispatchMessage(Handler.java:99)
                                                                                                        at android.os.Looper.loopOnce(Looper.java:233)
                                                                                                        at android.os.Looper.loop(Looper.java:344)
                                                                                                        at android.app.ActivityThread.main(ActivityThread.java:8212)
                                                                                                        at java.lang.reflect.Method.invoke(Native Method)
                                                                                                        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584)
                                                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)

Expected behavior

We'd expect the graph to draw as usual.

Vico version(s)

2.0.0-alpha.27

Android version(s)

Android 12

Additional information

No response

Gowsky commented 1 month ago

Hi @diego-francisco-jt, thank you for your nice words, we appreciate it! We haven't observed this crash in the sample app, and the code you shared contains unknown references and thus we can't run it. Please provide an MRE that we can run to reproduce the crash. #659 is a good example of a complete bug report.

We'll answer your migration-related questions as soon as possible.

qq2540040142 commented 1 month ago

I have also encountered this problem. After testing, I found that this situation occurs on devices with low performance (possibly due to insufficient computing power and fast scrolling, causing the marker to be outside the screen and x to get 0?), The specific operation is that normal sliding to view data will cause it to crash, but it will not appear on devices with good performance. My temporary solution is to display only 5 pieces of data, so that users cannot perform sliding operations and therefore cannot cause crashes. But this is not the solution to the fundamental problem.

diego-francisco-jt commented 1 month ago

@Gowsky Sorry for not providing the minimal reproducible example šŸ™‡ I've updated the original message with the MRE, I hope it helps.

matejbart commented 1 month ago

I ran into the same issue today when I simply just swapped the HorizontalLayout to horizontalLayout = HorizontalLayout.fullWidth() and then the chart started to crash in my @Preview. Later I realized that the crash only occurs if the chart encompasses the entire screen's width (which was true for my preview).

I was able to fix this by either using the HorizontalLayout.segmented() layout or by adding at least a 1.dp horizontal padding to the parent composable (theBox in the @Preview of your sample).

diego-francisco-jt commented 1 month ago

I ran into the same issue today when I simply just swapped the HorizontalLayout to horizontalLayout = HorizontalLayout.fullWidth() and then the chart started to crash in my @Preview. Later I realized that the crash only occurs if the chart encompasses the entire screen's width (which was true for my preview).

I was able to fix this by either using the HorizontalLayout.segmented() layout or by adding at least a 1.dp horizontal padding to the parent composable (theBox in the @Preview of your sample).

I can confirm that setting the layout to HorizontalLayout.segmented() worked. The 1.dp horizontal margin to the parent composable did the trick only for the Preview not for running the app.

Thanks for the temporary solution, @matejbart šŸ™Œ

diego-francisco-jt commented 1 month ago

Taking advantage of the fact that I have created this issue, we're in the middle of a migration from 2.0.0-alpha.14 to 2.0.0-alpha.17 and it would be amazing if you could help us with some questions we've faced during the migration.

Custom marker

We do have a custom marker to change the overlay design using the SpannedString. However, even though I read some issues and all the versions' documentation, I'm not able to migrate from the markedEntries to the targets.

This is the piece of code we'd like to convert to the CartesianMarkerValueFormatter:

private val labelFormatter: MarkerLabelFormatter = MarkerLabelFormatter { markedEntries, chartValues ->
        if (markedEntries.isNotEmpty()) {
            val entryIndex = if (markedEntries.size == 1) PercentageDecimals else indexEntryModelToShowIndicator
            val xEntry = markedEntries[entryIndex].entry.x.toInt()
            val attended = markedEntries.last().entry.y.toInt()
            val requested = markedEntries.first().entry.y.toInt()

I hope you can help us, we're kinda struggling šŸ™‡ Keep it up, guys!

Also, I'd appreciate it if you could help us with this as well @Gowsky. Thanks! Forget about it. I was able to migrate from markedEntries to `targets. However, I think it should be valuable to add some migration documentation since I had to do it digging into the code. Thanks anyway for the help šŸ™‡

Gowsky commented 1 month ago

@diego-francisco-jt thanks for the updated example. It will work, but please ensure it is minimal next time.

Also, I'd appreciate it if you could help us with this as well @Gowsky. Thanks!

As I initially said, we will help you with the migration as soon as we can.

@qq2540040142 @matejbart thank you for your tips!

Gowsky commented 1 month ago

We've reproduced the bug. Here's a temporary workaround that is almost invisible.

horizontalLayout =
  with(LocalDensity.current) {
    HorizontalLayout.fullWidth(
      unscalableStartPadding = 1.toDp(),
      unscalableEndPadding = 1.toDp(),
    )
  }
hadys7s commented 1 month ago

We've reproduced the bug. Here's a temporary workaround that is almost invisible.

horizontalLayout =
  with(LocalDensity.current) {
    HorizontalLayout.fullWidth(
      unscalableStartPadding = 1.toDp(),
      unscalableEndPadding = 1.toDp(),
    )
  }

i tried to put this solution but it still crashes i have more than 300 points

FATAL EXCEPTION: main Process: com.areeb.invest_01.dev, PID: 20003 java.lang.IllegalArgumentException: x must be >= 0 at android.graphics.Bitmap.checkXYSign(Bitmap.java:440) at android.graphics.Bitmap.checkPixelAccess(Bitmap.java:2148) at android.graphics.Bitmap.getPixel(Bitmap.java:2058) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.updateMarkerTargets(LineCartesianLayer.kt:392) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.drawInternal$lambda$3$lambda$2$lambda$1(LineCartesianLayer.kt:361) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.$r8$lambda$X5LX4R_c_OLNMkJTndrlghKMN_c(Unknown Source:0) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer$$ExternalSyntheticLambda2.invoke(Unknown Source:27) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.forEachPointInBounds(LineCartesianLayer.kt:548) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.drawInternal(LineCartesianLayer.kt:360) at com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer.drawInternal(LineCartesianLayer.kt:76) at com.patrykandpatrick.vico.core.cartesian.layer.BaseCartesianLayer.draw(BaseCartesianLayer.kt:59) at com.patrykandpatrick.vico.core.cartesian.CartesianChart$drawingModelAndLayerConsumer$1.invoke(CartesianChart.kt:96) at com.patrykandpatrick.vico.core.cartesian.CartesianChart.forEachWithLayer(CartesianChart.kt:420) at com.patrykandpatrick.vico.core.cartesian.CartesianChart.draw-43zNVmo(CartesianChart.kt:247) at com.patrykandpatrick.vico.compose.cartesian.CartesianChartHostKt.CartesianChartHostImpl$lambda$19(CartesianChartHost.kt:228) at com.patrykandpatrick.vico.compose.cartesian.CartesianChartHostKt.$r8$lambda$b9Nv60KzYR9TCaxUzosrVlUpJUU(Unknown Source:0) at com.patrykandpatrick.vico.compose.cartesian.CartesianChartHostKt$$ExternalSyntheticLambda4.invoke(Unknown Source:25) at androidx.compose.ui.draw.DrawBackgroundModifier.draw(DrawModifier.kt:116) at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:105) at androidx.compose.ui.node.LayoutNodeDrawScope.draw-x_KDEd0$ui_release(LayoutNodeDrawScope.kt:86) at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:364) at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176) at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361) at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176) at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361) at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353) at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:926) at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:174) at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361) at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176) at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361) at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:176) at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:361) at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:353) at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:926) at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:174)

Gowsky commented 1 month ago

Vico 2.0.0-alpha.28 fixes this bug.

@diego-francisco-jt

However, I think it should be valuable to add some migration documentation

We plan on adding more information about CartesianMarker.Target to the wiki.