Let's Make a Bar Chart with SVG
This tutorial is Part 2 of a series that begins with the introductory Let's Make a Bar Chart lesson. In this followup, we'll switch from using basic DIV elements in a web page to using SVG (Scalable Vector Graphics). This will get us one big step closer to the full power of D3 to create highly graphical and interactive data visualizations.
Before you begin, you should have a basic understanding of what SVG provides. There are online tutorials for SVG (e.g., here and here) that can be good background for what we'll discuss on here.
For this lesson, the following key ideas are most important:
- Browsers can render SVG "scenes" to create pictures embedded within a web page.
- A "scene" in SVG is defined as a hierarchy of elements as part of an HTML page.
- Browsers allow SVG scenes to be defined via a
<svg>
element. Other SVG elements can be added as children of this element to define a scene. - SVG defines several types of elements including rect (for rectangles), circle, ellipse, line, polyline, polygon (like polyline, but creates a closed shape), and a general path for arbitrary shapes including curves.
An SVG Element as a Container
In our previous lesson, we created a bar chart by appending a set of DIVs (one for each bar) to a parent DIV that was the overall container for the bar chart. In this example, we'll do something very similar. We'll add SVG rectangles (rect elements) to a parent SVG element. In the code snippet below, you'll see we now have a SVG element of class "chart." You can also see that we've assigned a height and width to the SVG element. In this case, we'll create a chart that is 300x500 pixels in size.
<html>
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg class="chart" height="300" width="500">
</svg>
<script>
// This is the data array which will be represented as a bar chart.
let data = [4, 8, 15, 16, 23, 42];
</script>
</body>
</html>
Creating a D3 Selection
The next step is exactly the same as it was in our prior lesson. We
<html>
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg class="chart" height="300" width="500">
</svg>
<script>
// This is the data array which will be represented as a bar chart.
let data = [4, 8, 15, 16, 23, 42];
// Select the chart svg which will be the container for the new bar chart
let chart = d3.select(".chart");
</script>
</body>
</html>
Using the D3 Data Join to Add the Bars
Like last time, we'll use D3's x
x, y
, width
, and height
. We also need to use
the right CSS style attributes for SVG which differ a bit from how we style DIVs in traditional HTML. Finally, SVG
rect elements can't contain text. So we'll have to add text labels separately. For now, we just skip the text labels
and focus on the bars. Otherwise, you should see this looks almost identical to what we had in our previous lesson.
Here is the updated example using SVG rects to create a basic bar chart.
<html>
<body>
<script src="https://d3js.org/d3.v7.min.js"</script>
<svg class="chart" height="300" width="500">
</svg>
<script>
// This is the data array which will be represented as a bar chart.
let data = [4, 8, 15, 16, 23, 42];
// Select the chart svg which will be the container for the new bar chart
let chart = d3.select(".chart");
// Define some layout parameters.
let bar_height = 40;
let bar_spacing = 2;
// Perform the data join and add the new rectangles
chart.selectAll("rect")
.data(data).join("rect")
.style("fill", "steelblue")
.attr("x", 0)
.attr("y", (d,i) => (bar_height+bar_spacing)*i))
.attr("height", bar_height)
.attr("width", d => d);
</script>
</body>
</html>
Here is the output produced by the code above. We've added a think black border around the SVG element to let you see how it takes up 300x400 pixels, but the chart appears only in the top left. To fix this, we'll want to use scales.
Scales for the X and Y Axes
In this version of the chart, we have two axes: the x
axis represents the value of the number;
the y
axis represents the index of the number in the array. We use the y axis to position the
bars to a fixed height of 20 in the previous step of this example. Meanwhile, we used to raw data value to
set the width of each bar. Now we're going to use D3 scales to make the charts take up the full height and width
of the SVG element. Let's create the two scales.
let x = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, 500]);
let y = d3.scaleLinear()
.domain([0,data.length])
.range([0,300]);
Now we can use the x
scale to map data values to the range 0-500 on the x axis. For
the y axis, we can determine the height of a single bar by calculating the difference between y(1) and y(0) (i.e., the
y axis positions for the first two bars. We further subtract the bar_spacing
variable to leave a
small gap between the bars.
At ths point, all we need to do is insert these scales into our overall web page and use them when setting the
position and width of the rect
s.
<html>
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg class="chart" height="300" width="500">
</svg>
<script>
// This is the data array which will be represented as a bar chart.
let data = [4, 8, 15, 16, 23, 42];
// Define an x and y scale.
let x = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, 500]);
let y = d3.scaleLinear()
.domain([0, data.length])
.range([0, 300]);
// Select the chart svg which will be the container for the new bar chart
let chart = d3.select(".chart");
// Define some layout parameters.
let bar_spacing = 2;
let bar_height = y(1) - y(0) - bar_spacing;
// Perform the data join and add the new rectangles
chart.selectAll("rect")
.data(data).join("rect")
.style("fill", "steelblue")
.attr("x", 0)
.attr("y", (d,i) => y(i))
.attr("height", bar_height)
.attr("width", d => x(d));
</script>
</body>
</html>
Nice! Now we have our chart so far! This version will re-scale as new data items are added to the array so that it will always fit within the 300x500 SVG area. Sweet!
Adding Text Labels
The next step is to add our text labels. Text in SVG has its own element type. We can append these to the bar chart just like we did with the rectangles. We just need to make sure we assign values for the various text attributes that SVG defines for us. We add some style settings to right align the text at the end of the bars. We could also set a class for these text elements and use standard CSS to specify these style attributes.
<html>
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg class="chart" height="300" width="500">
</svg>
<script>
// This is the data array which will be represented as a bar chart.
let data = [4, 8, 15, 16, 23, 42];
// Define an x and y scale.
let x = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([0, 500]);
let y = d3.scaleLinear()
.domain([0, data.length])
.range([0, 300]);
// Select the chart svg which will be the container for the new bar chart
let chart = d3.select(".chart");
// Define some layout parameters.
let text_spacing = 4;
let bar_spacing = 2;
let bar_height = y(1) - y(0) - bar_spacing;
// Perform the data join and add the new rectangles
chart.selectAll("rect")
.data(data).join("rect")
.style("fill", "steelblue")
.attr("x", 0)
.attr("y", (d,i) => y(i))
.attr("height", bar_height)
.attr("width", d => x(d));
// Perform the data join again, this time to create the text labels to show the data values.
chart.selectAll("text")
.data(data).join("text")
.style("fill", "white")
.style("font-family", "sans-serif")
.style("font-size", "16pt")
.style("text-anchor", "end")
.style("dominant-baseline", "middle")
.attr("x", d => x(d)-text_spacing)
.attr("y", (d,i) => y(i)+0.5*bar_height)
.text(d => d);
</script>
</body>
</html>
Now with Vertical Bars
Using SVG for a simple chart like this might seem unnecessary. We could, after all, use the same DIV-based approach we followed in the first lesson in this series to create a similar chart with just a few tweaks to sizes and the layout. The full power of SVG, however, is that we can use arbitrary shapes and arbitrary layout options. For example, a vertical bar chart would be a little more difficult to with plain old DIVs. Let's take a look at a slightly modified version of our SVG-based chart which results in a vertical bar version.
We essentially swap the x
and y
scales in this version, reflecting the fact that a vertical
bar chart uses height to encode values.
<html>
<body>
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg class="chart" height="300" width="500">
</svg>
<script>
// This is the data array which will be represented as a bar chart.
let data = [4, 8, 15, 16, 23, 42];
// Define an x and y scale.
let x = d3.scaleLinear()
.domain([0, data.length])
.range([0, 500]);
let y = d3.scaleLinear()
.domain([0, d3.max(data)])
.range([300, 0]);
// Select the chart svg which will be the container for the new bar chart
let chart = d3.select(".chart");
// Define some layout parameters.
let text_spacing = 4;
let bar_spacing = 2;
let bar_width = x(1) - x(0) - bar_spacing;
// Perform the data join and add the new rectangles
chart.selectAll("rect")
.data(data).join("rect")
.style("fill", "steelblue")
.attr("x", (d,i) => x(i))
.attr("y", d => y(d))
.attr("height", d => 300-y(d))
.attr("width", bar_width);
// Perform the data join again, this time to create the text labels to show the data values.
chart.selectAll("text")
.data(data).join("text")
.style("fill", "white")
.style("font-family", "sans-serif")
.style("font-size", "16pt")
.style("text-anchor", "middle")
.style("dominant-baseline", "hanging")
.attr("x", (d,i) => x(i) + 0.5*bar_width)
.attr("y", d => y(d)+text_spacing)
.text(d => d);
</script>
</body>
</html>
Here is the final chart with vertical bars.
Next Steps...
Want to experiment with an interactive version of this program? Click here!
Ready to learn more? Check out the other lessons on this site using the menus at the top. More lessons will be added over time.