Callum Macrae

Building an animated COVID-19 tracker using Vue.js: part two

This is part two of a two-part mini-series about how you can use Vue and SVG to build an animated COVID-19 tracker.

  • Part one contained a brief primer to SVG and built a static chart using SVG and Vue.
  • Part two shows how to animate it the chart built in part one.

The code for the entire article can be found on GitHub: callumacrae/covid-visualisations.

On with the article!


#The static chart

In part one after a quick SVG primer, we downloaded some open source COVID-19 data from GitHub, transformed it into the data we actually wanted to visualise, and created the following chart:

Afghanistan 84 Albania 146 Algeria 302 Andorra 188 Angola 3 Antigua and Barbuda 3 Argentina 387 Armenia 265 Australia 2364 Austria 5588 Azerbaijan 93 Bahamas 5 Bahrain 419 Bangladesh 39 Barbados 18 Belarus 86 Belgium 4937 Benin 6 Bhutan 2 Bolivia 32 Bosnia and Herzegovina 176 Brazil 2554 Brunei 109 Bulgaria 242 Burkina Faso 146 Cabo Verde 4 Cambodia 96 Cameroon 75 Canada 3251 Central African Republic 3 Chad 3 Chile 1142 China 81661 Colombia 470 Congo (Brazzaville) 4 Congo (Kinshasa) 48 Costa Rica 201 Cote d'Ivoire 80 Croatia 442 Diamond Princess 712 Cuba 57 Cyprus 132 Czechia 1654 Denmark 1862 Djibouti 11 Dominican Republic 392 Ecuador 1173 Egypt 456 El Salvador 9 Equatorial Guinea 9 Eritrea 4 Estonia 404 Eswatini 4 Ethiopia 12 Fiji 5 Finland 880 France 25600 Gabon 6 Gambia 3 Georgia 75 Germany 37323 Ghana 93 Greece 821 Guatemala 24 Guinea 4 Guyana 5 Haiti 8 Holy See 4 Honduras 36 Hungary 226 Iceland 737 India 657 Indonesia 790 Iran 27017 Iraq 346 Ireland 1564 Israel 2369 Italy 74386 Jamaica 26 Japan 1307 Jordan 172 Kazakhstan 81 Kenya 28 Korea, South 9137 Kuwait 195 Kyrgyzstan 44 Latvia 221 Lebanon 333 Liberia 3 Liechtenstein 51 Lithuania 274 Luxembourg 1333 Madagascar 19 Malaysia 1796 Maldives 13 Malta 129 Mauritania 2 Mauritius 48 Mexico 475 Moldova 149 Monaco 31 Mongolia 10 Montenegro 52 Morocco 225 Namibia 7 Nepal 3 Netherlands 6438 New Zealand 205 Nicaragua 2 Niger 7 Nigeria 51 North Macedonia 177 Norway 3084 Oman 99 Pakistan 1063 Panama 443 Papua New Guinea 1 Paraguay 37 Peru 480 Philippines 636 Poland 1051 Portugal 2995 Qatar 537 Romania 906 Russia 658 Rwanda 41 Saint Lucia 3 Saint Vincent and the Grenadines 1 San Marino 208 Saudi Arabia 900 Senegal 99 Serbia 384 Seychelles 7 Singapore 631 Slovakia 216 Slovenia 528 Somalia 1 South Africa 709 Spain 49515 Sri Lanka 102 Sudan 3 Suriname 8 Sweden 2526 Switzerland 10897 Taiwan* 235 Tanzania 12 Thailand 934 Togo 23 Trinidad and Tobago 60 Tunisia 173 Turkey 2433 Uganda 14 Ukraine 145 United Kingdom 9640 Uruguay 217 Uzbekistan 60 Venezuela 91 Vietnam 141 Zambia 12 Zimbabwe 3 Dominica 7 Grenada 1 Mozambique 5 Syria 5 Timor-Leste 1 Belize 2 Laos 3 Libya 1 West Bank and Gaza 59 Guinea-Bissau 2 Mali 2 Saint Kitts and Nevis 2 UAE 333 United States 65778

