Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Enhancing a Kotlin Chart With Advanced Charting Kit (Part 1)

DZone's Guide to

Enhancing a Kotlin Chart With Advanced Charting Kit (Part 1)

Learn how to better enhance your Kotlin chart.

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

Ever since Google announced support for Kotlin on Android back in 2017, its popularity amongst developers has continued. Having initially experimented with Kotlin, I was keen to see how it would get along with the shinobicharts and Advanced Charting Kit libraries. Being the owner of a wearable fitness tracking device, I decided to write a simple app that would display a chart showing my heart rate over a typical working day.

I began by taking a day’s worth of data and placing into a simple file. The data is presented as a timestamp value and an integer showing the beats per minute at that time. A typical data row looks like this:

1536040890113,56


In the production code, your data may be real-time but a simple file is adequate for this demo. The device that I have captures a data point approximately every 6 seconds, so for a typical working day, there was approximately 9,000 data points. What makes shinobicharts a favorite with our customers is its ability to handle lots of data, so this number was no cause for concern. Getting a shinobichart up and running in Kotlin really is simple. A Kotlin quick-start guide and sample app can be found in the docs section of the download bundle or here. My first attempt gave a chart that looked like this:

If you’ve seen the Kotlin quick start sample app, you’ll be familiar with the basics of how to set up a chart in Kotlin. The main difference here is that I use code similar to the following to read from a text file and populate a DataAdapter:

val reader = BufferedReader(InputStreamReader(context.assets.open(filename)))
do {
    val dataRow = reader.readLine()
    if (dataRow != null) {
        val lineItem = dataRow.split(',')
        dataAdapter.add(DataPoint(Date(lineItem[0].toLong()),
                lineItem[1].toDouble()))
    }
} while (dataRow != null)
reader.close()


Its a good start, but it needs a little color. Being a heart rate chart, it would make sense to show the low, medium, and high zones, using suitable colors within the series’ fill. The Advanced Charting Kit (from herein I’ll refer to this as ACK) library offers a nice AdvancedLineSeriesStyleclass that allows me to quickly and easily add desired colors using gradient stops. Now, the chart looks a little better:

While quite a lot has changed visually, the code to achieve this really is rather simple:

bpmSeries.style = AdvancedLineSeriesStyle().apply {
    areaLineColor = ContextCompat.getColor(context, R.color.colorBpmLine)
    areaColor = ContextCompat.getColor(context, R.color.colorBpmLine)
    fillStyle = SeriesStyle.FillStyle.GRADIENT
    addGradientStop(GradientStop.create(ContextCompat.getColor(context, R.color
            .colorBpmLow), 0.3f))
    addGradientStop(GradientStop.create(ContextCompat.getColor(context, R.color
            .colorBpmMedium), 0.6f))
    addGradientStop(GradientStop.create(ContextCompat.getColor(context, R.color
            .colorBpmHigh), 0.9f))
}


This code demonstrates one of the benefits of Kotlin over Java — its lack of verbosity. Using Kotlin’s applyfunction reduces the number of lines needed to perform the work and, in my opinion, makes for a much more readable function.

As you can see, the chart shows data from around 7 am through until 10 pm with some noticeable spikes in the morning, middle of the day, and evening. These spikes represent my walk to work, a lunchtime run, and my walk home from work. What is also apparent is the frequency of the data results in a lot of noise, which tends to blur the message that the data seeks to present. What is needed is some way of reducing the amount of data displayed but maintaining the shape of the data. ACK addresses this requirement with its sampling functionality:

As the above screenshot shows, the sampling feature of ACK makes for much clearer data; a lot of the noise has been filtered, or sampled out. In particular, the sampler that wraps the chart’s DataAdapterreturns every 30th data point to the chart for display. Changing 1 line of code is all that is needed to achieve this:

bpmSeries.dataAdapter = NthPointSampler<Date, Double>(bpmDataAdapter, 30)


