Blog Infos
Author
Published
Topics
,
Published

Yeah! you just read that. Today I’m going to show you how to build your own chart (this time, a linear one). No extra libraries need it, only Jetpack Compose UI and a small amount of math.

⚠️ Before we start I want to tell you something. This blog is going to be pretty straightforward, therefore, I assume you already have experience working with Jetpack Compose, at least the basics like configuring your project, what are “composables”, how to they work, etc. because that is not described here️.

Alright, let’s start! 🚀

I’m almost 100% sure that you have experience working with Views like TextView, EditText, etc. or ViewGroups like ContraintLayout, LinearLayout, etc. and how to they operate together. Yes, those components are Views ViewGroups.

If you want to know the internals of them, I highly recommend this talk from one of the greatest, Huyen Tue Dao.

Yeah! you just read that. Today I’m going to show you how to build your own chart (this time, a linear one). No extra libraries need it, only Jetpack Compose UI and a small amount of math.

Droidcon NYC 2016 — Measure, Layout, Draw, Repeat: Custom Views and ViewGroups

 

Back to our main subject, how do we render something like a chart?

In the “old” view system we have to do it from scratch by extending the View class and overriding it’s onDraw(canvas: Canvas) method. With that done, we could play with the canvas object and literally draw anything you can imagine.

Custom drawing happens thanks to Canvas (official docs), no matter if it’s on the “old” View System or with Compose. In this Canvas we can draw lines, rects, circles, ovals, paths, arcs, etc. all of that just by connecting points in it’s “own” coordinate system.

Yes, I write it’s “own” coordinate system because the start point

(0;0)🠒(x;y)

Reference of the Cartesian Coordinate System as we may already know and in Android Development

is now located in the TOP LEFT CORNER, not the center. That’s the place where our X axis and Y axis now begin (look at the image! 👆). For now on this the TOP LEFT CORNER is going to be the start point of reference for our “Canvas Coordinate System”.

 wrote an amazing blog with more detail of it here, you should really read it!

Our linear chart is going to look like this:

Custom Line Chart with Jetpack Compose

Let’s analyze a little bit the image:

  1. We can see that the whole chart has a start and an ending point, so it’s finite, and it must fit in the Width (⟷ ) of the View.
  2. We can see that each point represent a “Transaction” per second, and according to that “transaction” the line can go up or down.
  3. We know that a line is composed by two points, start(a;b) and end(c;d) and that there is a distance from “start” point to “end” point.

I think we analyze enough of that image for now. We could say we need something like this:

Line Chart Decomposition Analysis (yes I know, I’m not Da Vinci).

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Jetpack Compose: Drawing without pain and recomposition

This is a talk on recomposition in Jetpack Compose and the myths of too many calls it is followed by. I’ll briefly explain the reasons behind recompositions and why they are not as problematic as…
Watch Video

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jobs

Now we can see a bigger picture, we need a function to that takes transactions as inputs and return points for our coordinate system. We also need to know the height of our View in order to establish some height limits.

First, let’s model our “Transaction” object. For this particular case, I’m consuming the API from Blockchain.

/**
 * Represents a group of Transactions
 * @param maxTransaction the max transaction value in the list of transactions previously calculated in some repository.
 * @param transactions list of transactions per second.
 */
data class TransactionsPerSecond(
    val maxTransaction: Double,
    val transactions: List<TransactionRate>
)

/**
 * Represents a transaction rate.
 * @param timeStamp the time stamp of the transaction.
 * @param transactionsPerSecondValue the quantity of transactions made per second.
 */
data class TransactionRate(
    val timeStamp: Long,
    val transactionsPerSecondValue: Double
)

Now that we have our model set up, let’s code our composable function:

@Composable
fun LinearTransactionsChart(
modifier: Modifier = Modifier,
transactionsPerSecond: TransactionsPerSecond
) {
if (transactionsPerSecond.transactions.isEmpty()) return
Canvas(modifier = modifier) {
// Total number of transactions.
val totalRecords = transactionsPerSecond.transactions.size
// Maximum distance between dots (transactions)
val lineDistance = size.width / (totalRecords + 1)
// Canvas height
val cHeight = size.height
// Add some kind of a "Padding" for the initial point where the line starts.
var currentLineDistance = 0F + lineDistance
transactionsPerSecond.transactions.forEachIndexed { index, transactionRate ->
if (totalRecords >= index + 2) {
drawLine(
start = Offset(
x = currentLineDistance,
y = calculateYCoordinate(
higherTransactionRateValue = transactionsPerSecond.maxTransaction,
currentTransactionRate = transactionRate.transactionsPerSecondValue,
canvasHeight = cHeight
)
),
end = Offset(
x = currentLineDistance + lineDistance,
y = calculateYCoordinate(
higherTransactionRateValue = transactionsPerSecond.maxTransaction,
currentTransactionRate = transactionsPerSecond.transactions[index + 1].transactionsPerSecondValue,
canvasHeight = cHeight
)
),
color = Color(40, 193, 218),
strokeWidth = Stroke.DefaultMiter
)
}
currentLineDistance += lineDistance
}
}
}

LinearTransactionChart Composable

As I already told you, Canvas is all you need for custom drawing on Android. Let’s review the gist step by step (the most important ones).

  1. First it uses the Canvas composable, which receive a Modifier as parameter and provide us with a DrawScope ~ Line 8.
  2. Because we want to “expand” our chart to fit the whole View width, let’s figure it out the “max” distance our “points” can have ~ Line 13.
  3. We get the Height of our canvas, because we want to limit how high or low each point can be placed in our chart. ~ Line 16.
  4. Inside the DrawScope mentioned in point #1 we are going to perform our operations, such as find the total number of transactions per second, transform each transaction into a valid point, draw the lines with the given points and finally render a chart ~ Line 22 and below.
  5. Finally we are going to iterate over all the “Transactions” and draw a line that have as start point the current transaction looping, and as end point the next transaction in the array/list.

Oh yeah, I made a small helper function for calculate the Y coordinate

/**
* Calculates the Y pixel coordinate for a given transaction rate.
*
* @param higherTransactionRateValue the highest rate value in the whole list of transactions.
* @param currentTransactionRate the current transaction RATE while iterating the list of transactions.
* @param canvasHeight the canvas HEIGHT for draw the linear chart.
*
* @return [Float] Y coordinate for a transaction rate.
*/
private fun calculateYCoordinate(
higherTransactionRateValue: Double,
currentTransactionRate: Double,
canvasHeight: Float
): Float {
val maxAndCurrentValueDifference = (higherTransactionRateValue - currentTransactionRate)
.toFloat()
val relativePercentageOfScreen = (canvasHeight / higherTransactionRateValue)
.toFloat()
return maxAndCurrentValueDifference * relativePercentageOfScreen
}

And just like that, you can implement your own LinearChart in no more than ~ 100 lines of code approximately, pretty impressing isn’t it? If you want to see the full app running check out the repo itself and don’t forget to smash the ⭐ button on it!

This small app, has a lot of improvements to be done, but I think it will help you to get started with custom drawings with out making your head explode.

So, stop procrastinating, stop avoiding these topics and start from something! Everything is practice 🙂.

See you soon in the next blog 👋! HUGE kudos to the Android Developers Team for Jetpack Compose, they are doing a great job with the library, codelabs, samples and documentation.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu