How to create — Heart Rate Chart with CSS and Math

Marek Bombera
5 min readMar 28, 2023

--

Title stock image
Photo by Jeffrey Leung on Unsplash

Recently, I came across an interesting challenge: creating a heart rate chart without relying on data visualization libraries like D3.js, Fusion Charts, or Chart.js. These libraries can be quite heavy, and in this particular case, they seemed like overkill.

In this post, I’ll guide you through the simple math and CSS needed to create a heart rate chart like the one below.

Heart rate chart

You can also check out the example on GitHub or CodeSandbox.

Getting Started

First, let’s take a look at the heart rate data we’ll be working with. The data is stored in an array where each value represents a point on the chart.

While timestamps are optional in this example, they would most likely be present in a real data.

const heartRateData = [
{
timestamp: '2022-07-27T04:28:40.000Z',
heartRate: 67,
},
{
timestamp: '2022-07-27T04:28:45.000Z',
heartRate: 69,
},
...
]

Before we dive into the calculations, let me explain the logic.

Essential Chart Variables

We need 3 values that we will use in calculations and design: chart height, data point width, and data point height.

// Values represent PX
const CHART_HEIGHT = 150
const DATA_POINT_WIDTH = 7
const DATA_POINT_HEIGHT = 7

To position each data point on the vertical Y-axis, we will assume that the min heart rate is 0% and the max heart rate is 100%.

// In arrays we can easily find lowest and highest value
const minHeartRate = Math.min(...heartRateData.map(({ value }) => value));
const maxHeartRate = Math.max(...heartRateData.map(({ value }) => value));

Calculating Data Point Positions and Heights

Take each heart rate value and convert it to a percentage between the min (0%) and max (100%). The result will be used as a bottom position.

To “connect” the data points visually, we also need to get the bottom position of the next heart rate value.

// Calculated data point bottom position
const calcBottom = ((heartRateValue - minHeartRate) / (maxHeartRate - minHeartRate)) * GRAPH_HEIGHT
const calcNextBottom = ((nextHeartRateValue - minHeartRate) / (maxHeartRate - minHeartRate)) * GRAPH_HEIGHT

To calculate the height, we first subtract the vertical position of the next data point from the vertical position of the current data point. This gives us the distance between the two data points.

Since the distance between the two data points could be positive or negative, we use the Math.abs() method to ensure that the result is always a positive value.

Finally, we add the height of the data point itself, represented by the constant DATA_POINT_HEIGHT, to the distance between the two data points.

This gives us the total height of the data point in the graph.

const calcHeight = Math.abs(calcBottom - calcNextBottom) + DATA_POINT_HEIGHT

Now, to account for different data point heights and ensure they’re connected, we need to get the smaller of the two bottom positions between the current and next data points.

To determine the smaller bottom position, we just use the Math.min() method.

const pickSmallerBottom = Math.min(calcBottom, calcNextBottom)

Addressing Overflow and Scrollbar Nuances

There is also one nuance to mention and that is overflow + scrollbar.

Since the height is fixed to CHART_HEIGHT with overflow-x:scroll we have to account for scrollbar height else we’ll have this issue.

There are at least 2 different ways to solve this. The latter solution is what I used in the end.

  1. Add overflow-x:scroll + :hover{overflow-x: overlay} which is kinda awkward but doesn’t change the height of the chart.
First scrollbar solution

2. Add height of the scrollbar to the Chart height (in CSS not CHART_HEIGHT variable since we don’t want to account for the scrollbar in all other calculations).

From my research the height/width of a scrollbar in main browsers ( Chrome, Firefox, Safari, Edge) should be 15px which seems correct from my own testing.

Keep in mind that the scrollbar height might vary across different browsers and operating systems. The 15px value is an estimate based on popular browsers as of now but may change in the future.

You can also consider implementing a more dynamic solution that calculates the scrollbar height depending on the user’s browser and operating system.

The Final Chart: See the Live Demo

Final heart rate chart with animation

And that’s it for the chart!

To see the chart in action, I encourage you to check out the CodeSandbox link provided. Feel free to explore and experiment with the code to see how everything works together.

Check here -> CodeSandbox

When to Use Data Visualization Libraries Instead

This is a very niche use-case and solution and while creating custom visualizations using simple math and CSS can be lightweight and efficient in certain scenarios, there are several reasons why you might want to use a data visualization library instead:

  1. Complexity: Data visualization libraries offer a wide range of pre-built chart types and configurations that cater to complex data sets and relationships. Creating these visualizations from scratch can be time-consuming and require a deep understanding of both the data and the visualization techniques.
  2. Scalability: Libraries like D3.js, Fusion Charts, and Chart.js are designed to handle large data sets and perform efficiently even when rendering thousands of data points. Building custom visualizations from scratch might not offer the same level of performance, especially when dealing with big data.
  3. Interactivity: Data visualization libraries provide built-in interactive features, such as tooltips, zooming, and panning, which can be challenging to implement manually.

--

--

Marek Bombera

Hi 👋 I'm Marek a Front-end developer from Prague, Czechia. I like newest tech and web3 world. See my work at: https://marekbombera.dev