DEV Community

Leopold
Leopold

Posted on

Build cool charts and visualize data with d3.js

You probably have seen a lot of "covidtracker" or cryptocurrency charts applications for some time now, some of them offer really awesome charts which you can hardly make with commons libraries of "ready to make" charts.

I must admit... I love play with data and seeing these kinds of beautiful UIs with all those numbers so well presented makes me jealous, I was like : oh, I really need to be able to build that !
So I recently started to put my head into the famous d3.js library.
Don't expect I come up with something impressive here, no but I hope a good basic to introduce you to data visualization with d3.js.

What we are going to build is is a bar chart of the top 10 population by country :

DEMO

What is d3.js ?

 
In fact d3.js is not a chart library, It is a kind of big API to manipulate the DOM and offers a lot of utilities functions. You can manipulate svg, canvas, html and so construct charts, graphs, maps, everything you want for data visualization.
The biggest advantage of using d3 is that every cool data visualization you've ever seen on the web are probably feasible using d3.js.
The major drawback is d3.js may be overwhelming at first and is quite hard to learn in my opinion.

 

Build the application

 

Prepare the project :

 
We won't use any framework or particular config here just for the sake of simplicity some pure javascript.

Create a index.html file and include this :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>d3.js Demo</title>
</head>
<body>
    <h1>Top 10 population by country</h1>
    <div id="chart"></div>
    <div id="tooltip">
        <h3 id="country_name"></h3>
        <p id="country_population"></p>
    </div>

    <script src="https://d3js.org/d3.v6.js"></script>
    <script src="index.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

We are importing the d3.js library and the index.js file which is going to contain our code.

Create a style.css file with thoses styles :

* {
    margin: 0;
    box-sizing: border-box;
}

body {
    box-sizing: border-box;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-family: Avenir, Helvetica, Arial, sans-serif;
}

svg {
    background-color: #2a2a2e;
    color: white;
    border-radius: 5px;
}

h1 {
    padding-bottom: 2rem;
}

#tooltip {
    position: absolute;
    visibility: hidden;
    background-color: white;
    font-size: .7rem;
    border-radius: 5px;
    padding: .3rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: center;
}

#country_name {
    padding: .3rem;
}

#country_population {
    padding: .3rem;
}

.bar {
    transition: .2s all;
}
Enter fullscreen mode Exit fullscreen mode

Finally, add an index.js file. We are now ready to start coding.
 

Prepare the data

 
We are going to use restcountries API.

What we want to display on the chart is a list of the top 10 countries in term of population so we only need the name and population for x/y axis of each country.

Let's start by gathering these data and format them in the desired shape :

const API_URL = 'https://restcountries.eu/rest/v2/all';

const getData = async () => {
  const reponse = await fetch(API_URL);
  const result = await reponse.json();
  // Sort by population
  const dataSort = result.sort((a, b) => b.population - a.population);
  // only keep the top 10 population
  dataSort.length = 10;
  // We only need name + population
  const dataReady = dataSort.map((country) => ({
    name: country.name,
    population: Math.floor(country.population / 1000000),
  }));
  return dataReady;
};
Enter fullscreen mode Exit fullscreen mode

Now we have exactly what we need :

https://i.gyazo.com/384c61698aae5dc6f39c86d8b5447150.png
 

Build the chart :

 

Create the main element

Create a new function which will be in charge of generating the bar chart with the help of d3 :

const generateChart = (popData) => {
  const margin = {
    top: 20,
    right: 40,
    bottom: 60,
    left: 80,
  };
  const width = 1000 - margin.left - margin.right;
  const height = 500 - margin.top - margin.bottom;

  // Create svg
  const svgElement = d3
    .select('#chart')
    .append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append('g')
    .attr('transform', `translate(${margin.left},${margin.top})`);
}
Enter fullscreen mode Exit fullscreen mode

Notice how we use "d3.select" to select a dom element and chains other methods such as "append" and "attr" to construct our elements. Thoses are really the basic of d3.
The margin, width and height variables are there to set up the position of our svg element and will be use to position our axis as well.
 

Create axis

Next let's create our axis with d3-scale methods :

    // Add X axis
    const xScale = d3.scaleBand()
    .range([0, width])
    .domain(popData.map((s) => s.name))
    .padding(0.2)
    svgElement.append('g')
        .attr('transform', `translate(0, ${height})`)
        .call(d3.axisBottom(xScale));

    // Add Y axis
    const yScale = d3.scaleLinear()
        .domain([popData[0].population, popData[9].population])
        .range([0, height]);
    svgElement.append('g')
        .call(d3.axisLeft(yScale));

   // Add grid
   svgElement
    .append('g')
.call(d3.axisLeft(yScale).ticks().tickSize(-width).tickFormat(''));
Enter fullscreen mode Exit fullscreen mode

We have used several utilities methods from d3 part of the scale module to correctly map our axis with the datas (scaleLinear, scaleBand).
If you open your navigator you see now an svg element with two axis but no data yet.
 

Represent the data with bars

To represent our data with bar we simply create rectangles and add them to our main svg element with the correct width and proportion again thanks to certains d3-scale methods (bandwidth, xScale, yScale) :

  // Draw the bars
  svgElement
    .append('g')
    .selectAll('.bar')
    .data(popData)
    .enter()
    .append('rect')
    .attr('class', 'bar')
    .attr('x', (d) => xScale(d.name))
    .attr('width', xScale.bandwidth())
    .attr('y', (d) => yScale(d.population))
    .attr('height', 0)
    .style('fill', '#00FA9A')
    .transition()
    .duration(750)
    .attr('height', (d) => height - yScale(d.population));
Enter fullscreen mode Exit fullscreen mode

Our chart is working right now but let's make it a bit more "alive".
 

Add Tooltip and hover effect :

We want to show the exact population when we hover one bar so we need to create a tooltip and add mouse events on each bar.
Remember: there is a div element in our index.html page with a tooltip id, and some styles in the css. In fact everything was ready, the tooltip is there but hidden, we simply need now to add mouse event :

 // create a tooltip
  const tooltip = d3.select('#tooltip');
  const tooltip_name = d3.select('#country_name');
  const tooltip_pop = d3.select('#country_population');

  // Add mouse event to show the tooltip when hovering bars
  d3.selectAll('.bar')
    .on('mouseover', function () {
      d3.select(this).style('fill', '#59ffb2');
      tooltip.style('visibility', 'visible');
    })
    .on('mousemove', function (e, d) {
      tooltip
        .style('top', event.pageY - 10 + 'px')
        .style('left', event.pageX + 10 + 'px');
      tooltip_name.text(d.name);
      tooltip_pop.text(`Population: ${d.population} Millions`);
    })
    .on('mouseout', function () {
      d3.select(this).style('fill', '#00FA9A');
      tooltip.style('visibility', 'hidden');
    });
Enter fullscreen mode Exit fullscreen mode

 

Add text label for the axis :

  // text label for the y axis
  svgElement
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('y', 0 - margin.left)
    .attr('x', 0 - height / 2)
    .attr('dy', '1em')
    .style('text-anchor', 'middle')
    .style('fill', 'white')
    .text('Population (in millions)');

  // text label for the y axis
  svgElement
    .append('text')
    .attr('y', height + 30)
    .attr('x', 0 + width / 2)
    .attr('dy', '1em')
    .style('text-anchor', 'middle')
    .style('fill', 'white')
    .text('Country name');  
Enter fullscreen mode Exit fullscreen mode

 

Execute the code :

In the main scope simply execute our functions

getData().then(generateChart);
Enter fullscreen mode Exit fullscreen mode

And there you go, now you should have this result .

If you want to check the entire code : here

There are really so much possibilities and things to build in the data visualization domain, just be curious and explore !
I hope I have been clear enough and that it has help you to apprehend chart construction for creating better UI.

Have a good day!

Top comments (0)