# How to Make an Animated Bubble Chart With CSS

We've covered plenty of techniques for developing different types of charts using CSS or/and JavaScript. Today, I’ll show you how to make a bubble chart with CSS.

### What We’re Building

Here’s the bubble chart we’ll be working on (hit reload to see it animate, and hover over the bubbles to reveal the tooltips):

## What is a Bubble Chart?

bubble chart is a chart type, considered to be the cousin of the scatter plot, and its primary goal is to visualize relationships between three different dimensions/variables: one is represented by the X axis, one by the Y axis, and one by the bubble size.

This kind of chart can provide useful information for trends over the year, historical data comparison, sales comparison, etc.

You can build complex dynamic bubble charts by using different JavaScript libraries like Highcharts.js and ApexCharts.js.

## 1. Begin With The Data

For this demo, we’ll borrow some data from a previous chart tutorial and elaborate on them a bit. So, let’s assume that we want to visualize in a bubble chart the following data that describes the funding of a charitable organization over the years:

Year Funding Number of Employees
2018 €95,000 12
2016 €72,000 10
2015 €50,000 8
2012 €35,000 6
2010 €15,000 4

Each table row will correspond to a point (bubble). The first two columns will describe the bubble position along the X and Y axes, while the third one will indicate its size.

## 2. Specify the Page Markup

We’ll specify a wrapper element that contains two lists:

• The first list sets the y-axis range. If you take a closer look at the table data above, you’ll see that the second column includes values up to 95,000. Keeping this in mind, we’ll define six values from 0 to 100,000 with a step size of 20,000. The values of the y-axis will therefore be 0, 20,000, 40,000, 60,000, 80,000, and 100,000.
• The second list sets the x-axis data. These figures are extracted from the first column of our table, from lowest to highest. Notice in the markup below though, that a list item contains the same year twice. We could have omitted to set the year as the item’s text node. But it’s important to store this value in the data-year attribute. As we’ll see later, we’ll pass the value of this attribute to the related ::before pseudo-element. Consider also the data-details attribute. Its value will serve as the tooltip of the corresponding bubble. Inside this value exist multiple appearances of the &#xA; that is the HTML representation in hex format of a line feed character. We’ll put it after every bit of text where we want to force a new line. More on that later.

Here’s the required markup:

 1 
 2 
 3 
• €100,000
•  4  ...  5 
 6 
 7 
•  8  2010  9 
•  10  ...  11 
 12 


## 3. Style the Chart

For simplicity, I’ll skip some reset/basic styles. You can check the rest of them by clicking the CSS tab of the demo project.

I haven’t optimised the styles, making it easier for you to see what’s going on

Lastly, I’ve embedded the premium Cheddar Gothic font taken from Envato Elements for the heading.

The chart wrapper will be a flex container with a maximum width of 700px and horizontally centered content. The y-axis will be three times bigger than the x-axis.

The related CSS:

 1 .chart-wrapper {  2  display: flex;  3  max-width: 700px;  4  padding-right: 15px;  5  margin: 30px auto 0;  6 }  7 8 .chart-wrapper .chart-y {  9  flex: 1;  10 }  11 12 .chart-wrapper .chart-x {  13  flex: 3;  14 } 

### The x-axis

The second list which includes the x-axis data will also be a flex container. Its items will have a width of 12%, be evenly distributed across the main axis, and sit at the bottom of the container. We’ll make the list look like a neumorphic element with the help of this tool.

Plus, each list item will have a height that will depend on the associated funding value (see table above). For example, a value of 15,000 corresponds to height: 15%.

