How to visualize data with a bar chart using d3
Tags: beginners, javascript, d3, webdev
Published: 2023-06-04 22:31:29

Maybe it just occurred to you that you haven't visualized any data before since the start of your coding journey, or perhaps you haven't found a good blog explaining how data can be visualized. 

Say no more.

In this blog post, we are going through the process of displaying data with a bar-chart using my freeCodeCamp data visualization project as a real life [demo](https://codepen.io/Luwa_I/full/poxGPvo).

We would also be using a javaScript library called D3.js, for those unfamiliar with D3 it is a JavaScript library for producing dynamic, interactive data visualizations in web browsers. 

The first step is to add the D3 script tag in the head of your `html`.

```html
<script src="https://d3js.org/d3.v7.min.js"></script>
```
With that added, congrats we can now visualize data.

I am keeping in mind that d3 can be a very confusing thing to grasp at first sight, which is why I am going to break it down into much smaller bit so you understand why we do what we do.

##The HTML

###1. Add an svg element in your html: 
This can be done manually by adding the following line of code 

```html
   <svg id="chart"></svg>
```
Your svg `id` be anything you like as long as it relates to what is being done.

Now, adding an svg doesn't do anything but hold the rest of the element we are going to put in it later. Before moving on to the javascript aspect, our bar chart needs an heading.

###2. Add a text element: 
we can again just include this with a `text` tag directly in our `svg`, add the line below in the `svg` tag.
```html
 <text id="title" x="280" y="40">United States GDP</text>
```
Not a fan of Maths? Don't be confused the `x` and `y` above only helps with the positioning as it doesn't behave like a normal html element, x is the left and y is the top think of it as a margin left and margin top.

In summary our html file should look similar to this
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>bar chart</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
    <body>
       <svg id="canvas">
         <text id="title" x="280" y="40">United States GDP</text>
        </svg>
    </body>
</html>
```
##Some CSS

If you aren't ready to give your bar chart some styling, I suggest giving the `svg` at least a `background-color` so you can see the changes that will be made, for this project I have a few styles added, you can customize this to suit your taste.

```css
 body {
     width: 100%;
     height: 100vh;
     margin: 0;
     padding: 0;
     display: flex;
     flex-direction: column;
     justify-content: center;
     align-items: center;
     background-color: #4a4e69;
     font-family: Arial, Helvetica, sans-serif;
  }
  svg {
     background-color: white;
     border-radius: 10px;
     padding: 10px;
  }
  #title {
     font-size: 20px;
  }
   #tooltip {
     margin-top: 20px;
     font-size: 18px;
     color: white;
  }
  rect {
     fill: #4a4e69;
    
  }
  rect:hover {
     fill: #f2e9e4
  }
```
With `html` and `css` out of the way, lets get to why you are here.

##The JavaScript
###1. Defining some variables: 
In your `script` we are going to define the variables that are going to be used later in the creation of our bar chart, add the code below in your `script`

```javascript
let values = [];

let heightScale;
let xScale;
let xAxisScale;
let yAxisScale;

let width = 800;
let height = 600;
let padding = 40; 
```
The `values` array is empty because later on it's going to contain the data we are going to get from an api.

The `heightScale` is what will determine the height of each of the bars as it cannot have say a constant value like 50 or 60, if that were so then all bars will have the same height irrespective of the value it represents. 

The `xScale` will determine where the bars are placed horizontally in the chart.

The `xAxisScale` will be use to create the `xAxis` at the bottom while the `yAxisScale` will be used to create to the `yAxis` on the left, you can think of it as the x and y axis on a graph.

The `width`, `height` and `padding` are attributes that are going to be added to the `svg` .

###2. Select the `svg`: 
To select an element in normal javascript we usually go through the route of `document.getElementById()` or `document.querySelector()` or whatnot but because we are using d3 here we can just do this
```javascript
let svg = d3.select('svg')
```
and that's it, you have selected the `svg` element in your html and saved it in a variable called `svg`, if there are more than one `svg` in the document then it will return the first element, you can also select by the `id` or `class` of the element for example
```javascript
let svg = d3.select('#chart')
```
both are valid ways of selecting an element in d3.

###3. Create functions: 
Now that we have our variables set, we need to create some functions that will be called to do one thing or another in order to create our bar chart.

####I. Let's `drawChart()`: 
The `svg` is where all other element are going to sit like the `xAxis`, `yAxis` and the `bars`. For that we need to define the `width` and `height` of it using a function. 
Add the code below after selecting your `svg`
```javascript
  let drawChart = () => {
        svg.attr('width', width)
           .attr('height', height)
   }