While the shape of the data is more transparent, noise is still present in the form of sharp edges. To really make the data’s shape stand out, I can smooth the shape of the line using ACK, as you can see below. ACK’s data smoothers achieve this by wrapping the DataAdapterthen adding additional synthetic data points to the series for display. The original data remains unchanged.

To add smoothing to the heart rate series, only 1 line of code is needed:

bpmSeries.linePathInterpolator = CatmullRomSplineSmoother<Date, Double>(6)


The sampled and smoothed heart rate data is much clearer as the trend can be seen at a glance; I feel, however, that the chart can be made even more useful. A typical metric associated with some activities is pace; it would be nice to show this on the chart. My wearable device captures this data as meters per second so this should be a good fit for the chart. On my device pace data is only recorded during the activity itself so unlike the heart rate data I cannot simply add a LineSeriesto cover the whole day.

The solution is to have a LineSeriesto represent each activity. Like before, I’ve extracted the data to flat files for simplicity as real-time data streaming is beyond the scope of this blog. The data is presented in a similar fashion to that previous; this time we have timestamp and meters per second data, such as:

1536060702113,2.743000030517578


This data point shows that at this point in time I had a pace of approximately 6.08 minutes per KM. To aid clarity, I’ve also enabled theLegendto show what each series represents.

After adding the 3 pace series we see a chart like that above. You’ll notice I’ve added an additional y-axis on the reverse, or right-hand, side of the chart. I’ve also added code that converts the m/s data into the more common pace reading. A noteworthy point is that I’ve negated each y value before it’s given to the DataAdapters and then used an OnTickMarkDrawListenerto convert the negative tick mark labels back to positive values. My helper function convertLabelSign assists with this work. This is needed to ensure the pace data is displayed with tick marks that decrease rather than an increase in value. You could, of course, omit this step but I feel the shape of the data would be incorrect; heart rate and pace typically have a similar rather than inverse relationship. Implementing the OnTickMarkDrawListener is straightforward. I first declare that my MainActivity class implements it using code such as:

class MainActivity: ShinobiChart.OnTickMarkDrawListener


I then implement theonDrawTickMarkfunction, using the ChartUtilsclass to draw the custom tick marks:

override fun onDrawTickMark(canvas: Canvas?, tickMark: TickMark?, labelBackgroundRect: Rect?,
                            tickMarkRect: Rect?, axis: Axis<*, *>?) {
    ChartUtils.drawTickMarkLine(canvas, tickMark)
    val x = labelBackgroundRect!!.centerX()
    val y = labelBackgroundRect.centerY()
    if (axis!! == shinobiChart.allYAxes[1]) {
        tickMark!!.labelText = convertLabelSign(tickMark.value as Double)
    }
    ChartUtils.drawText(canvas, tickMark!!.labelText, x, y, paceMajorTickLabelPaint)
}


By implementing this listener, I effectively disable the chart’s built-in tick mark drawing function. This listener function will be called for all axes, not just the pace axis. What I need then is to firstly reinstate the standard drawing function for the time (x) and heart rate (y) axes then bring in the custom behavior for the pace (reverse y) axis. In the code above, you can see that I use the ChartUtilsconvenience functions to draw the tick mark lines and labels. I check to see if the axis associated with the tick mark I am trying to draw is the reverse y. If it is, I use the convertLabelSignhelper function to covert the sign back to a positive value. Enabling this listener on the chart is very simple:

shinobiChart.setOnTickMarkDrawListener(this)


You can take a look at the code yourself here. ACK comes with comprehensive how-to guides that demonstrate in detail how to achieve the concepts discussed. You can find these in the download bundle or here.

In this blog, I’ve shown how you can use ACK to enhance data visualized by shinobicharts using Kotlin. I will take things a little further in part 2. We hope you will experiment by using shinobicharts and ACK within your own apps. If you have not already done so, why not download a free 30-day trial? If you have any questions, feel free to get in touch.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
kotlin ,java ,charting ,kit ,how-to ,code ,tutorial ,kotlin tutorial ,java tutorial ,charts

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}