Here are the related styles:

 1 .chart-wrapper .chart-x {  2  display: flex;  3  justify-content: space-around;  4  align-items: flex-end;  5  border-radius: 48px;  6  background: #f0f0f0;  7  box-shadow: -8px 8px 20px #b4b4b4, 8px -8px 20px #ffffff;  8 }  9 10 .chart-wrapper .chart-x li {  11  position: relative;  12  width: 12%;  13 }  14 15 .chart-wrapper .chart-x li:nth-child(1) {  16  height: 15%; /*represents €15,000*/  17 }  18 19 .chart-wrapper .chart-x li:nth-child(2) {  20  height: 35%; /*represents €35,000*/  21 }  22 23 .chart-wrapper .chart-x li:nth-child(3) {  24  height: 50%; /*represents €50,000*/  25 }  26 27 .chart-wrapper .chart-x li:nth-child(4) {  28  height: 72%; /*represents €72,000*/  29 }  30 31 .chart-wrapper .chart-x li:nth-child(5) {  32  height: 95%; /*represents €95,000*/  33 } 

Next, we'll use the ::before pseudo-element of each item to output the labels of the x-axis.

The associated CSS:

 1 .chart-wrapper .chart-x li::before {  2  content: attr(data-year);  3  position: absolute;  4  left: 50%;  5  bottom: 0;  6  width: 100%;  7  text-align: center;  8  transform: translate(-50%, 40px) rotate(45deg);  9 } 

### Bubbles

Each span element will correspond to a bubble (point). Each bubble will have a different color and size.

The bubble size will depend on the value of the related third table column (see table). Assuming that the width and height for the smallest (first) bubble will be 15px, we’ll calculate accordingly the size of the remaining ones.

Another thing we’ll do is to prevent showing the bubbles by default and instead show them with a fade and scale animation. In your own pages where the DOM will probably contain more elements, for more accurate results you can wait until the page loads before showing the bubbles as we did in the thermometer chart.

Here are the associated styles:

 1 /*CUSTOM VARIABLES HERE*/  2 3 .chart-wrapper .chart-x li:nth-child(1) span {  4  width: 15px; /*represents 4 employees*/  5  height: 15px; /*represents 4 employees*/  6  background-color: var(--bubble-color1);  7 }  8 9 .chart-wrapper .chart-x li:nth-child(2) span {  10  width: 22.5px; /*represents 6 employees*/  11  height: 22.5px; /*represents 6 employees*/  12  background-color: var(--bubble-color2);  13 }  14 15 .chart-wrapper .chart-x li:nth-child(3) span {  16  width: 30px; /*represents 8 employees*/  17  height: 30px; /*represents 8 employees*/  18  background-color: var(--bubble-color3);  19 }  20 21 .chart-wrapper .chart-x li:nth-child(4) span {  22  width: 37.5px; /*represents 10 employees*/  23  height: 37.5px; /*represents 10 employees*/  24  background-color: var(--bubble-color4);  25 }  26 27 .chart-wrapper .chart-x li:nth-child(5) span {  28  width: 45px; /*represents 12 employees*/  29  height: 45px; /*represents 12 employees*/  30  background-color: var(--bubble-color5);  31 }  32 33 .chart-wrapper .chart-x span {  34  content: "";  35  position: absolute;  36  top: 0;  37  left: 50%;  38  border-radius: 50%;  39  font-size: 0;  40  cursor: pointer;  41  opacity: 0;  42  transform: scale(0.001);  43  animation: fade-in 0.8s linear forwards;  44 }  45 46 @keyframes fade-in {  47  100% {  48  opacity: 1;  49  transform: scale(1) translateX(-50%);  50  }  51 } 

### Tooltips

As we’ve discussed, each bubble will have a tooltip to show more information. This will be visible when the viewport width is larger than 700px and when we hover over a bubble.

To create it, we’ll use the ::before and ::after pseudo-elements of each span (bubble). The ::after pseudo-element will serve as the actual tooltip while the ::before one will have a more decorative place and serve as the tooltip’s triangle.

As we’ve mentioned above, the tooltip info we’ll come from the data-details attribute. Here we’ve used the &#xA; hex representation to split the attribute value into multi-lines. But this isn’t enough; we also have to use the white-space property. Here’s a detailed Stack Overflow thread about it.

The related styles:

 1 .chart-wrapper .chart-x span::before {  2  content: "";  3  position: absolute;  4  left: 50%;  5  bottom: calc(100% + 3px);  6  transform: translateX(-50%);  7  width: 0;  8  height: 0;  9  border-style: solid;  10  border-width: 6px 6px 0 6px;  11  border-color: rgba(0, 0, 0, 0.7) transparent transparent transparent;  12  opacity: 0;  13  transition: opacity 0.15s linear;  14  pointer-events: none;  15 }  16 17 .chart-wrapper .chart-x span::after {  18  content: attr(data-details);  19  position: absolute;  20  left: 50%;  21  bottom: calc(100% + 9px);  22  min-width: 90px;  23  line-height: 1.35;  24  transform: translateX(-50%);  25  color: var(--white);  26  background: rgba(0, 0, 0, 0.7);  27  font-size: 1rem;  28  white-space: pre;  29  border-radius: 3px;  30  padding: 6px;  31  opacity: 0;  32  z-index: 1;  33  transition: opacity 0.15s linear;  34  pointer-events: none;  35 }  36 37 .chart-wrapper .chart-x span:hover::before,  38 .chart-wrapper .chart-x span:hover::after {  39  opacity: 1;  40 }  41 42 @media screen and (max-width: 700px) {  43  .chart-wrapper .chart-x span::before,  44  .chart-wrapper .chart-x span::after {  45  display: none;  46  }  47 } 

## Conclusion

Done, folks! Today, we practiced our CSS knowledge by building a fully functional bubble chart. While it isn’t the most popular method to visualize data, hopefully, it’ll have a place in one of your future data visualization projects.

Let’s remind ourselves of what we built: