BookmarkSubscribeRSS Feed

How to create a basic bar chart in SAS Visual Analytics with #D3Thursday

Started ‎09-13-2018 by
Modified ‎10-03-2018 by
Views 2,864

85455_banner_D3Thursday.png

This #D3Thursday post includes:

  • Comparing JavaScript charting libraries
  • Getting started with D3.js
  • Creating a basic bar chart

The Other Guys

I won’t spend much time comparing D3.js with competing JavaScript charting libraries since there are dozens of blog and forum posts dedicated to this purpose already.

 

Three of D3.js’ major competitors are Highcharts, Google Charts, and C3.js. All three perform similar functions. They are high-level charting libraries with an easy learning curve. They attempt to make it as easy as possible to create dynamic charts.

 

Right off the bat we can eliminate Highcharts since it is not free for commercial use.

 

Both Google Charts and C3.js are free for commercial use, but there is little reason to use them in a SAS Visual Analytics (VA) Data-Driven Content (DDC) object because they add little value over the existing VA chart objects.

 

D3.js, on the other hand, is a low-level data manipulation library that gives the user absolute control over their creations. This means D3.js will allow us to create some truly unique visualizations.

 

For more information on how these charting libraries compare, see the links at the bottom of this post.

 

A Short Tutorial

Unfortunately, this also means D3.js is more difficult to learn than the other libraries. For this reason, we will start slow with a tutorial on D3.js.

 

Selections

Everything in D3.js revolves around selections. As the name implies, selections allow you to select elements in your HTML document. D3.js selections use standard CSS selectors just like you would expect.

 

To perform a selection you can use the select or selectAll functions. As the names imply, the select function only returns a single element while the selectAll function returns all elements that match the selector.

 

The following code selects all elements with the class “data-bar” within the selected SVG element.

d3.select(“svg”).selectAll(“.data-bar”);

After selecting an object you can use the attr and style functions to manipulate the selected object(s). These functions can be chained together to make multiple changes to the selection at once.

 

The following code changes the fill color and opacity of the selection made above.

d3.select(“svg”).selectAll(“.data-bar”)
  .attr(“fill”, “red”)
  .style(“opacity”, .5);

 

D3 Lifecycle

The true power of D3 comes from joining data to selections. Joining data to your selections allows you to change elements based on the content of your dataset. This can be extremely powerful when creating dynamic charts.

 

When a data join is created, three selections are created.

  1. The Update Selection – Elements with data bound to them
  2. The Enter Selection – Data for which there are no elements
  3. The Exit Selection – Elements for which there are no data

Let’s look at an example to explain this concept better. First, let’s add some element to an SVG with a data join.

 

The following code creates a data join on the array data and adds a circle element to the SVG for each entry in the data array.

const data = [{category: “red”, freq: 5}, {category: “blue”, freq: 10}];
const circle_data_join = d3.select(“svg”).selectAll(“circle”).data(data, function(d) { 
return d.category;
}); // Append entered elements circle_data_join.enter()
.append(“circle”) .attr(“cx”, 50) .attr(“cy”, 50) .attr(“r”, function(d) { return d.freq; }) .attr(“fill”, function(d) { return d.category; }) .style(“opacity”, .25);

Notice how a callback function was used to assign the radius and fill color according to the values of the datum being joined to the elements. This call back can optionally be passed the arguments d or i, which represent the data joined to the selected element and the index of the selected element, respectively.

 

Also notice how a callback function is used in the data join step to key the data to the joined elements. This ensures that if the value of category is changed in the bound dataset, the changed entry will be treated as a new element rather than as an updated element.

 

Let’s look at an example of a changed dataset to clarify this. Imagine here that the above example has already been executed, creating a red circle and a blue circle.

const new_data = [{category: “green”, freq: 5}, {category: “blue”, freq: 15}];
circle_data_join = d3.select(“svg”).selectAll(“circle”).data(new_data, function(d) { 
return d.category;
}); // Update changed elements circle_data_join .attr(“r”, function(d) { return d.freq; }) .attr(“fill”, function(d) { return d.category; }); // Append entered elements circle_data_join.enter()
.append(“circle”)
.attr(“cx”, 50) .attr(“cy”, 50) .attr(“r”, function(d) { return d.freq; }) .attr(“fill”, function(d) { return d.category; }) .style(“opacity”, .25); // Remove exited elements circle_data_join.exit()
.remove();

Since our data was keyed by category, this change to the dataset will result in the radius for the blue circle being updated, the red circle being removed, and the green circle being entered. Note that if our data was not keyed, this change to the dataset would result in the radius for the blue circle being updated and the fill for the red circle being updated to green. We will discuss the reasoning for keying our data in more detail in a future blog post.

 

I know this was a very brief introduction to D3.js, but fortunately D3.js is documented excellently and has a large user base. More information on D3.js can be found using the links at the bottom of this post.

 

A Quick Example

Now let’s take what we’ve learned about D3.js to create a basic bar chart.

 

The Skeleton

First let’s look at the overall file structure.

<!DOCTYPE html>
<html>
<head>
  <!-- Import D3.js -->
  <script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
</head>
<body>
<style type="text/css">
  html, body, svg {
    overflow: hidden;
    margin: 0px;
    width: 100%;
    height: 100%;
  }
</style>

<script>
"use strict";

document.addEventListener("DOMContentLoaded", function(e) {
  // D3 Code Here
});
</script>
</body>
</html>

All the files we create will share a similar beginning structure to this. We could separate the JavaScript, CSS, and HTML into separate files, but we will keep the JavaScript and CSS embedded in the HTML for simplicity.

 

Notice that we explicitly set the overflow, margin, width, and height styles of the html, body, and SVG elements. This ensures that our visualization expands to the size of its container.

 