```
The above code is a simple arrow function that takes the `svg` variable and adds an attribute to it. The d3 `.attr()` takes two argument first is the attribute we want to add which in this case is the `width` and `height` and second is the value.

####II. Let's `fetch()` some data: 
Before we proceed creating the bar chart, we need to fetch the `data` that the bar chart will be created with, now freeCodeCamp gave us an api to work with.
```javascript
https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json
```
You can open this up in a new tab to see how the data is structured.
To fetch this data I am going simply use javaScript's own `fetch` method, add the below code after the `drawChart()` 

```javascript
  fetch('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json') //get the data
      .then(res => res.json()) //convert the data to json
      .then(data => { 
       values = data.data //save the data in the values array
       console.log(values) //just so you can see that values now contains the data
 });

```javascript
The code above simply gets the data and stores them in the `values` array, if you check your console you should see this

![data from an api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ueo57uofj4qhmbhw5ond.png)

Lastly within the `fetch` method we want to call the functions we'll be creating later, add the code below to your `fetch`

```javascript
drawChart()
generateScales()
drawBars()
generateAxes()
```
You should have something like this

```javascript
  fetch('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json') 
      .then(res => res.json()) 
      .then(data => { 
       values = data.data;
       console.log(values) 

       drawChart()
       generateScales()
       drawBars()
       generateAxes()
 });

```

####III. Let's `generateScales()`: 
We need to create the scales that the axis are going to use, add the below code after your `drawChart()`

```javascript
let generateScales = () => {
    heightScale = d3.scaleLinear()
                    .domain([0, d3.max(values, (item) => {
                    return item[1]
                    })])
                   .range([0, height - (2 * padding)])

    xScale = d3.scaleLinear()
              .domain([0, values.length - 1])
              .range([padding, width - padding]) 
}
```javascript
First, we defined the `heightScale` by calling `d3.scaleLinear()` function, after that we had to call a `domain`, a `domain` is simply telling it that the `values` we are expecting here have a maximum and minimum, take the code below for an example

```javascript
const arr = [100, 200, 300, 400, 500, 600];

```
In the array above the minimum number present is 100 and the maximum is 600. That is essentially what the domain does, it gets the min and max of the data. 

But the domain can't magically know the min and max of the data, we have to tell it by opening an array in it and specifying the lowest value as the first item and highest value as the second.

```javascript
.domain([0, ])
```
Since we are working with GDP data we know for sure that the lowest can only be 0 else we'd have worked with `d3.min()` to find the lowest value in the array.

Now that the min of the domain is set we have to find the max as the second item in the domain array, now in real life one cannot always know the exact figure and it maybe subject to change in the future, that's why the below code is added as the second item in the array, it takes the values array and maps over each item before returning the highest value of the second index.
```javascript
d3.max(values, (item) => {
 return item[1]
})
```
That's how we have this 
```javascript
.domain([0, d3.max(values, (item) => {
            return item[1]
})])
```
Now to the `range`, it also takes in an array with two values, the first is the min then the max, take the code below for an example

```javascript
// our data
const arr = [100, 200, 300, 400, 500, 600];

```
If you think about it how can one represent a huge value like 600 should the height then be 600px tall? Then what happens when we work in thousands or millions how to be present the height of that bar representing the value? That is what the `range` does

```javascript
const arr = [100, 200, 300, 400, 500, 600];
// we can say, to represent this data the range can be;
.range([10, 100])
```
What this does is that the value of 100 will be represented at the height of 10 because it is the min and 600 will be represented at the height of 100 because it is the max, likewise the rest of the value's height will be calculated within the range we have set for it.

I hope this code now makes more sense to you
```javascript
.range([0, height - (2 * padding)])

```
It sets the min range to 0 and the max to the height of the svg take away the padding on both sides so there's a bit of a space. 

Then we defined our xScale similarly
```javascript
xScale = d3.scaleLinear()
            .domain([0, values.length - 1])
            .range([padding, width - padding]) 
```
This scale is used to position the `xAxis` which would sit horizontally at the bottom.

Moving on, in the `generateScale()` right after the `xScale` add the following code.
```javascript
 let datesArr = values.map(item => {
    return new Date(item[0]);
    })  
   console.log(datesArr) // so you can see

   xAxisScale = d3.scaleTime()
                  .domain([d3.min(datesArr), d3.max(datesArr)])
                  .range([padding, width - padding])
  yAxisScale = d3.scaleLinear()
                 .domain([0, d3.max(values, (item) => {
                            return item[1]
                        })])
                 .range([height - padding, padding])
```
Now in order to have this bottom axis paired with dates below

![x axis of a bar chart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2r1p7o1jkksaahjzrdfa.png)

We need to convert the string date from our data and change it to an actual date, if you studied the data you will see that the date for each value is at the index of 0


![data from an api](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x3nwjvnze5bgw0ssgzb9.png)

For this conversion to happen we created a `datesArr` to hold the newly converted dates

```javascript
 let datesArr = values.map(item => {
    return new Date(item[0]);
  })  
```
If you `console.log(datesArr)` you'll find our array containing the newly converted dates

![an array of converted dates](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/su9a4savfm44kmbur5a1.png)

After that, we defined the `xAxisScale` by calling the `d3.scaleTime()` because the values we are working with are dates, then we called the `domain` and `range`, likewise for the `yAxisScale`.

####IV. Let's `generateAxes()`:
Now that the hard part which is `generateScales()` is out of the way it's time to start with our both of our axis, add this code below the `generateScales()` function.

```javascript
 let generateAxes = () => {
       let xAxis = d3.axisBottom(xAxisScale)

       svg.append('g')
       .call(xAxis)
       .attr('id', 'x-axis')
       .attr('transform', `translate(0, ${height - padding})`);

       let yAxis = d3.axisLeft(yAxisScale);

       svg.append('g')
       .call(yAxis)
       .attr('id', 'y-axis')
       .attr('transform', `translate(${padding}, 0)`)
    }

```
Within the function above we defined an `xAxis` representing the `x` of our graph, before calling it with the `d3.axisBottom(xAxisScale)` method and giving it the `xAxisScale` 

Now to display this on screen, a `g` element is appended the already selected `svg` then we called the `xAxis` which is essentially telling it to draw the `xAxis` within the `g`

```javascript
 let xAxis = d3.axisBottom(xAxisScale)

       svg.append('g')
       .call(xAxis)
       .attr('id', 'x-axis')
       .attr('transform', `translate(0, ${height - padding})`);
```
 An `id` attribute is added to the `g` and a `transform` attribute which if not added would naturally sit at the top of the svg, try taking away the `transform` attribute and you will have something like this

![a bar chart containing the x axis only](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rz14l7kzo89a7wdsmxji.png)

After that the `yScale` is defined and called with the `d3.axisLeft()`, just like we did for the `xAxis`, another `g` is appended to the `svg` then we told it to draw the `yAxis` within it, then an `id` attribute is given to it and a `transform` attribute to position it better within the `svg`, try taking the `transform` out to see its natural position.
```javascript
   let yAxis = d3.axisLeft(yAxisScale);

       svg.append('g')
       .call(yAxis)
       .attr('id', 'y-axis')
       .attr('transform', `translate(${padding}, 0)`)
```
So in total we have something like this

![a bar chart with both x and y axis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd9mir12xtl6wwe8bxxf.png)


####V. Let's `drawBars()`: 
Finally we are going to be filling the graph with bars representing its data, add the code below after the `generateScales()` 

```javascript
       let drawBars = () => {
        svg.selectAll('rect')
            .data(values)
            .enter()
            .append('rect')
            .attr('class', 'bar')
            .attr('width', (width - (2 * padding)) / values.length)
            .attr('data-date', (item) => {
                return item[0];
            })
            .attr('data-gdp', (item) => {
                return item[1];
            })
            .attr('height', (item) => {
                return heightScale(item[1])
            })
            .attr('x', (item, index) => {
                return  xScale(index)
            })
            .attr('y', (item) => {
                return (height - padding) - heightScale(item[1])
            })
           
```
Breaking it down 
```javascript
  svg.selectAll('rect')
            .data(values)
            .enter()
            .append('rect')
```
First we selected all `rect` or rectangle within the svg whether they exist or not then we bind the `rect` with `values` with `.data(values)` to associate each `rect` with each `value`, then the `.enter()` is called to tell it what to do if no rectangles are found and right after that the `.append(rect)` is called which would create new rectangle for each value.

 Then we added some attributes like `width`, `height`, `class`, 'x' and 'y' for proper display and positioning of each bar, other attributes like `data-date` and `data-gdp` are for the fulfillment of freeCodeCamp's test but it's good practice to add them too.

Again try taking away the `x` or `y` attribute to see how they would be positioned by default.

```javascript
  .attr('class', 'bar')
  .attr('width', (width - (2 * padding)) / values.length)
  .attr('data-date', (item) => {
            return item[0];
         })
   .attr('data-gdp', (item) => {
            return item[1];
    })
    .attr('height', (item) => {
            return heightScale(item[1])
     })
    .attr('x', (item, index) => {
            return  xScale(index)
     })
    .attr('y', (item) => {
            return (height - padding) - heightScale(item[1])
}
```
You should have something similar to this now

![A bar chart displaying data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k53gy482j12liqalrcu1.png)

###VI. The Tooltip: 
Now that we have our bar chart displayed the last thing is to add a tooltip to display some information about the particular bar hovered on, now for this I have created a very simple tooltip, add the code below at the top of your `drawBars()` before the `rect` selection

```javascript
  let tooltip = d3.select('body')
                   .append('div')
                   .attr('id', 'tooltip')
                    .style('visibility', 'hidden')
                    .style('width', 'auto')
                    .style('height', 'auto')

```
What this does is to select the `body` add a `div` to it and give it an attribute of `id` which is set to `tooltip`, right after that, we gave a few styles to it like setting the `visibility` to hidden by default, setting the `width` and `height` to `auto`.

Now that we have our `tooltip` the next thing is displaying the necessary information when hovered upon, for this, d3 has a method for us which is the `.on()`, add the code below right after the `y` attribute of the `svg`, think of it as adding an event listener to the bars.

```javascript
 .on('mouseover', (item, index) => {

     tooltip.style('visibility', 'visible')
            .html(`Date: ${index[0]} Data: <b>${index[1]}</b>`)
            .attr('data-date', index[0])
        
  })
           
 .on('mouseout', (item) => {
     tooltip.style('visibility', 'hidden')
   })

```

Using the `.on()` method the first argument takes the name of the the event while the second is the function that should run when the event happens, this function takes the `item` and the `index` of the `item`. Inside the function the tooltip `visibility` style is set back to `visible` when the mouseover happens and is given some `html` which include the date and value of the particular bar hovered upon. a `data-date` attribute is also given to the tooltip whose value is the date of the bar hovered upon.

```javascript
 .on('mouseover', (item, index) => {

     tooltip.style('visibility', 'visible')
            .html(`Date: ${index[0]} Data: <b>${index[1]}</b>`)
            .attr('data-date', index[0])
        
  })
```
Now for when the mouse leaves we have this line of code below, which sets the `visibility` of the tooltip back to `hidden` that way it toggles on and off based on the event happening.

```javascript
 .on('mouseout', (item) => {
     tooltip.style('visibility', 'hidden')
   })
```
###The Full code

```javascript
let values = [];

let heightScale;
let xScale;
let xAxisScale;
let yAxisScale;

let width = 800;
let height = 600;
let padding = 40;

let svg = d3.select('#canvas');

// the drawChart function

let drawChart = () => {
        svg.attr('width', width)
            .attr('height', height)
 }

// the generateScales function

let generateScales = () => {
          heightScale = d3.scaleLinear()
                .domain([0, d3.max(values, (item) => {
                    return item[1]
                })])
                .range([0, height - (2 * padding)])

        xScale = d3.scaleLinear()
            .domain([0, values.length - 1])
            .range([padding, width - padding]) 
            
       let datesArr = values.map(item => {
           return new Date(item[0]);
       })  
      console.log(datesArr)

       xAxisScale = d3.scaleTime()
                      .domain([d3.min(datesArr), d3.max(datesArr)])
                       .range([padding, width - padding])

       yAxisScale = d3.scaleLinear()
                       .domain([0, d3.max(values, (item) => {
                            return item[1]
                        })])
                       .range([height - padding, padding])
   }

// the drawBars function

  let drawBars = () => {
      let tooltip = d3.select('body')
                       .append('div')
                       .attr('id', 'tooltip')
                       .style('visibility', 'hidden')
                       .style('width', 'auto')
                       .style('height', 'auto')

      svg.selectAll('rect')
           .data(values)
           .enter()
           .append('rect')
           .attr('class', 'bar')
           .attr('width', (width - (2 * padding)) / values.length)
           .attr('data-date', (item) => {
                return item[0];
            })
           .attr('data-gdp', (item) => {
                return item[1];
            })
           .attr('height', (item) => {
                return heightScale(item[1])
            })
           .attr('x', (item, index) => {
                return  xScale(index)
            })
           .attr('y', (item) => {
                return (height - padding) - heightScale(item[1])
            })
           .on('mouseover', (item, index) => {

                tooltip.style('visibility', 'visible')
                .html(`Date: ${index[0]} Data: <b>${index[1]}</b>`)
                .attr('data-date', index[0])
        
            })
           
          .on('mouseout', (item) => {
                tooltip.style('visibility', 'hidden')
           })
    }

     // the generateAxes function

      let generateAxes = () => {
       let xAxis = d3.axisBottom(xAxisScale)

       svg.append('g')
       .call(xAxis)
       .attr('id', 'x-axis')
       .attr('transform', `translate(0, ${height - padding})`);

       let yAxis = d3.axisLeft(yAxisScale);

       svg.append('g')
       .call(yAxis)
       .attr('id', 'y-axis')
       .attr('transform', `translate(${padding}, 0)`)
  }

// fetching the data

      fetch('https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json')
      .then(res => res.json())
      .then(data => {

       values = data.data
       console.log(values)

       drawChart()
       generateScales()
       drawBars()
       generateAxes()
 });
```


##Conclusion

Congratulations! You have just created a bar chart. The code above shows how HTML, SVG, CSS, and JavaScript can work together to create a stunning data graph. Understanding how each of the different elements works and their functions is crucial for developing more complex data graphs, if you want to know more about d3 there are many tutorials and online resources that can have you have better understanding of it.
 
This can be a lot to grasp but don't be too discouraged, if you want me to make a detailed tutorial about d3 all you need to do is comment down below.

Thanks for reading!