Adding features to your chart on Android
In the previous post I did show you how to make a basic setup of Android linear chart using MPAndroidChart library. Now I'll show you some features I found useful.
Custom handling of chart tap event
The library provides default highlight values behavior on tap or drag. You may want to handle it by yourself, so get familiar with OnChartValueSelectedListener
. It contains 2 methods:
onNothingSelected
onValueSelected
onValueSelected
provides Entry
object which gives you an access to the data, that is FoodSearch
in our case. Let's use it to something simple, like displaying some info in the toast, to see how it works:
lineChart.isHighlightPerTapEnabled = true
lineChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
override fun onNothingSelected() {}
override fun onValueSelected(e: Entry, h: Highlight) {
val yearWeek = (e.data as FoodSearch).week_id
Toast.makeText(baseContext, "date: $yearWeek", Toast.LENGTH_SHORT).show()
}
})
If you want to display some view over selected Entry you may take advantage of the Highlight
object in the onValueSelected
method and get Entry's coordinates on the screen by accsessing xPx
and yPx
properties and set them to the view.
Live Data
MPAndroidChart does not officially support realtime data, however (...)
As long as we keep reference to the specific DataSet
object, we are able to add and remove entries dynamically. It might be a result of syncing data from the internet or partial progress some asynchronous task - doesn't matter. Let's add a random entry on the button click.
val rand = Random()
bananaDataSet.addEntry(Entry(bananaDataSet.entryCount.toFloat(), rand.nextFloat() * 100))
yogurtDataSet.addEntry(Entry(yogurtDataSet.entryCount.toFloat(), rand.nextFloat() * 100))
When modifying data set, BOTH LineData
object and the chart need to be notified about it. To do so, invoke proper method on them, then simply invalidate the chart like you would with any other view element.
lineChart.data.notifyDataChanged()
lineChart.notifyDataSetChanged()
lineChart.invalidate()
I recommend you to check out other ways of updating chart data described in the docs.
Viewport
Let's say you want to put readability aside, remove labels and axes with all that white spaces and make a pure chart view fill the screen. Labels/ axis configuration is obvious, just disable them:
lineChart.axisLeft.isEnabled = false
lineChart.axisRight.isEnabled = false
lineChart.xAxis.isEnabled = false
lineChart.description.isEnabled = false
lineChart.legend.isEnabled = false
Unfortunately, it does not make the job done. As you can see below there are still space between the chart and the parent view left.
How to disable the chart padding? The library provides us a simple way to modify chart's Viewport
. In this case call method setViewPortOffsets(0f,0f,0f,0f)
on the chart object. It sets chart's offset (padding) to 0. Just remember all viewport modifications have to be called after data is set up.
BUT...!
It's a solution you need to be cautious about. What does the documentation say about using this approach?
USE THIS ONLY WHEN YOU KNOW WHAT YOU ARE DOING.
Why? During a short adventure with chart's viewport I came across one problematic issue. Offset seemed to be recalculated independently and every other view besides the chart itself didn't know about the change. This may cause some render problems, for example like this:
Displayed value is the mentioned "other view" that does not know about the offset change, so edge values are cut. Keep this in mind modifying viewport! For more viewport modifications check out the project's wiki page.
Drawable Values
If you need to mark any value in some special way you may define Entry
's icon
property. It's an instance of a Drawable
class, so it's very easy to layout the icon in a resource file and then apply it to the Entry. In our example let's mark with a red dot each value that differs from the previous one more than 15 to see every bigger steeper slope on the chart.
Define any drawable resource, for example 20x20 red oval:
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="20dp" android:height="20dp" />
<solid android:color="#ff0000" />
</shape>
Now add some code to getEntriesFromCSV
method. Before adding a new Entry
to an array, add the Drawable as the icon's property if the point fulfills the condition:
data?.mapIndexed { index, foodSearch ->
val entry = Entry(index.toFloat(), foodSearch.value.toFloat(), foodSearch)
val steeperSlope = 15
if (index != 0 && (foodSearch.value > data?.get(index - 1)!!.value + steeperSlope ||
foodSearch.value < data?.get(index - 1)!!.value - steeperSlope)) {
val icon = resources.getDrawable(R.drawable.ic_balloon, null)
entry.icon = icon
}
entries.add(entry)
}
In order to see the icons, set the drawIcons flag to true
on each DataSet
object:
bananaDataSet.setDrawIcons(true)
yogurtDataSet.setDrawIcons(true)
Thank's to that icons are displayed over specified entries: