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:

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 select the "chart" SVG element and store this selection in a variable. This is the selection we'll use when appending bars to our 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");
        </script>

    </body>
</html>
        

Using the D3 Data Join to Add the Bars

Like last time, we'll use D3's data join to add bars to our chart. However, this time we'll append SVG rect elements to the chart instead of DIVs. Since we're using rect elements, we need to define the required attributes: xx, 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 rects.

<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.

All content authored by David Gotz. Copyright © 2021-2022. All rights reserved.