Basic Chart - Line Chart
FREE     Duration: 18:42
Part of Course: Introductory D3 Course
 

Takeaways:

  • You will use the TSV Data from the D3js.org website Line Chart Example to see how a full D3 Line Chart data visualization is built
  • Though it is customary to style the SVG elements you create with the D3 .style operator, once you've built the full visualization it's important to extract the styling into a separate <style> </style> section
  • Notice the D3 Margin Convention
  • Notice the D3 Time Formatting
  • Notice the D3 Time Scale
  • Notice the D3 Linear Scale
  • Notice the D3 SVG Axis Component creation, definition, and instantiation for the X-Axis and Y-Axis
  • Notice the D3 SVG Line Path Generation function
  • Notice the D3 Type-specific XHR call - d3.tsv(...)
  • Notice that the creation of the visualization is all done within the JavaScript Callback Function within the d3.tsv call
  • Notice the D3 Data Join
  • Notice how D3 helps create visual representations of the data

Transcript:

Basic Chart - Line Chart

Visual Code Walk Through


[ Image: Line Chart Example ]

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


[ Image: Data Of Line 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.


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

Highlight <style>

We haven't spoken too much about style yet as to me that comes after the basic framework is built.

The little that we have covered should help us understand this section.


This CSS defines the style for the body element.

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 style for the axis path and line.

BROWSER Highlight .axis.... }

The .axis path and .axis line are both HTML classes that have a style associated with them.

Here the code is saying no fill, a stroke of #000 which is the HTML Color for black

And finally telling the browser that the SVG Content should be using the shape-rendering attribute of crisp edges.


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.


The next section defines the style for the line that will be the path generated by the data.

BROWSER Highlight .line { .... }

The fill is set to none and uses the stroke-width to give the line some depth so that it is seen on the screen.

The stroke color of the line is defined as steelblue.

And that is the end of the styling.

Why separate the code when D3 makes it easy to attach style and fill attributes?

Because we want to keep a separation of concerns.

The D3 JavaScript code gets, manipulates and figures out how to display the data.

The CSS Style code defines how to style the DOM elements once they are displayed.


Next we go into the JavaScript Sections of the documents.


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

BROWSER Highlight <script src....> ... </script>

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 always using the same version you want to use.


Next, we go into the heart of the D3 code.

BROWSER Highlight

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

We've seen this code before.

This is the D3 Margin Convention.

It specifies what margins the inner drawing space will have in order to offset 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 D3 code that is new to us.

BROWSER Highlight

var parseDate = d3.time.format("%d-%b-%y").parse;

The idea behind this code is that it will take a string formatted in the way specified and convert it into a JavaScript Date Object.

When we covered the D3 Time Formatting, we went over a few of what the different letters mean.

These formatting helpers are modeled after the C-Library Standards and the Python time module.

This takes in a string that has a date then a dash then the three letter code for a month then a dash and finally a two digit number for the year.


Next we have a timescale function for the x-axis data.

BROWSER HIGHLIGHT

var x = d3.time.scale()
.range([0, width]);

This code will create a scaling function where the range goes from zero to the width of the inner drawing space.

We will set the domain later after we have loaded in the data.


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

HIGHLIGHT

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

This code will create a 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.

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


Next we create the X-Axis function

HIGHLIGHT

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

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

This means that the text will be below the line.

Though in this case, if you remember that we defined the CSS style of the path as display none, we won't see a line - so we'll just see the text.

One question that might come up --- how can 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

HIGHLIGHT

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

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.


Next, the code defines the D3 path generation function.

HIGHLIGHT

var line = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });


This uses the D3 Path Data Generator Functionality.


For x values and y values, the code defines specific accessor functions.

The data set we are looking at is comprised of dates and closing prices for the Apple stock.

Which means the x values access the date from the data passed in through an anonymous function

While the y values access the stock price close from the data passed in through an anonymous function.

This creates another function which will be called later.


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

HIGHLIGHT

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 + ")");

We have seen this code many times now.

First the code selects the body and then append 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 assigned to the variable SVG which everything else in the code will use as the reference drawing space.


The next code is where the D3 code magic happens.

HIGHLIGHT all d3.tsv

This is where 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.

In this case, the callback function is an anonymous function.


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


First, we have code that iterates through the array of JavaScript objects

HIGHLIGHT

data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;
});

For each JavaScript object it does two things:

one, it converts the date string to a JavaScript Date Object

and two, and it converts the closing price from a string to a number.


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

HIGHLIGHT

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 of the array.

So it is redefines the values it finds to the same keys in the same objects.

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

Since each element of the array is a JavaScript Object, this code iterates through the JavaScript Objects and redefines the values for the keys date and close.