While it isn't fully static as you can interact with the chart to pan through the data, it isn't animated, either - let's look at that now.

#Animating the chart

Now that we've got a chart displaying data for specified days, let's look at animating it when the day is changed.

We want to animate the following things:

  • When the countries change order, their position should be transitioned.
  • When a new country sees their first case, it should fade in (and vice versa if going through the data backwards).
  • When a countries case count increases or decreases, the bar width should change smoothly.

Vue's list transitions can help us with the first two, but it can't really help us with the width change, as that's a state transition.

Before we start animating the chart, let's move the bar logic into a separate component which will be passed the value and be responsible for the width - that means the animation will also happen inside the component.

Let's call our component ChartBar and give it the following API:

<ChartBar
  v-for="({ country, value }, i) in chartData"
  :transform="`translate(0, ${i * 60})`"
  :country="country"
  :chart-width="chartWidth"
  :max-value="maxValue"
  :value="value"
/>

We could also pass in i and have the component be responsible for its own positioning, but I decided it would be better outside the component. It doesn't really make much difference!

Here's the source for the new component:

<template>
  <g>
    <rect :x="barStart" :width="barWidth" height="50" />
    <text y="25" :x="barStart - 10">
      {{ country }}
    </text>
    <text y="25" :x="barStart + barWidth">
      {{ value }}
    </text>
  </g>
</template>

<script>
  export default {
    props: {
      country: { type: String, required: true },
      chartWidth: { type: Number, required: true },
      maxValue: { type: Number, required: true },
      value: { type: Number, required: true }
    },
    data: () => ({ barStart: 150 }),
    computed: {
      barWidth() {
        return (this.chartWidth - this.barStart) /
          this.maxValue * this.value;
      }
    }
  };
</script>

(I promise this the longest code block of the article)

#List enter and leave transitions

Let's start by fading in countries when they're added and fading them out again when they're removed.

Vue has a bunch of helpers built for animating and transitioning content. You can find the full documentation for it here: Transitions & Animation.

The list enter and leave transitions in particular are what we will be using here. In fact, there's an example in the documentation which is almost exactly what we want to do, just with HTML instead of SVG (which has implications we'll get to in a bit!)

Using Vue's list transitions is usually pretty straightforward. To start with, let's wrap the <ChartBar> element with a <transition-group> component:

<transition-group name="country-list" tag="g">
  <ChartBar
    v-for="({ country, value }, i) in chartData"
    class="country"
    :transform="`translate(0, ${i * 60})`"
    :country="country"
    :max-value="maxValue"
    :value="value"
    :chart-width="chartWidth"
    :key="country"
  />
</transition-group>

Now, when an element enters or leaves the DOM, Vue will add a class that we can use to apply a CSS transition to the change.

Let's add the following CSS to our main file (not the ChartBar component) to utilise the classes Vue is adding:

.country {
  transition: opacity 0.3s linear;
}
.country-list-enter,
.country-list-leave-to {
  opacity: 0;
}

.country-list-enter is added before the element is added to the DOM and removed immediately afterwards, so the element will fade in, and .country-list-leave-to is added when an element is going to be removed, so the element will fade out.

Here's where we're at now:

