Basic Chart - Bar Chart
FREE     Duration: 20:27
Part of Course: Introductory D3 Course
 

Takeaways:

  • You will use the TSV data from the D3js.org website Scatterplot Example to see how a full D3 Scatterplot data visualization is built
  • Notice that the styling is done in the <style> </style> section of the HTML document
  • Notice the D3 Margin Convention
  • Notice the D3 Formatting Function for the percentage formatting
  • Notice the D3 Ordinal Scale with rangeRoundBands for the X-Axis
  • Notice the D3 Linear Scale for the Y-Axis
  • Notice the D3 SVG Axis Component creation and definition of the X-Axis and Y-Axis
  • Notice the tickFormatting for the Y-Axis
  • Notice the D3 Type-specific XHR call - d3.tsv(...)
  • Notice the setting of the domain for the X-Axis and Y-Axis scales
  • Notice the D3 SVG Axis Component instantiation of the X-Axis and Y-Axis
  • Notice the D3 Data Join
  • Notice how D3 helps create visual representations of the data

Transcript:

Basic Chart - Bar Chart

Visual Code Walk Through


[ Image: Bar Char Example ]

We will use the TSV Data from the D3js.org website Bar Chart Example.


[ Image: Data for Bar Chart Example ]

We will save this data into a file called data.tsv

This file will be located in the folder where we will run the python SimpleHTTPServer command.

This file is the one that will be loaded asynchronously using the D3.tsv request functionality.


Let's walk through the D3.js code together.

BROWSER HIGHLIGHT First section

<!-DOCTYPE...........<meta....>

We start at the top of the document.

First is the Document Type Declaration.

This tells the browser how to render the page in a standards compliant mode.

This specific doctype is the correct declaration for HTML5.

Next comes the meta character set.

This sets the character set to UTF-8.

If you are using non-minified D3.js, this is important because the D3 JavaScript file needs this particular type of encoding.


The next section is the style definition of the document.

BROWSER HIGHLIGHT <style>

This is where the CSS style of the HTML and SVG DOM Elements are defined.

This allows for separation of concerns.

The D3 code processes the data and creates the DOM Elements and the CSS styles them.


This CSS defines the style for the body element.

BROWSER HIGHLIGHT body { ...}

Here, the CSS specifies that the font should be 10px tall and should use a sans-serif font.


The next section defines the CSS style for the axis path and axis line html Classes.

BROWSER Highlight .axis.... }

The CSS specifies that no fill is needed.

Sets the stroke to the Hexadecimal number for the HTML Color Black

And also tells the browser that the SVG Content should use the shape-rendering attribute of crisp edges.


The next section defines the style for a DOM element class called "bar"

BROWSER HIGHLIGHT .bar {.....}

This defines the fill to blue for all of these DOM elements.


The next section defines the style for the x-axis path.

BROWSER HIGHLIGHT .x.axis ... }

Here the code is specifying that we do not want to display the path.

Why not just not draw it?

Because the code later uses the D3.axis functionality that auto generates the axis tick marks, spacing and line to connect all the ticks.

So rather than having to figure out how to not have the D3.axis functionality not draw the line that connects all the ticks, this CSS just hides it.


Next we go into the JavaScript Sections of the document.


First, we load the D3.js code from the web.

BROWSER HIGHLIGHT

You can use this or a local version for personal and educational projects.

If you are doing a commercial project you should use your own version hosted on your own server or content delivery network.

This ensures that you are aware of what version you are using and it doesn't change without your knowledge.


Next, we go into the D3 code.

var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

This is the D3 Margin Convention.

It specifies what margins the Inner Drawing Space will have, in order to separate it from the overall SVG Container.

Then the width and height for the Inner Drawing Space are defined in terms of the margins and the overall width and height of the SVG Container.

The SVG Container will be 500 pixels tall by 960 pixels wide.


Next, we have a number formatting function.

var formatPercent = d3.format(".0%");

The idea behind this code is that it will take in a number and convert it to a specific formatting.

D3 has a core library of formatting functions for numbers, dates and comma-separated values.

The formatting function has many types of formats available like binary, octal, rounded percentage, exponent and others.

The formatting function also allows you to add pre-fixes to numbers.

This allows you to add things like the character M to stand for Millions.

The formatting function also allows you to add the plus sign, minus sign, spaces and even specify whether the number is right or left aligned.

This particular code converts a number to a decimal percentage format.

So 0 point 1 1 gets converted to 11 percentage sign.


Next we have an ordinal scale function for the x-axis data.

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

This code creates an ordinal scaling function where the range goes from 0 to the width of the Inner Drawing Space.

This ordinal scaling function is using the Range Round Bands to set the bands.

Also, the point 1 is the the padding added to offset the bands from the edge of the interval.


Next we have a scale linear function for the y-axis data.

var y = d3.scale.linear()
    .range([height, 0]);

This code creates a linear scaling function where the range goes from the height of the inner drawing space to 0.

Why backwards?

Because this inverts the SVG Coordinate Space along the Y-Axis.

Let me repeat that - this inverts the SVG Coordinate Space along the Y-Axis.

Which means that the origin point will now be at the bottom left instead of the top left for numbers passed into this scaling function.

So as the y axis variable grows it will move up rather than down.


Next we create the X-Axis function

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

We pass in the x-scale function we created earlier and give the axis an orientation of bottom.

This means that the text will be below the line.

Note that we pass in the x scale function before we give it a domain.

The reason we can do this is because the x scale function is a function.

Until we call it, we can continue to modify the function.

So the xAxis function itself now contains the x-scale function.

Nothing is executed until the x-axis function itself is called.


Then we create the Y-Axis the same way

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(formatPercent);

We pass in the y-scale function we created earlier and give the axis an orientation of left.

This orientation will make the axis vertical and make the text appear on the left of the line.

One new thing in this code is the dot tickformat() code.


The dot tickformat() functionality sets or gets the value formatter for labels of the D3 axis component.

.tickFormat(formatPercent);

In this case, we are passing the number formatting function defined earlier to set the value formatter for the D3 Axis Component Generator Function.

This ensures that when the text labels for the ticks are generated, that they will have the format that was specified earlier.

So the Y axis labels will have the format of percentages rather than decimal numbers.

Again, instead of having a y-axis label be zero point 11, it will be 11 percent.


The next code creates the SVG Container and the Inner Drawing Space.

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

First the code selects the body and then appends an SVG Container.

Then the code defines the width and height attributes of the SVG Container in terms of the Inner Drawing Space width and height and the relevant margins.

Then the code appends an SVG Group Element which will be the Inner Drawing Space.

This Inner Drawing Space is transform translated to the right and down by the relevant margins.

All of this is then assigned to the variable SVG which everything else in the code will use as the reference drawing space.


The next code is where D3 asynchronously gets the data from the server and creates a bar chart.


BROWSER HIGHLIGHT all d3.tsv

D3 does an XHR type specific call to the server to get the "data.tsv" file.

Once the server responds with the file, the D3.tsv function calls the callback function with two arguments, the error and the data.

The anonymous callback function then uses the error and data variables to create the D3 Data Visualization.


Let's go through the callback function section by section.


First, we have code that iterates through the data variable which is an array of JavaScript objects

data.forEach(function(d) {
    d.frequency = +d.frequency;
});

For each JavaScript object it does one things:

It converts the frequency from a string to a number


The D3 forEach iteration method is used on the data array.

forEach

Is an iteration method D3 provides for iterating through a JavaScript array.

It applies the function specified to each element of the array.

In this case, it is applying an anonymous function to each element.

This function redefines the values it finds to the same keys in the same objects.

This is used to convert the data to a more usable type of data.

Since each value of the key,value pairs is a JavaScript String, this code iterates through the JavaScript Objects and redefines the values from strings to numbers for the frequency variable.


The + sign in front of the d dot frequency converts the string to a number.

d.frequency = +d.frequency;

This is a quick way to convert a string to a number in JavaScript.


Next, we set the domain for the x scale function

x.domain(data.map(function(d) { return d.letter; }));

Now that we have the data, we can use the map function to generate an array of all the possible letters in the specific order of the JavaScript Objects in the callbackData variable.

We use this newly created array to set the domain of the Ordinal X Scale function.


Next, we set the domain for the y scale function

y.domain([0, d3.max(data, function(d) { return d.frequency; })]);

Now that we have the data, we can set the domain of the y scale function by using the D3.max functionality.

The domain of the Y function is a continuous linear function, so the domain will be defined as going from 0 to the max frequency found in the data.

The d3.max function uses an anonymous function to go through all of the JavaScript objects in the data variable to look for the max frequency.

Once this is found, this number is used to set the domain of the y scaling function.


Next, we call D3.axis operator for the x-axis.

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

First the code appends an SVG Group Element to hold the x-axis.

Then the group element is given the class of "x axis"

Then it is transform translated by the height of the Inner Drawing Space.

This transform translate moves the G element containing the X-Axis elements to the bottom of the Inner Drawing Space.

Then, the xAxis function is called.

This works correctly because we have now defined the x scaling function domain and range.


Next, we call D3.axis operator for the y-axis.

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Frequency");

First the code appends an SVG Group Element to hold the y-axis.

Then the group element is given the class of "y axis"

Then then yAxis function is called.

This works correctly because we have now defined the y scaling function domain and range.

Finally, we append text to the Y Axis which will be used as an axis label.

This text is then transformed by rotating it -90 degrees.

Then the y and dy attributes are defined.

The style is defined as a text-anchor and placed at the end of the Y axis.

And lastly, the text for the SVG Text is defined as "Frequency"

Overall, this places a small label on the Y Axis.


Next we draw the bars that represent the data in the graph.

svg.selectAll(".bar")
    .data(data)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d.letter); })
    .attr("width", x.rangeBand())
    .attr("y", function(d) { return y(d.frequency); })
    .attr("height", function(d) { return height - y(d.frequency); });

This is the D3 pattern.

We selectAll a class of DOM elements which do not yet exist.

We bind data to these elements.

We choose the enter selection.

We append and merge the SVG Rectangle Elements with the placeholder elements created by the Data Functionality.

Then we add SVG Rectangle Specific Attributes based on the data bound to the Rectangle elements.

Each rectangle gets the class of "bar".

This is used in the CSS to style the fill of the rectangles the color steelblue.

Then the x coordinate is determined by passing the letter of the Data Object bound to the specific element to the X Ordinal Scaling Function.

The X Ordinal Scaling Function takes in the X and produces a number in the range specified by the rangeRoundBands.

The Width of the rectangle is determined by the rangeBand of the X Ordinal Scaling Function.

The y coordinate is determined by passing the frequency of the Data Object bound to the specific element to the y Linear Scaling Function.

Lastly, the height of the rectangle is calculated as the difference of the height of the Inner Drawing Space and the frequency of the Object after it has gone through the y Linear Scaling Function.

This is done in this way because the Y axis has been inverted by the y scaling function for values passed into it, but not for the construction of SVG Rectangles.

We'll take a look at the Chrome Developer Tools in the JavaScript Section to really drive this point home.


And that is the end of the callback function and the end of the d3.tsv function.

});

When this is done the graph will have been fully generated.


Let's now build this part by part in JavaScript.



JavaScript Code Build


Because the building of the chart happens inside of the callback function, we will use a more simple anonymous callback function.

d3.tsv("data.tsv", function(error, data){...});

// =>

var callbackError;

var callbackData;

d3.tsv("data.tsv", function(error, data){
    callbackError = error;
    callbackData = data;
});

Notice that the order of the error and data arguments matter in the callback function definition.

The d3.tsv function is going to call the anonymous callback function with two variables.

The error will alway be first and the data will always be second.

So it's important to keep this order.

We use a more simple anonymous callback function for two reasons:

one - it's easier to do in the JavaScript console as we build the chart piece by piece

and two, it reinforces the idea of the callback function and how it works.

Though, to be honest, the preferred way of coding it when you code it into your web page is the way it's done in the example.

That way it is clear that it is a callback function and it is all in one place.


Alright, to the JavaScript Console.


CLEAR CHROME BROWSER CACHE


We start by saving the example data into the data.tsv file.


This file lives in the folder where we will start the Python SimpleHTTPServer.

Save the data.


Next, we start the Python SimpleHTTPServer from the command line

cd Desktop/d3_projects/

python -m SimpleHTTPServer


Now, we have the server going and have the data file ready to be served up.


Next, we make sure the index.html file is saved in the right place and has D3 being loaded into it.

Show index file and save it.


BROWSER - go to the 0.0.0.0:8000/ or localhost:8000/

We can see the web page.


We open the Chrome Developer tools and test to make sure D3 loaded correctly and then clear the screen.

d3.version;

clear();

D3 loaded correctly.

Now we clear the screen.


Next, we go step by step building the visualization.


We start by defining the callbackError and callbackData variables which will be used to house the data we get back from the d3.tsv function.

var callbackError;

var callbackData;


The first step is defining the margins and the width and height of the Inner Drawing Space.

var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;


Next we define the number formatting that will be used to format the numbers on the Y Axis.

var formatPercent = d3.format(".0%");


Let's test the formatPercent functionality with 0 point 1 1

formatPercent(0.11);

You can see that it returns the string of one one percentage sign - that is, 11 percent.


Next - define the x ordinal scaling function as well as the range round bands with padding.

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);


Next - define the y scaling function as well as the range of the function


Remember to pay attention to the fact that the range has height first and then 0.

var y = d3.scale.linear()
    .range([height, 0]);


Next - define the xAxis function and provide it with a scale and orientation.

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");


Next - define the yAxis function and provide it with a scale and orientation as well as the tick formatting function.

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(formatPercent);


Next - define the SVG Container and the Inner Drawing Space

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

BROWSER - Open the Body element, the SVG element and show the inner SVG Group Element.

This is the first sign of anything occurring in the browser.

Up to now we have just been defining functions that will use or be used by the data that is passed in.


This code is where we are going to differ from the example code.

d3.tsv("data.tsv", function(error,data) {
    callbackError = error;
    callbackData  = data;
});

Instead of defining an anonymous callback function that does all the generating of the chart in one go, we will define a callback function that assigns the data and error to variables.

We'll then use these variables to build the bar chart.


Let's check what the d3.tsv call assigned to the callbackError variable.

callbackError;

The callbackError is null, which means the D3.tsv call worked correctly.


Let's check what the d3.tsv call assigned to the callbackData variable.

callbackData;

callbackData.length;

The callbackData variable is an array of 26 elements, which means the D3.tsv call worked correctly.

We use the dot length array function because if it's a small number of elements, the JavaScript Console displays all of them rather than a count.


Let's take a look at the first element of this array.

callbackData[0];

You can see that it is a JavaScript object that has the key,value pairs for letter and frequency.

Each value is currently a string.


Next - we use the D3 Array forEach iterator to go through the array and change the string values to Numbers.

callbackData.forEach(function(d) {
    d.frequency = +d.frequency;
});


Let's take a look now at the first element of this array.

callbackData[0];

You can see that the value for frequency is longer a string - is is now a number.


To check to make sure the frequency is now a JavaScript number we can use the typeof JavaScript function:

typeof(callbackData[0]['frequency']);

Which tell us it is a number.


Satisfied that the frequency is a number, let's move on.


Next - define the domain of the x ordinal scale function

x.domain(callbackData.map(function(d) { return d.letter; }));


Let's check to see what array was passed into the ordinal scale domain

callbackData.map(function(d) { return d.letter; });

We can see that the array passed in was an array of 26 capital letters where each one is a string.


Let's also check to see what the domain of the x ordinal scale function is

x.domain();

You can see that it's the same array.


Next - define the domain of the y scale function

y.domain([0, d3.max(callbackData, function(d) { return d.frequency; })]);


Let's check to see what the max frequency was.

d3.max(callbackData, function(d) { return d.frequency; });

We can see that the max frequency was 0 point 1 2 7 0 2


Let's also check to see what the domain of the y scale function is

y.domain()

You can see that the minimum number of the domain is zero as was defined.

And that the max number of the domain is the 0 point 1 2 7 0 2 number that the d3 dot max function gave us.


Next - the x-axis is created

svg.append("g")
.attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis);

Note again that the transform translate moves the x-axis Group element to the bottom of the Inner Drawing Space.

On my screen, you can see the start of the x-axis as Chrome developer tools are taking up about 2/3s of the web page.

That said, you can see the letters A to the letter K.

BROWSER - click into the G element

If we click into the SVG Group Element for the Inner Drawing Space

You can see the SVG Group element with the class "x axis".

This is the X axis.


Next - the y-axis is created.

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Frequency");

You can see the y-axis and the text anchor declaring that it is the Frequency in Percent.

BROWSER - click into the G element

If we click into the SVG Group Element for the Inner Drawing Space

You can see the SVG Group element with the class "y axis".

This is the y axis.


Now we create the bars according to the data.

svg.selectAll(".bar")
    .data(callbackData)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d.letter); })
    .attr("width", x.rangeBand())
    .attr("y", function(d) { return y(d.frequency); })
    .attr("height", function(d) { return height - y(d.frequency); });

And there you have it - you can see the data represented as bars.


Because we are not applying any CSS to style the bars, let's do the fill of the rectangles in D3.

svg.selectAll(".bar").attr("fill","steelblue");

This is done in CSS in the example.


Before we close the Chrome Developer Tools, let's look at the rectangle for letter A.

BROWSER - Click on the First Rectangle

BROWSER - Double click on the height.

BROWSER - Make it 145

When we make the height 145, notice that it shrunk up!

Why did this happen?

This happens because SVG rectangles are drawn in SVG Coordinate Space.

Which means the x and y point define the top left corner

The width defines the width

and the height defines the height.

Which in the case of the SVG Coordinate system, means that as the height grows it moves towards the bottom of the chart.

While the Y scaling function provides scaling that inverts the chart, it doesn't actually invert the chart.

This is why the height of the rectangle is defined as the height of the Inner Drawing Space minus the y scaling function of the frequency of the letter.


And there we go, we have the finished Bar Chart.


Let's close the Chrome Developer tools to get a better look.

BROWSER - close the Chrome Developer Tools.

BROWSER - zoom out.

You can see the full picture.

The only difference between this and the example was the styling applied to the various DOM Elements.


And with that we built the Basic Chart Bar Chart.

We used Data served from a web server and processed it through an asynchronous XHR call provided by the D3.tsv type specific method.

<< Back To D3 Screencast and Written Tutorials Index