The parseDate is the function we defined earlier.

HIGHLIGHT

d.date = parseDate(d.date);

This takes in a string and using the format we specified, it creates a new JavaScript Date Object.

Then it assigns it right back to the d.date key.


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

HIGHLIGHT

d.close = +d.close;

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


Next, we set the domain for the x scale function

HIGHLIGHT

x.domain(d3.extent(data, function(d) { return d.date; }));

Now that we have the data, we can set the domain of the x scale function by using the D3.extent method.

This returns an array containing the minimum and maximum Dates.

An anonymous function is used to get the date out of the Data Objects.


Next, we set the domain for the y scale function

HIGHLIGHT

y.domain(d3.extent(data, function(d) { return d.close; }));

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

This returns an array containing the minimum and maximum close prices.

An anonymous function is used to get the close out of the Data Objects.


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

HIGHLIGHT

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 is not something we have covered before, though it's been alluded to.

To move the Axis elements around, you have to move the G element in which they live in.

In this case, we are moving the G element to the bottom of the Inner Drawing Space.

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

HIGHLIGHT

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("Price ($)");

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.

This is new.

This text is 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.

Finally the text for the SVG Text is defined.

This places a small label on the Y Axis.


And then finally, we draw the line that is the graph of the data.

HIGHLIGHT

svg.append("path")
    .datum(data)
    .attr("class", "line")
    .attr("d", line);

This is the D3 pattern.

We define the drawing space.

We append a path

We use datum(data) since there is only one piece of a data the array.

The path instructions be generated by the D3 Path Generator functionality using that one array.

Then we give the path a class of line.

This is how the CSS knows to provide styling to the line.

And finally, we add the attribute d, which is the D3 Path Generator Function.

This will take in the data that was passed into the datum and generate the path for us.

One thing to notice here is that the passing of the data object is not explicit.

D3 implicitly understands that it should use the Data bound to the SVG path object.

Thus it it is able to use the data passed into the datum call without having it be specified.


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

HIGHLIGHT

});

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 function

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

// =>

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

We do this in this way 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 which 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.

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 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 from the example is defining the margins and the width and height of the inner drawing space.

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


Next - define the date parsing function.

var parseDate = d3.time.format("%d-%b-%y").parse;


Next - define the x scaling function as well as the range of the function.

var x = d3.time.scale()
    .range([0, width]);


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 - which inverts the y-axis.

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.

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


Next - define the D3 path Generator function.

var line = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.close); });


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.


Next is where we are going to differ a bit from the code of the example.

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'll define a callback function that assigns the data and error to variables.

We'll then use these variables to build the line 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;

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


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 date defined as a string and the close defined as a string.


Next - we use the D3 Array forEach iterator to go through the array and change the string values to either JavaScript Date Objects or Numbers.

callbackData.forEach(function(d) {
    d.date = parseDate(d.date);
    d.close = +d.close;
});


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

callbackData[0];

You can see that the values are no longer strings.

The date value is a date and the close value is a number.


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

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

Which tell us it is an object.


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

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

Which tell us it is an number.

Satisfied that we have JavaScript Date Objects and a numbers, let's move on.


Next - define the domain of the x scale function

x.domain(d3.extent(callbackData, function(d) { return d.date; }));


Let's check to see what the extent was for the x-scale

d3.extent(callbackData, function(d) { return d.date; });

We can see that the lowest date is Tuesday April 24th 2007 and the highest date is Tuesday May 1st 2012


Next - define the domain of the y scale function

y.domain(d3.extent(callbackData, function(d) { return d.close; }));


Let's check to see what the extent was for the y-scale

d3.extent(callbackData, function(d) { return d.close; });

We can see that the lowest stock price was 78.2 and the highest stock price was 636.23.


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

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("Price ($)");

You can see the y-axis and the price text anchor declaring that it is a price in dollars.

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.


Lastly - we create the line by using the D3 Path Generator.

svg.append("path")
    .datum(callbackData)
    .attr("class", "line")
    .attr("d", line);

BROWSER - Click on the path class="line"

You can see that the SVG path was generated.

Why does it show up in a weird way?

It shows up in a weird way because in the example, the styling was defined in the CSS.

Here because we gave the line no fill, stroke color or stroke width, the line is visible and has a fill in it.


Let's delete the path in the Elements section and this time provide the command with the style attributes attached.

Click on the path and delete it.

You can see the path is now gone.


Let's redefine the path now.

svg.append("path")
    .datum(callbackData)
    .attr("class", "line")
    .attr("d", line)
    .attr("fill","none")
    .attr("stroke","steelblue")
    .attr("stroke-width","1.5px");


And there we go, we have the chart.


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

Close the Chrome Developer Tools.

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


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