pradeepkumarreddy
Posts: 16
Joined: Mon Jun 20, 2022 1:58 pm

HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

This question is related to HighCharts usage in Android
When i place HIChartView inside NestedScrollView or RecyclerView and try to scroll up the page by touching on the ChartView, the recyclerview scroll movement is very laggy.
This is happening only when i touch the chartview and try to scroll the page up. But the recyclerview scroll is smooth when i touch outside of the chartview and move the page up.

Any ideas on how to solve this issue ?
hubert.k
Posts: 1164
Joined: Mon Apr 11, 2022 11:48 am

Re: HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

Hello pradeepkumarreddy!
After a discussion with our Android developer, I have an answer for you. Highcharts on mobile is rendering in WebView, which is quite slow and if we have a lot of views in the list it will cause a laggy scroll. One of the solution is to introduce the pagination mechanism, but eventually, when enough charts are loaded, the whole UI will slow down (depending on the device, on emulators something jams pretty quickly because a little memory of RAM is allocated but on current phones it will not be such a problem). Everyone does pagination according to their requirements and needs. Our Android developer usually tries to use the Paging library from the Jetpack Compose (but depending on the project, it may not always be applicable).

Feel free to ask any further questions.
Kind regards!
Hubert Kozik
Highcharts Developer
pradeepkumarreddy
Posts: 16
Joined: Mon Jun 20, 2022 1:58 pm

Re: HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

hubert.k wrote: Wed Jun 29, 2022 8:10 am Hello pradeepkumarreddy!
After a discussion with our Android developer, I have an answer for you. Highcharts on mobile is rendering in WebView, which is quite slow and if we have a lot of views in the list it will cause a laggy scroll. One of the solution is to introduce the pagination mechanism, but eventually, when enough charts are loaded, the whole UI will slow down (depending on the device, on emulators something jams pretty quickly because a little memory of RAM is allocated but on current phones it will not be such a problem). Everyone does pagination according to their requirements and needs. Our Android developer usually tries to use the Paging library from the Jetpack Compose (but depending on the project, it may not always be applicable).

Feel free to ask any further questions.
Kind regards!
I don't have lot of views in the list to use pagination. In the list i have only one ChartView and 4 other simple textviews.
hubert.k
Posts: 1164
Joined: Mon Apr 11, 2022 11:48 am

Re: HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

pradeepkumarreddy,
Could you send me the chart configuration, which you are using? We will try to find some solution for you.
Regards!
Hubert Kozik
Highcharts Developer
pradeepkumarreddy
Posts: 16
Joined: Mon Jun 20, 2022 1:58 pm

Re: HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