Afghanistan 84 Albania 146 Algeria 302 Andorra 188 Angola 3 Antigua and Barbuda 3 Argentina 387 Armenia 265 Australia 2,364 Austria 5,588 Azerbaijan 93 Bahamas 5 Bahrain 419 Bangladesh 39 Barbados 18 Belarus 86 Belgium 4,937 Benin 6 Bhutan 2 Bolivia 32 Bosnia and Herzegovina 176 Brazil 2,554 Brunei 109 Bulgaria 242 Burkina Faso 146 Cabo Verde 4 Cambodia 96 Cameroon 75 Canada 3,251 Central African Republic 3 Chad 3 Chile 1,142 China 81,661 Colombia 470 Congo (Brazzaville) 4 Congo (Kinshasa) 48 Costa Rica 201 Cote d'Ivoire 80 Croatia 442 Diamond Princess 712 Cuba 57 Cyprus 132 Czechia 1,654 Denmark 1,862 Djibouti 11 Dominican Republic 392 Ecuador 1,173 Egypt 456 El Salvador 9 Equatorial Guinea 9 Eritrea 4 Estonia 404 Eswatini 4 Ethiopia 12 Fiji 5 Finland 880 France 25,600 Gabon 6 Gambia 3 Georgia 75 Germany 37,323 Ghana 93 Greece 821 Guatemala 24 Guinea 4 Guyana 5 Haiti 8 Holy See 4 Honduras 36 Hungary 226 Iceland 737 India 657 Indonesia 790 Iran 27,017 Iraq 346 Ireland 1,564 Israel 2,369 Italy 74,386 Jamaica 26 Japan 1,307 Jordan 172 Kazakhstan 81 Kenya 28 Korea, South 9,137 Kuwait 195 Kyrgyzstan 44 Latvia 221 Lebanon 333 Liberia 3 Liechtenstein 51 Lithuania 274 Luxembourg 1,333 Madagascar 19 Malaysia 1,796 Maldives 13 Malta 129 Mauritania 2 Mauritius 48 Mexico 475 Moldova 149 Monaco 31 Mongolia 10 Montenegro 52 Morocco 225 Namibia 7 Nepal 3 Netherlands 6,438 New Zealand 205 Nicaragua 2 Niger 7 Nigeria 51 North Macedonia 177 Norway 3,084 Oman 99 Pakistan 1,063 Panama 443 Papua New Guinea 1 Paraguay 37 Peru 480 Philippines 636 Poland 1,051 Portugal 2,995 Qatar 537 Romania 906 Russia 658 Rwanda 41 Saint Lucia 3 Saint Vincent and the Grenadines 1 San Marino 208 Saudi Arabia 900 Senegal 99 Serbia 384 Seychelles 7 Singapore 631 Slovakia 216 Slovenia 528 Somalia 1 South Africa 709 Spain 49,515 Sri Lanka 102 Sudan 3 Suriname 8 Sweden 2,526 Switzerland 10,897 Taiwan* 235 Tanzania 12 Thailand 934 Togo 23 Trinidad and Tobago 60 Tunisia 173 Turkey 2,433 Uganda 14 Ukraine 145 United Kingdom 9,640 Uruguay 217 Uzbekistan 60 Venezuela 91 Vietnam 141 Zambia 12 Zimbabwe 3 Dominica 7 Grenada 1 Mozambique 5 Syria 5 Timor-Leste 1 Belize 2 Laos 3 Libya 1 West Bank and Gaza 59 Guinea-Bissau 2 Mali 2 Saint Kitts and Nevis 2 UAE 333 United States 65,778

It's easier to see the affect if you go to day one and click forwards from there, as that's when most countries start appearing.

#List move transitions?

Vue contains "list move transition" functionality where it applies the FLIP technique to elements being reordered to smoothly transition them to the new position. At first glance, it looks like it might come in handy here, but it turns out we don't have to use it in this case as we're positioning groups using transform in the first place.

All we have to do is add a transition for transform to the existing .country transition declaration:

.country {
  transition: opacity 0.3s linear,
    transform 0.3s linear;
}
.country-list-enter,
.country-list-leave-to {
  opacity: 0;
}

Unfortunately, I ran into what looked like an issue with Vue here and had to make some changes to work around it. While currently i is the index of the country in chartData (a sorted array), that caused some strange unexpected behaviour.

To work around the issue, we have to sort the data in another array and add a position property to the object for each country instead.

Our modified chartData function looks like this:

chartData() {
  const chartData = Object.entries(data.countryData)
    .map(([country, dataArray]) => {
      return {
        country,
        value: dataArray[this.day]
      };
    })
    .filter(({ value }) => value);

  const sortedData = chartData.slice()
    .sort((a, b) => b.value - a.value);

  return chartData.map(item => ({
    position: sortedData.indexOf(item),
    ...item
  }));
},

Now the order of chartData isn't changed so the issue won't occur, but we have to use position instead of i. Let's go ahead and make that change:

<transition-group name="country-list">
  <ChartBar
    v-for="{ country, value, position } in chartData"
    class="country"
    :transform="`translate(0, ${position * 60})`"
    :country="country"
    :max-value="maxValue"
    :value="value"
    :chart-width="chartWidth"
  />
</transition-group>

You could even just call it i if you wanted!

Here's the updated demo:

Afghanistan 84 Albania 146 Algeria 302 Andorra 188 Angola 3 Antigua and Barbuda 3 Argentina 387 Armenia 265 Australia 2,364 Austria 5,588 Azerbaijan 93 Bahamas 5 Bahrain 419 Bangladesh 39 Barbados 18 Belarus 86 Belgium 4,937 Benin 6 Bhutan 2 Bolivia 32 Bosnia and Herzegovina 176 Brazil 2,554 Brunei 109 Bulgaria 242 Burkina Faso 146 Cabo Verde 4 Cambodia 96 Cameroon 75 Canada 3,251 Central African Republic 3 Chad 3 Chile 1,142 China 81,661 Colombia 470 Congo (Brazzaville) 4 Congo (Kinshasa) 48 Costa Rica 201 Cote d'Ivoire 80 Croatia 442 Diamond Princess 712 Cuba 57 Cyprus 132 Czechia 1,654 Denmark 1,862 Djibouti 11 Dominican Republic 392 Ecuador 1,173 Egypt 456 El Salvador 9 Equatorial Guinea 9 Eritrea 4 Estonia 404 Eswatini 4 Ethiopia 12 Fiji 5 Finland 880 France 25,600 Gabon 6 Gambia 3 Georgia 75 Germany 37,323 Ghana 93 Greece 821 Guatemala 24 Guinea 4 Guyana 5 Haiti 8 Holy See 4 Honduras 36 Hungary 226 Iceland 737 India 657 Indonesia 790 Iran 27,017 Iraq 346 Ireland 1,564 Israel 2,369 Italy 74,386 Jamaica 26 Japan 1,307 Jordan 172 Kazakhstan 81 Kenya 28 Korea, South 9,137 Kuwait 195 Kyrgyzstan 44 Latvia 221 Lebanon 333 Liberia 3 Liechtenstein 51 Lithuania 274 Luxembourg 1,333 Madagascar 19 Malaysia 1,796 Maldives 13 Malta 129 Mauritania 2 Mauritius 48 Mexico 475 Moldova 149 Monaco 31 Mongolia 10 Montenegro 52 Morocco 225 Namibia 7 Nepal 3 Netherlands 6,438 New Zealand 205 Nicaragua 2 Niger 7 Nigeria 51 North Macedonia 177 Norway 3,084 Oman 99 Pakistan 1,063 Panama 443 Papua New Guinea 1 Paraguay 37 Peru 480 Philippines 636 Poland 1,051 Portugal 2,995 Qatar 537 Romania 906 Russia 658 Rwanda 41 Saint Lucia 3 Saint Vincent and the Grenadines 1 San Marino 208 Saudi Arabia 900 Senegal 99 Serbia 384 Seychelles 7 Singapore 631 Slovakia 216 Slovenia 528 Somalia 1 South Africa 709 Spain 49,515 Sri Lanka 102 Sudan 3 Suriname 8 Sweden 2,526 Switzerland 10,897 Taiwan* 235 Tanzania 12 Thailand 934 Togo 23 Trinidad and Tobago 60 Tunisia 173 Turkey 2,433 Uganda 14 Ukraine 145 United Kingdom 9,640 Uruguay 217 Uzbekistan 60 Venezuela 91 Vietnam 141 Zambia 12 Zimbabwe 3 Dominica 7 Grenada 1 Mozambique 5 Syria 5 Timor-Leste 1 Belize 2 Laos 3 Libya 1 West Bank and Gaza 59 Guinea-Bissau 2 Mali 2 Saint Kitts and Nevis 2 UAE 333 United States 65,778