Declaring Global Variables

Next, let’s set up all the global variables we will be using to create our visualization.

// Static data variables
const SVG_ID = "simple-bar"; // ID of SVG element
const DATA = [["Apple", 50], ["Banana", 35], ["Mango", 40], ["Orange", 20]]; // Hard coded data in same format SAS VA sends data

// Static dimension variables
const EDGE_PADDING = 10; // Padding between end of axes and edge of SVG
const Y_AXIS_WIDTH = 30; // Width allocated for y-axis
const X_AXIS_HEIGHT = 30; // Height allocated for x-axis

// Dynamic dimension variables
let WIDTH = window.innerWidth; // Width of SVG element
let HEIGHT = window.innerHeight; // Height of SVG element
let CHART_WIDTH = WIDTH - Y_AXIS_WIDTH - EDGE_PADDING; // Width of chart area for bars
let CHART_HEIGHT = HEIGHT - X_AXIS_HEIGHT - EDGE_PADDING; // Height of chart area for bars

// Selection and d3 variables
let SVG; // SVG selection
...
let DATA_BARS; // Data bars data-join

Declaring these variables as globals, especially in this example, isn’t strictly necessary. I tend to declare all my variables for two reasons:

 

First, doing so will reduce the number of variable declarations in future examples. As you will see in weeks to come, we will often want to make the same selection multiple times throughout our code. Storing these selections as global variables means you won’t need to declare the same selection variable multiple times.

 

Secondly, declaring your globals and writing comments for them before you start writing code forces you to enumerate all the elements you will need for a visualization. This ensures you have a good understanding of your visualization before you start coding.

 

Static Elements

Now let’s create the static elements we need for our visualization.

// Append svg and save reference
d3.select("body")
.selectAll("#" + SVG_ID)
.data([DATA])
.enter()
.append("svg") .attr("id", SVG_ID); SVG = d3
.select("#" + SVG_ID) .attr("width", WIDTH) .attr("height", HEIGHT); // Append chart area group and save reference SVG.selectAll(".g-chart-area")
.data([DATA])
.enter()
.append("g") .classed("g-chart-area", true) .attr("transform", "translate(" + (Y_AXIS_WIDTH) + ", " + (EDGE_PADDING) + ")"); G_CHART_AREA = SVG.select(".g-chart-area");

Since the SVG element was already appended to the DOM we only need to set it’s width and height, and save the selection.

 

For the group elements, we had to append the group and then save a selection. Notice that we saved each selection after appending the group element. Can you think of another way to do this?

 

Would the following code accomplish the same thing (ignoring the missing attr callback)?

G_CHART_AREA = SVG.selectAll(“.g-chart-area”)
.data([DATA])
.enter()
.append(“g”) .classed(“g-chart-area”, true);

What about this code?

GROUP_SELECTION = SVG
.append(“g”) .classed(“g-chart-area”, true);

In this example both of the above methods would work as well, but what would happen if we ran this code more than once?

 

If we were to run the first method a second time, then GROUP_SELECTION would be assigned to an empty selection since the group has already been appended. This would break any references to GROUP_SELECTION further down in the code.

 

If we were to run the second method a second time, then another group element would be appended to the document causing a duplication of elements. Additionally, all elements entered within this group would also be duplicated causing performance degradation.

 

As you will see in next time’s example, we will need to call this code anytime the window resizes to reposition to resize chart elements appropriately.

 

Dynamic Elements

Finally, let’s create some data-dependent elements.

// Create x scale
X_SCALE = d3.scaleBand()
  .rangeRound([0, CHART_WIDTH])
  .padding(0.1)
  .domain(
DATA.map(function(d) {
return d[0];
})
); ... // Create x axis X_AXIS = SVG.selectAll(".x-axis").data([DATA]); X_AXIS.enter().append("g")
.classed("x-axis", true) .attr("transform", "translate(" + (Y_AXIS_WIDTH) + ", " + (HEIGHT-X_AXIS_HEIGHT) + ")") .call(d3.axisBottom(X_SCALE)); ... // Create bars for each country + year DATA_BARS = G_CHART_AREA.selectAll(".data-bar").data(DATA); DATA_BARS.enter().append("rect") .classed("data-bar", true) .attr("x", function(d) {
return X_SCALE(d[0]);
}) .attr("y", function(d) {
return Y_SCALE(d[1]);
}) .attr("width", function() {
return X_SCALE.bandwidth();
}) .attr("height", function(d) {
return CHART_HEIGHT - Y_SCALE(d[1]);
});

Luckily, we don’t have to create every element that encompasses the axes when using D3. All we have to do is setup a scale as shown with the appropriate range and domain, and then let D3 automatically generate our axes.

 

For more information on how D3 scales or axes work check out the link for the D3 API at the bottom of this post.

 

Some Styling

Finally, let’s add a little styling to our chart to make it more palatable.

.data-bar {
  fill: steelblue;
}

.data-bar:hover {
  fill: lightsteelblue;
}

The result is the following bar chart. 

 

Basic D3 Bar ChartBasic D3 Bar Chart

The complete code for this example as well as a live demo can be found using the GitHub links at the bottom of this post.

 

Additional Resources

Next Post

In the next #D3Thursday post, we will expand on this example to make it more dynamic by adding selectable elements, dynamic resizing, and enter/exit/update transitions. 

 

Remember to follow the D3 Thursday article series on the SAS Communities Library and the #D3Thursday Twitter hashtag. Comment below or Tweet your questions and comments!

Version history
Last update:
‎10-03-2018 08:22 AM
Updated by:
Contributors

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

Free course: Data Literacy Essentials

Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning  and boost your career prospects.

Get Started

Article Labels
Article Tags