Draw sleep timeline graph in Compose
Viktor Mykhailiv on 2025-01-06
Draw sleep timeline graph in Compose

Custom drawing is useful for when the built-in components just don’t cover exactly what our app needs. This article provides a guide to create a custom sleep timeline graph, similar to those you can find in the Fitbit app.

How do we draw in Compose?
To get started with drawing in Compose we can use drawing modifiers or Canvas
composable which gives us DrawScope
— a declarative, stateless API to draw shapes and paths without requiring consumers to maintain underlying state. DrawScope
implementations are also provided sizing information and transformations are done relative to the local translation.
Note: Jetpack Compose (Android only) and Compose Multiplatform (Desktop, Android, iOS, web) have similar drawing API. Screenshots below are made on Desktop (macOS), but the result is the same on all platforms (check the last screenshot).

What is the sleep timeline?
We can read or write sleep data in Health Connect. Sleep data is displayed as a session, and can be divided into sleep stages:
- Awake: the user is awake within a sleep cycle.
- Light sleep: the user is in a light sleep cycle.
- Deep sleep: the user is in a deep sleep cycle.
- REM: the user is in a REM sleep cycle.
The SleepSessionRecord
data type has two parts:
- The overall session, spanning the entire duration of sleep.
- Individual stages during the sleep session such as light sleep or deep sleep.
Math
During the sleep session we can be in the same stage many times at different moments in time. We need to calculate the start and end points relative to the sleep session.
To draw a rect in Compose we need topOffset
and size
.


Drawing
Let’s build our custom Canvas to draw one stage of the sleep session, e.g. deep.
If we run the project with the previously defined sleep session, we will see 3 rects: 1 grey rect for background and 2 purple rects for deep sleep stage.

To draw all stages of the sleep session (awake, REM, light, and deep) we need to make a few adjustments to draw each stage type as Column component, vertically, by drawing line by line and applying some offset for the next line.

Draw text
To draw a text in Compose, we can typically use the Text
composable. However, in our example we are in a DrawScope
and we can use the DrawScope.drawText()
method.
Drawing text works a bit differently from other drawing commands. Normally, we give the drawing command the size (width and height) to draw the shape/image as. With text, there are a few parameters that control the size of the rendered text, such as font size, font, ligatures, and letter spacing. We need to use a TextMeasurer
to get access to the measured size of text, depending on the above factors.


Please find the full example in my repository: https://github.com/vitoksmile/Sleep-timeline-graph