hubert.k wrote: Thu Jun 30, 2022 7:01 am pradeepkumarreddy,
Could you send me the chart configuration, which you are using? We will try to find some solution for you.
Regards!
This is the code
[private fun drawGraph(graphData: GraphData?, yAxisUnit: String?, filter: String?) {
binding.trendGraph.invalidate()

val chartOptions = HIOptions()
val title = HITitle()
title.text = ""
chartOptions.title = title

val credits = HICredits()
credits.enabled = false
chartOptions.credits = credits

val exporting = HIExporting()
exporting.enabled = false
chartOptions.exporting = exporting

val legend = HILegend()
legend.layout = "horizontal"
legend.alignColumns = false
legend.itemMarginBottom = 5
chartOptions.legend = legend

val chart = HIChart()
chart.type = graphData.plotType
chart.zoomType = "xy"
chart.animation = HIAnimationOptionsObject()
chart.animation.duration = 3000
chartOptions.chart = chart

val xAxis = HIXAxis()
xAxis.zoomEnabled = true
xAxis.offset = 24.0
xAxis.labels = HILabels()
xAxis.labels.allowOverlap = false

val dataLabels = HIDataLabels()
dataLabels.enabled = false
val dataLabelsList: ArrayList<HIDataLabels> = ArrayList()
dataLabelsList.add(dataLabels)

val events = HIEvents()
events.legendItemClick = HIFunction("function(e){e.preventDefault();}")

val tooltip = HITooltip()
chartOptions.tooltip = tooltip
chartOptions.tooltip.animation = true

if (graphData.categories == null) {
tooltip.formatter =
HIFunction(
"function() { var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', " +
"'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var dateTime = new Date(this.x * 1000);" +
"return 'X: '+ dateTime.getDate() + ' ' + month[dateTime.getMonth()] +', '+ " +
"dateTime.getHours() +':' + dateTime.getMinutes()+'<br>'+'Y: '+this.y; }"
)
} else {
if (graphData.plotType == PlotType.scatter.name) {
tooltip.formatter =
HIFunction(
"function() { return this.series.name + '<br>X: <b>' + this.x + '</b><br>Y: <b>' + this.y; }"
)
} else {
tooltip.formatter =
HIFunction(
"function() { " +
"if(this.point.low === this.point.high){ return 'X: <b>' + this.x + '</b><br>Y: <b>' + this.y; }" +
"else{ return 'X: <b>' + this.x + '</b><br>Y: <b>' + this.point.low + '</b> - <b>' + this.point.high;}}"
)
}
}

when (graphData.plotType) {
PlotType.scatter.name -> {
if (graphData.categories != null) {
xAxis.type = "category"
xAxis.categories = graphData.categories
xAxis.min = 0
xAxis.max = graphData.categories.size - 1
val greenReadings = ArrayList<List<Float>>()
val redReadings = ArrayList<List<Float>>()
val grayReadings = ArrayList<List<Float>>()
val plotDataByFilter = graphData.plotData?.get(filter)
if (greenReadings.isEmpty() && redReadings.isEmpty() && grayReadings.isEmpty()) {
legend.enabled = false
isChartEmpty = true
}
val seriesList = ArrayList<HISeries>()
seriesList.add(getGreenScatter(dataLabelsList, events, greenReadings))
seriesList.add(getRedScatter(dataLabelsList, events, redReadings))
seriesList.add(getGrayScatter(dataLabelsList, events, grayReadings))
chartOptions.series = seriesList
} else {
xAxis.type = "datetime"
xAxis.min = graphData.startDate
xAxis.max = graphData.endDate
xAxis.labels.formatter =
HIFunction(
"function() { return Highcharts.dateFormat('%d %b<br>%H:%M',this.value*1000+(5*60+31)*60000); }"
)

val greenReadings = ArrayList<List<Float>>()
val redReadings = ArrayList<List<Float>>()
val grayReadings = ArrayList<List<Float>>()
val readings = graphData.plotData?.get(filter)?.readings
if (readings != null) {

} else {
legend.enabled = false
isChartEmpty = true
}
val seriesList = ArrayList<HISeries>()
seriesList.add(getGreenScatter(dataLabelsList, events, greenReadings))
seriesList.add(getRedScatter(dataLabelsList, events, redReadings))
seriesList.add(getGrayScatter(dataLabelsList, events, grayReadings))
chartOptions.series = seriesList
}
}
PlotType.columnrange.name -> {
val highColumnRange = HIColumnrange()
legend.enabled = false

if (graphData.categories != null) {
xAxis.type = "category"
xAxis.categories = graphData.categories
xAxis.min = 0
xAxis.max = graphData.categories.size - 1

val readings = ArrayList<List<Float>>(graphData.categories.size)
val greenReadings = ArrayList<List<Float>>(graphData.categories.size)
val redReadings = ArrayList<List<Float>>(graphData.categories.size)
val grayReadings = ArrayList<List<Float>>(graphData.categories.size)

graphData.categories.forEach { _ ->
readings.add(emptyList())
greenReadings.add(emptyList())
redReadings.add(emptyList())
grayReadings.add(emptyList())
}

var isGreenReadingsEmpty = true
var isRedReadingsEmpty = true
var isGrayReadingsEmpty = true
var isRangeReadingsEmpty = true

if (isRangeReadingsEmpty && isGreenReadingsEmpty && isRedReadingsEmpty && isGrayReadingsEmpty) {
isChartEmpty = true
}
highColumnRange.data = readings

val seriesList = java.util.ArrayList<HISeries>()
seriesList.add(highColumnRange)
if (!isGreenReadingsEmpty) {
val greenScatter = getGreenScatter(dataLabelsList, events, greenReadings)
seriesList.add(greenScatter)
}
if (!isRedReadingsEmpty) {
val redScatter = getRedScatter(dataLabelsList, events, redReadings)
seriesList.add(redScatter)
}
if (!isGrayReadingsEmpty) {
val grayScatter = getGrayScatter(dataLabelsList, events, grayReadings)
seriesList.add(grayScatter)
}
chartOptions.series = seriesList
} else {
xAxis.type = "datetime"
xAxis.min = graphData.startDate
xAxis.max = graphData.endDate
xAxis.labels.formatter =
HIFunction(
"function() { return Highcharts.dateFormat('%d %b<br>%H:%M',this.value*1000+(5*60+31)*60000); }"
)

val columnRangeReadings = ArrayList<List<Float>>()
val readings = graphData.plotData?.get(filter)?.readings
if (readings != null) {
for (i in readings.size - 1 downTo 0 step 1) {
val dataPoint =
listOf(
readings.instant?.toFloat() ?: 0f,
readings.value ?: 0f
)
columnRangeReadings.add(dataPoint)
}
} else {
legend.enabled = false
isChartEmpty = true
}
highColumnRange.data = columnRangeReadings
val seriesList = java.util.ArrayList<HISeries>()
seriesList.add(highColumnRange)
chartOptions.series = seriesList
}
}
}
chartOptions.xAxis = arrayListOf(xAxis)

val yAxis = HIYAxis()
yAxis.startOnTick = false
yAxis.endOnTick = false

val yTitle = HITitle()
yTitle.align = "high"
yTitle.rotation = 0

yAxis.title = yTitle
yAxis.title.text = if (yAxisUnit.isNullOrEmpty()) {
"Value"
} else {
"Value ($yAxisUnit)"
}
yTitle.x = 60 + yAxis.title.text.length
yTitle.y = -15

val yLabel = HILabels()
yLabel.reserveSpace = false
yAxis.labels = yLabel

val plotBands = HIPlotBands()
plotBands.color = HIColor.initWithHexValue("f0f2ff")
plotBands.from = graphData.plotData?.get(filter)?.plotRange?.low
plotBands.to = graphData.plotData?.get(filter)?.plotRange?.high
yAxis.plotBands = arrayListOf(plotBands)

chartOptions.yAxis = arrayListOf(yAxis)
chartOptions.chart.marginLeft = 40
chartOptions.chart.marginTop = 25

binding.trendGraph.options = chartOptions

if (isChartEmpty) {
binding.tvLoading.visibility = GONE
binding.trendGraph.visibility = VISIBLE
binding.trendGraph.reload()
return
}
binding.trendGraph.reload()
handler.postDelayed({
binding.tvLoading.visibility = GONE
binding.trendGraph.visibility = VISIBLE
}, 500)
}
}
private fun getGrayScatter(
dataLabelsList: java.util.ArrayList<HIDataLabels>,
events: HIEvents,
grayReadings: ArrayList<List<Float>>
): HISeries {
// Adding gray scatter points
val grayScatter = HIScatter()
grayScatter.name = resources.getString(R.string.range_not_calculated)
grayScatter.color = HIColor.initWithHexValue("9c9eb9")
val grayMarker = HIMarker()
grayMarker.symbol = "circle"
grayScatter.marker = grayMarker
val grayLabels = HIDataLabels()
grayLabels.enabled = true
grayLabels.format = "{y}"
grayScatter.dataLabels = arrayListOf(grayLabels)
grayScatter.events = events
grayScatter.animate(true)
grayScatter.dataLabels = dataLabelsList
grayScatter.data = grayReadings
return grayScatter
}

private fun getRedScatter(
dataLabelsList: java.util.ArrayList<HIDataLabels>,
events: HIEvents,
redReadings: ArrayList<List<Float>>
): HISeries {
// Adding red scatter points
val redScatter = HIScatter()
redScatter.name = resources.getString(R.string.out_of_range)
redScatter.color = HIColor.initWithHexValue("ff9b90")
val redMarker = HIMarker()
redMarker.symbol = "circle"
redScatter.marker = redMarker
val redLabels = HIDataLabels()
redLabels.enabled = true
redLabels.format = "{y}"
redScatter.dataLabels = arrayListOf(redLabels)
redScatter.events = events
redScatter.animate(true)
redScatter.dataLabels = dataLabelsList
redScatter.data = redReadings
return redScatter
}

private fun getGreenScatter(
dataLabelsList: ArrayList<HIDataLabels>,
events: HIEvents,
greenReadings: ArrayList<List<Float>>
): HISeries {
// Adding green scatter points
val greenScatter = HIScatter()
greenScatter.name = resources.getString(R.string.in_range)
greenScatter.color = HIColor.initWithHexValue("7abd5e")
val greenMarker = HIMarker()
greenMarker.symbol = "circle"
greenScatter.marker = greenMarker
val greenLabels = HIDataLabels()
greenLabels.enabled = true
greenLabels.format = "{y}"
greenScatter.dataLabels = arrayListOf(greenLabels)
greenScatter.events = events
greenScatter.animate(true)
greenScatter.dataLabels = dataLabelsList
greenScatter.data = greenReadings
return greenScatter
}
][/code]
pradeepkumarreddy
Posts: 16
Joined: Mon Jun 20, 2022 1:58 pm

Re: HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

When the tooltip is visible on chart and user tries to scroll, then the lag is more evident and annoying.
hubert.k
Posts: 1164
Joined: Mon Apr 11, 2022 11:48 am

Re: HighchartView inside RecyclerView or NestedScrollView causes laggy scroll

pradeepkumarreddy,
I have found, that you asked the same question on Highcharts Android Wrapper GitHub here: https://github.com/highcharts/highchart ... issues/236
You will get the answer there, directly from our Android developers.
Kind regards!
Hubert Kozik
Highcharts Developer

Return to “Highcharts Usage”