D3.js — How to Make a Beautiful Bar Chart With The Most Powerful Visualization Library | by Dario Radečić | Towards Data Science

Choosing a go-to data visualization library is harder than it seems at first. There are so many to choose from, especially if you factor in different languages. I’ve spent most of my time with Plotly and Matplotlib in Python, but today we’ll try something different — D3.js, written in — you’ve guessed it — Javascript.
Let’s get a couple of things out of the way. First, I’m not a JavaScript expert, so there’s no need to point out if some things weren’t written optimally. Second, this library is verbose as they come, and it took me almost 100 rows of code to produce a decent-looking bar chart.
That’s isn’t necessarily a bad thing, as you can customize the hell out of every visualization. Before proceeding further, let’s take a quick look at the end result:
This color-changing happens on hover, so no, it’s not a glitch of any sort. The visualization is fairly simple but requires a decent amount of work. Let’s talk a little bit about D3 before proceeding with the code.
D3 stands for Data Driven Documents. Here’s a statement from the official documentation page:
D3 helps you bring data to life using HTML, SVG, and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.
Awesome! So, what’s the point of using it?
One feature I like in particular is that D3 outputs SVGs instead of PNGs, which Matplotlib outputs. It’s not my goal to confuse you with 3 letter acronyms, so I won’t dive deep into SVG and PNG differences.
Here’s the only thing you should now (to start with) — SVGs are used to draw vector graphics, which means there are no pixels, which further means we don’t lose quality on zooming and scaling. That’s not the case with PNGs.
Consider this image as an example:
The image says it all.
Let’s now make our chart. Be aware, it will be a lot of work.
First things first — the data. We’ll store some dummy data in the JSON format. I’ve named mine sales.json and it looks like this:
[  {“Period”: “Q1–2020”, “Amount”: 1000000},  {“Period”: “Q2–2020”, “Amount”: 875000},  {“Period”: “Q3–2020”, “Amount”: 920000},  {“Period”: “Q4–2020”, “Amount”: 400000}]
And that’s it for the data. Next, we need the HTML file. Don’t worry if you don’t know HTML, as it’s fairly straightforward, and we won’t spend much time on it.
For simplicity's sake, I’ve decided to embed CSS and JavaScript to the HTML file, but feel free to separate them if you wish. My file is named barchart.html:
<!DOCTYPE html><html lang=”en”><head>  <meta charset=”UTF-8">  <meta name=”viewport” content=”width=device-width, initial-scale=1.0">  <title>D3.JS Bar Chart</title></head>  <style>    rect.bar-rect { fill: #189ad3; }    rect.bar-rect:hover {       fill: #107dac;      transition: all .2s;    }  </style><body>  <div class=”canvas”></div>  <script src=”https://d3js.org/d3.v6.js"></script>  <script>    // Everything else will go here  </script></body></html>
This is a starter template. Don’t worry about the CSS, I’ve used it just to color the bars in their normal state and in the hover state.
We can now start with the chart itself!
Let’s get started with the real deal. Our chart section needs some dimension info — like width and height. Also, we don’t want to make the chart occupy the entirety of the chart section, so we’ll need to add some margins on the top, right, bottom, and left.
To do so we’ll set up a couple of constants:
const width = 1000;const height = 600;const margin = {‘top’: 20, ‘right’: 20, ‘bottom’: 100, ‘left’: 100};const graphWidth = width — margin.left — margin.right;const graphHeight = height — margin.top — margin.bottom;
I think those are pretty much self-explanatory. If not, here’s a quick clarification:
The entire chart area will occupy 1000 x 600 pixels
The chart itself will be inside the chart area and will have margins on all sides
Margins on the bottom and on the left are larger because we will put the axes there
As simple as that. Let’s proceed.
Okay, we’re ready to proceed with the fun part. We need to somehow select the div with the class of canvas, as our chart will be stored there. In it, we’ll create a svg (remember that D3 outputs SVGs) and set its height and width to the ones declared earlier.
Next, we can insert the graph element into the svg element, and set it dimensions to ones declared earlier. We don’t want it to start at the top left corner of the svg, so we need to translate it accordingly.
Finally, we can declare constants for both X and Y-axis groups (don’t worry about those for a moment).
Here’s the code:
const svg = d3.select(‘.canvas’)  .append(‘svg’)  .attr(‘width’, width)  .attr(‘height’, height);const graph = svg.append(‘g’)  .attr(‘width’, graphWidth)  .attr(‘height’, graphHeight)  .attr(‘transform’, `translate(${margin.left}, ${margin.top})`);const gXAxis = graph.append(‘g’)  .attr(‘transform’, `translate(0, ${graphHeight})`);const gYAxis = graph.append('g')
If you were to open the HTML file now there’s nothing you would see, but that doesn’t mean nothing is happening. Just pop up the console and go to the elements inspector. Here’s what you should see:
Awesome! Let’s finish this thing next.
And now the moment you’ve been waiting for. We still have a lot of things to do, so let’s get right to it.
First of all, we need to read our JSON data somehow. Then, we’ll declare scales in constants x and y respectively, and those are there to ensure that individual bars don’t overflow the svg container by accident.
Next, we need to make a rect element (rectangle) for every entry we have in the dataset. Each rectangle has its height and width, X and Y values, and we’ve also added a custom class to it, just so it’s easier to style with CSS.
Pretty much the same needs to be done afterward, after the enter function, and after that we can set up the axes as we wish.
Our chart will have only 5 ticks on the Y-axis, and the values on the same axis will be formatted as currency. And yeah, axis label sizes are set to 14.
It’s a lot of code to write, so take your time to understand what each part does:
d3.json(‘sales.json’).then(data => {  const y = d3.scaleLinear()    .domain([0, d3.max(data, d => d.Amount)])    .range([graphHeight, 0]);  const x = d3.scaleBand()    .domain(data.map(item => item.Period))    .range([0, 500])    .paddingInner(0.2)    .paddingOuter(0.2);  const rects = graph.selectAll(‘rect’)    .data(data);  rects.attr(‘width’, x.bandwidth)    .attr(‘class’, ‘bar-rect’)    .attr(‘height’, d => graphHeight — y(d.Amount))    .attr(‘x’, d => x(d.Period))    .attr(‘y’, d => y(d.Amount));  rects.enter()    .append(‘rect’)    .attr(‘class’, ‘bar-rect’)    .attr(‘width’, x.bandwidth)    .attr(‘height’, d => graphHeight — y(d.Amount))    .attr(‘x’, d => x(d.Period))    .attr(‘y’, d => y(d.Amount));  const xAxis = d3.axisBottom(x);  const yAxis = d3.axisLeft(y)    .ticks(5)    .tickFormat(d => `USD ${d / 1000}K`);  gXAxis.call(xAxis);  gYAxis.call(yAxis);  gXAxis.selectAll(‘text’)    .style(‘font-size’, 14);   gYAxis.selectAll(‘text’)    .style(‘font-size’, 14);});
If you were to refresh the HTML page now, you would see a chart presented on it. Not only that, but we’ve also added different color on bar hover. Nice!
That’s it for today. Let’s wrap things up in the next section.
D3.js requires a lot of code — no arguing there. But that’s just what makes it so special — the ability to customize absolutely everything. We’ve only scratched the surface here, and options to tweak are endless.
Let me know if you’ve enjoyed this piece, as I intend to do an entire series on the library.
Thanks for reading.
Loved the article? Become a Medium member to continue learning without limits. I’ll receive a portion of your membership fee if you use the following link, with no extra cost to you.