androidandroid-studiokotlinmpandroidchart

MPAndroidChart visual rendering problem with a specific data


I am trying to draw a polygon using MPAndroidChart in AndroidStudio in Kotlin for an Android developpment. I am facing a visual rendering problem.

I am drawing 2 different polygons on 2 different charts. The first one is working perfectly but the second one is creating a visual rendering problem and making my application to crash when I try to zoom or do anything in my chart.

The coordinate for the second polygon should be the following:

polygonTopView = Polygon(
   Pair(92.0, 3.0),
   Pair(100.0, 3.0),
   Pair(102.5, 1.5),
   Pair(102.5, -1.5),
   Pair(100.0, -3.0),
   Pair(92.0, -3.0),
   Pair(92.0, 3.0)
)

The result is the following picture below with the second polygon, for which I have only one line and it crashes whenever I touch it

enter image description here

If I just comment the coordinate Pair(100.0, -3.0), I don't have this problem but of course I want to draw my full polygon. Below is the polygon without the coordinate that generates the error:

enter image description here

Finally if the coordinate Pair(100.0, -3.0) is somewhere above or equal to the last one on the X-axis it works. For example if I put Pair(103.0, -3.0), it will works.

Does anyone can help me with this problem ?

I tried many things but I really don't get the problem

There is not much to say about the code because the problem is relative to the coordinates, but here is my function to draw my polygon:

    private fun drawPolygon(view: View, polygonSideView: Polygon, polygonTopView: Polygon){
    
    chartSideView = view.findViewById(R.id.chartSideView)
    chartSideView.setTouchEnabled(true)
    chartSideView.setPinchZoom(true)

    val entriesSideView = mutableListOf<Entry>()
    for (point in polygonSideView.vertices) {
        entriesSideView.add(Entry(point.first.toFloat(), point.second.toFloat()))
    }

    val dataSetSideView = LineDataSet(entriesSideView, "CG limit Side view")
    dataSetSideView.color = ColorTemplate.COLORFUL_COLORS[0]
    dataSetSideView.lineWidth = 1f // set the line width to 2 pixels
    dataSetSideView.fillColor = ColorTemplate.COLORFUL_COLORS[0]
    dataSetSideView.setDrawFilled(true)
    
    val lineDataSetsSideView = mutableListOf<ILineDataSet>()
    lineDataSetsSideView.add(dataSetSideView)

    val lineDataSideView = LineData(lineDataSetsSideView)
    chartSideView.data = lineDataSideView
    chartSideView.description.isEnabled = false
    chartSideView.legend.isEnabled = true
    chartSideView.axisRight.isEnabled = false
    chartSideView.axisLeft.axisMinimum = 1500f
    chartSideView.axisLeft.axisMaximum = 2600f
    //chartSideView.xAxis.labelRotationAngle = -45f
    chartSideView.xAxis.granularity = 1f
    chartSideView.xAxis.isGranularityEnabled = true
    chartSideView.xAxis.axisMinimum = 91f
    chartSideView.xAxis.axisMaximum = 103f


    chartSideView.invalidate()
    // end of sideview



    // chartview 2
    //chartTopView = view?.findViewById(R.id.chartTopView) ?: throw IllegalStateException("View is null")
    chartTopView = view.findViewById(R.id.chartTopView)
    chartTopView.setTouchEnabled(true)
    chartTopView.setPinchZoom(true)

    val entriesTopView = mutableListOf<Entry>()
    for (point in polygonTopView.vertices) {
        entriesTopView.add(Entry(point.first.toFloat(), point.second.toFloat()))
    }
    //entriesTopView.add(Entry(polygonTopView.vertices[0].first.toFloat(), polygonTopView.vertices[0].second.toFloat()))


    val dataSetTopView = LineDataSet(entriesTopView, "CG limit Top view")
    dataSetTopView.color = ColorTemplate.COLORFUL_COLORS[0]
    dataSetTopView.lineWidth = 2f // set the line width to 2 pixels
    dataSetTopView.fillColor = ColorTemplate.COLORFUL_COLORS[0]
    dataSetTopView.setDrawFilled(true)

    val lineDataSetsTopView = mutableListOf<ILineDataSet>()
    lineDataSetsTopView.add(dataSetTopView)

    val lineDataTopView = LineData(lineDataSetsTopView)

    chartTopView.data = lineDataTopView
    chartTopView.description.isEnabled = false
    chartTopView.legend.isEnabled = true
    chartTopView.axisRight.isEnabled = false
    chartTopView.axisLeft.axisMinimum = -4f
    chartTopView.axisLeft.axisMaximum = 4f
    //chartTopView.xAxis.labelRotationAngle = -45f
    chartTopView.xAxis.granularity = 1f
    chartTopView.xAxis.isGranularityEnabled = true
    chartTopView.xAxis.axisMinimum = 91f
    chartTopView.xAxis.axisMaximum = 103f

    chartTopView.invalidate()
}

Solution

  • The issue is that the line chart expects the provided points to be sorted by x-coordinate (increasing). When they are not, there are a number of errors that can occur. There is some background about this requirement in this Github issue and in the docs.

    Please be aware that this library does not officially support drawing LineChart data from an Entry list not sorted by the x-position of the entries in ascending manner. Adding entries in an unsorted way may result in correct drawing, but may also lead to unexpected behaviour.

    Even in the case that was "working" where you dropped the (100,-3) point, if you zoomed in on the chart and panned around the fill would show up wrong. In this case, when you try to pan or zoom including the (100,-3) point it throws the error below:

    Process: com.example.chartdemo, PID: 31183
    java.lang.NegativeArraySizeException: -6
        at com.github.mikephil.charting.utils.Transformer.generateTransformedValuesLine(Transformer.java:178)
        at com.github.mikephil.charting.renderer.LineChartRenderer.drawValues(LineChartRenderer.java:549)
        at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(BarLineChartBase.java:278)
        at android.view.View.draw(View.java:22644)
    

    One solution is to split the polygon edges into multiple separate lines, one which goes "over the top" of the polygon and one which forms the base. Both of these should be non-decreasing in X.

    val polyUpper = listOf(
        Pair(92.0, -3.0),
        Pair(92.0, 3.0),
        Pair(100.0, 3.0),
        Pair(102.5, 1.5),
        Pair(102.5, -1.5)
    )
    
    val polyLower = listOf(
        Pair(92.0, -3.0),
        Pair(100.0, -3.0),
        Pair(102.5, -1.5)
    )
    
    val entriesUpper = polyUpper.map { Entry(it.first.toFloat(), it.second.toFloat()) }
    val entriesLower = polyLower.map { Entry(it.first.toFloat(), it.second.toFloat()) }
    
    val dataSetUpper = LineDataSet(entriesUpper, "CG limit Side view")
    dataSetUpper.color = ColorTemplate.COLORFUL_COLORS[0]
    dataSetUpper.lineWidth = 1f // set the line width to 2 pixels
    dataSetUpper.fillColor = ColorTemplate.COLORFUL_COLORS[0]
    dataSetUpper.setDrawFilled(true)
    
    val dataSetLower = LineDataSet(entriesLower, "")
    dataSetLower.color = ColorTemplate.COLORFUL_COLORS[0]
    dataSetLower.lineWidth = 1f // set the line width to 2 pixels
    dataSetLower.fillColor = ColorTemplate.COLORFUL_COLORS[0]
    dataSetLower.setDrawFilled(true)
    
    val lineDataSideView = LineData(listOf(dataSetUpper, dataSetLower))
    

    The result: polygon demo

    Notes