We can now see that in addition to elements fading in and out when they're added and removed, they're also moving up and down smoothly when the countries change positions.

#Bar width transition

Now for the fun one!

Transitioning the width of the bar is much more complicated than the previous transitions we've looked at, as it involves transitioning the data itself, not just an attribute. If we only changed the width of the bar, the numbers displayed on the visualisation would be wrong.

This is a large part of the reason we created the ChartBar component—handling the transition logic for each bar in the component is a lot simpler than handling the transition logic for every single bar at the same time in the main file.

So how do we transition state?

There's a few different ways we can approach this, but the one we'll go for is as follows:

  • Add a new property of the data object of the ChartBar component called tweenedValue (I'll explain what tweening is in a couple paragraphs).
  • Add a watcher watching for when the value prop is changed.
  • When the value prop is changed, transition tweenedValue from the old value to the new value.
  • Use tweenedValue instead of value in the bar width calculation.

We also have to do the same to the maxValue prop to ensure that the bar width is transitioned smoothly.

To transition a value from one number to another, we could use requestAnimationFrame and transition the value ourselves, but for the sake of simplicity we'll use one of a number of available tweening libraries to do it for us.

Sidenote: if you're interested in how this could work without a library, check out this codepen where I tween the values using requestAnimationFrame.

Tweening (short for in-betweening) is the process in animation of generating the frames between two images, called key frames. For example, if you wanted to animate an element from one position to another and have it slow down when it's reaching its destination, a tweening library can help you with that.

In this case, we're not going to use the tweening library to change an element in the DOM, we're just going to use it to change a value on the data object.

GSAP (GreenSock Animation Platform) is a widely used animation library that provides pretty much everything you'd need to animate anything on your website or application. It turns out that its tweening functionality, in addition to being able to modify DOM elements, can also be used to smoothly transition values on the Vue data object.

It's important to note at this point that loading all of GSAP just to transition a number is definitely overkill. There's other smaller libraries that can do a good job of this, I just wanted to stick with a well known example.

Let's look at a quick example of how this works.

Click this button a couple times to see an example of a tweened number:

number is 1000.

tweenedNumber is 1000.

Here's what happens when we click on the button:

this.number = Math.round(Math.random() * 10000);
gsap.to(this.$data, {
  tweenedNumber: this.number
});

this.number is being set directly to a new random number. This simulates the width prop of the ChartBar component.

We're then calling gsap.to() to tell GSAP to tween tweenedNumber from its previous value to the new random number. this.$data is the data object of the current Vue instance - we could also pass it just this, but this.$data makes it clearer what is going on.

In affect, GSAP then repeatedly increases or decreases this.$data.tweenedNumber (aka this.tweenedNumber) until it equals the value specified.


Now let's apply this in watchers for width and maxWidth:

watch: {
  maxValue(newMax) {
    gsap.to(this.$data, {
      tweenedMaxValue: newMax
    });
  },
  value(newValue) {
    gsap.to(this.$data, {
      tweenedValue: newValue
    });
  }
}

And now, after changing the bar width logic to use tweenedValue instead of value, this is what we get:

Afghanistan 84 Albania 146 Algeria 302 Andorra 188 Angola 3 Antigua and Barbuda 3 Argentina 387 Armenia 265 Australia 2,364 Austria 5,588 Azerbaijan 93 Bahamas 5 Bahrain 419 Bangladesh 39 Barbados 18 Belarus 86 Belgium 4,937 Benin 6 Bhutan 2 Bolivia 32 Bosnia and Herzegovina 176 Brazil 2,554 Brunei 109 Bulgaria 242 Burkina Faso 146 Cabo Verde 4 Cambodia 96 Cameroon 75 Canada 3,251 Central African Republic 3 Chad 3 Chile 1,142 China 81,661 Colombia 470 Congo (Brazzaville) 4 Congo (Kinshasa) 48 Costa Rica 201 Cote d'Ivoire 80 Croatia 442 Diamond Princess 712 Cuba 57 Cyprus 132 Czechia 1,654 Denmark 1,862 Djibouti 11 Dominican Republic 392 Ecuador 1,173 Egypt 456 El Salvador 9 Equatorial Guinea 9 Eritrea 4 Estonia 404 Eswatini 4 Ethiopia 12 Fiji 5 Finland 880 France 25,600 Gabon 6 Gambia 3 Georgia 75 Germany 37,323 Ghana 93 Greece 821 Guatemala 24 Guinea 4 Guyana 5 Haiti 8 Holy See 4 Honduras 36 Hungary 226 Iceland 737 India 657 Indonesia 790 Iran 27,017 Iraq 346 Ireland 1,564 Israel 2,369 Italy 74,386 Jamaica 26 Japan 1,307 Jordan 172 Kazakhstan 81 Kenya 28 Korea, South 9,137 Kuwait 195 Kyrgyzstan 44 Latvia 221 Lebanon 333 Liberia 3 Liechtenstein 51 Lithuania 274 Luxembourg 1,333 Madagascar 19 Malaysia 1,796 Maldives 13 Malta 129 Mauritania 2 Mauritius 48 Mexico 475 Moldova 149 Monaco 31 Mongolia 10 Montenegro 52 Morocco 225 Namibia 7 Nepal 3 Netherlands 6,438 New Zealand 205 Nicaragua 2 Niger 7 Nigeria 51 North Macedonia 177 Norway 3,084 Oman 99 Pakistan 1,063 Panama 443 Papua New Guinea 1 Paraguay 37 Peru 480 Philippines 636 Poland 1,051 Portugal 2,995 Qatar 537 Romania 906 Russia 658 Rwanda 41 Saint Lucia 3 Saint Vincent and the Grenadines 1 San Marino 208 Saudi Arabia 900 Senegal 99 Serbia 384 Seychelles 7 Singapore 631 Slovakia 216 Slovenia 528 Somalia 1 South Africa 709 Spain 49,515 Sri Lanka 102 Sudan 3 Suriname 8 Sweden 2,526 Switzerland 10,897 Taiwan* 235 Tanzania 12 Thailand 934 Togo 23 Trinidad and Tobago 60 Tunisia 173 Turkey 2,433 Uganda 14 Ukraine 145 United Kingdom 9,640 Uruguay 217 Uzbekistan 60 Venezuela 91 Vietnam 141 Zambia 12 Zimbabwe 3 Dominica 7 Grenada 1 Mozambique 5 Syria 5 Timor-Leste 1 Belize 2 Laos 3 Libya 1 West Bank and Gaza 59 Guinea-Bissau 2 Mali 2 Saint Kitts and Nevis 2 UAE 333 United States 65,778

Nice!

The final piece is to display the tweened number on the bar instead of the actual value, so that it increases as the bar changes width. To do that, we'll also need to round it.

Here is our complete COVID-19 tracker, animated and interactive:

Afghanistan 84 Albania 146 Algeria 302 Andorra 188 Angola 3 Antigua and Barbuda 3 Argentina 387 Armenia 265 Australia 2,364 Austria 5,588 Azerbaijan 93 Bahamas 5 Bahrain 419 Bangladesh 39 Barbados 18 Belarus 86 Belgium 4,937 Benin 6 Bhutan 2 Bolivia 32 Bosnia and Herzegovina 176 Brazil 2,554 Brunei 109 Bulgaria 242 Burkina Faso 146 Cabo Verde 4 Cambodia 96 Cameroon 75 Canada 3,251 Central African Republic 3 Chad 3 Chile 1,142 China 81,661 Colombia 470 Congo (Brazzaville) 4 Congo (Kinshasa) 48 Costa Rica 201 Cote d'Ivoire 80 Croatia 442 Diamond Princess 712 Cuba 57 Cyprus 132 Czechia 1,654 Denmark 1,862 Djibouti 11 Dominican Republic 392 Ecuador 1,173 Egypt 456 El Salvador 9 Equatorial Guinea 9 Eritrea 4 Estonia 404 Eswatini 4 Ethiopia 12 Fiji 5 Finland 880 France 25,600 Gabon 6 Gambia 3 Georgia 75 Germany 37,323 Ghana 93 Greece 821 Guatemala 24 Guinea 4 Guyana 5 Haiti 8 Holy See 4 Honduras 36 Hungary 226 Iceland 737 India 657 Indonesia 790 Iran 27,017 Iraq 346 Ireland 1,564 Israel 2,369 Italy 74,386 Jamaica 26 Japan 1,307 Jordan 172 Kazakhstan 81 Kenya 28 Korea, South 9,137 Kuwait 195 Kyrgyzstan 44 Latvia 221 Lebanon 333 Liberia 3 Liechtenstein 51 Lithuania 274 Luxembourg 1,333 Madagascar 19 Malaysia 1,796 Maldives 13 Malta 129 Mauritania 2 Mauritius 48 Mexico 475 Moldova 149 Monaco 31 Mongolia 10 Montenegro 52 Morocco 225 Namibia 7 Nepal 3 Netherlands 6,438 New Zealand 205 Nicaragua 2 Niger 7 Nigeria 51 North Macedonia 177 Norway 3,084 Oman 99 Pakistan 1,063 Panama 443 Papua New Guinea 1 Paraguay 37 Peru 480 Philippines 636 Poland 1,051 Portugal 2,995 Qatar 537 Romania 906 Russia 658 Rwanda 41 Saint Lucia 3 Saint Vincent and the Grenadines 1 San Marino 208 Saudi Arabia 900 Senegal 99 Serbia 384 Seychelles 7 Singapore 631 Slovakia 216 Slovenia 528 Somalia 1 South Africa 709 Spain 49,515 Sri Lanka 102 Sudan 3 Suriname 8 Sweden 2,526 Switzerland 10,897 Taiwan* 235 Tanzania 12 Thailand 934 Togo 23 Trinidad and Tobago 60 Tunisia 173 Turkey 2,433 Uganda 14 Ukraine 145 United Kingdom 9,640 Uruguay 217 Uzbekistan 60 Venezuela 91 Vietnam 141 Zambia 12 Zimbabwe 3 Dominica 7 Grenada 1 Mozambique 5 Syria 5 Timor-Leste 1 Belize 2 Laos 3 Libya 1 West Bank and Gaza 59 Guinea-Bissau 2 Mali 2 Saint Kitts and Nevis 2 UAE 333 United States 65,778

You can now see that when you change the day, the numbers are animated as well as the bar widths.

The code on GitHub contains a couple extra features such as configurable animation time and smarter bar heights—here's the link again: callumacrae/covid-visualisation.


So what have we learned here?

  • SVG allows us to draw shapes embedded in our HTML documents, which enables us to create visualisations.
  • As SVG can be embedded directly in our HTML documents, we can use Vue templates to generate SVGs.
  • <transition-group> works in SVGs as well, and we can use it to fade elements in an out, just like with HTML.
  • CSS transitions also work just fine in SVGs, which was useful for animating positions when elements were being reordered.
  • Finally, while Vue doesn't provide us with any help here, animating state doesn't have to be difficult, especially if we use the help of a library like GSAP.

There's loads of other potential applications for this, and not just in data visualisation. In a future article I hope write about how you can use d3-shape directly with Vue to display and visualise data on a world map. If that's something you'd be interested in, make sure to follow me on Twitter so that you can see when I post it.

I hope you found this useful, and stay safe!

Big thanks to Darek Gusto and Bartosz Trzos for proof reading this article!

« Return to home