Lab: Introduction to D3

Assigned: Wednesday, Feb 8, 2017

Due: Monday, Feb 13, 2017 at noon

Collaboration: You should work with your assigned partner for this lab.

The essence of data visualization is generating a visual representation of data. D3, short for “data-driven documents”, makes this relatively easy to do inside of an HTML page. In this lab, you will practice using D3 to generate individual page elements; learn the basics of SVG, which lets you create graphics on a webpage; and learn how to generate page content from data using D3.

Groups

  • Nick R., JaeEun, and Uzo
  • Connor and Logan
  • An and Zachary
  • Linh and Ying
  • Maddy and Ritika
  • Alex and Reilly
  • Thomas and Bazil
  • Dennis and Jemuel
  • Ruth and Nick K.
  • Kevin and Sam
  • Ben and Jinlin
  • Larry and Peiyun
  • Trang and Minh

Getting Started

We’ll follow the usual process for GitHub labs; first, go to grinnell-cs/datavis-d3 on GitHub and create a fork of the repository. Next, add your group members as collaborators on the repository. Finally, use the git clone command to make a copy of your repository fork on the MathLAN machine you are working on.

Part A: Running D3

You should see a starter HTML file and JavaScript source file in the repository you cloned. Open this file in your browser; do you see any differences between the HTML file and what is displayed on the page? Take a look at the lab.js file to see how this extra content is being generated.

You may notice the indentation for this JavaScript code is a little unusual. That’s because D3 uses a technique called method chaining, where we invoke a method on the result of each previous call by chaining together .method()s. Sometimes sequences of chained methods will update the same page element (like .attr('id', 'paragraph3') and .text('...')), while other times the chained methods refer to a new child. I try to indent these methods so they match the nesting level of the elements they add to the HTML page.

One useful feature in modern web browsers is that the page inspector allows you to view the current state of the page’s HTML, not just the initial code. If you right click on the new third paragraph and choose “Inspect” you should see HTML for this element that was created by D3. You can use this tool to troubleshoot strange D3 behavior, which would be very difficult if the inspector only showed us the original HTML loaded before D3 ran.

Now that you have the basic page up and running, add D3 code to the lab.js file to complete each of the following problems. You will almost certainly need to refer to the D3 4.0 API Reference’s section on Selections.

Problem 1

Use the select, append, and text methods to add bold text to the second paragraph on the page.

Problem 2

Use the select and text methods to change the text of the first paragraph.

Problem 3

Use the selectAll and append methods to add italicised text to the end of every <p> tag on the page. You should do this with a single line of JavaScript code!

Once you have completed these problems, commit your changes and push them to GitHub before moving on to the next part.

Part B: Data Joins

D3’s power is not in creating individual elements, but in generating page content from data. While we’ll mostly use D3 to generate graphical elements for our visualizations, you can generate any HTML content using D3.

The following code executes a D3 join, which associates each page element with an entry in a data array.

var dataset = ['one', 'two', 'three', 'four'];
d3.select('body').selectAll('h2')
  .data(dataset)
  .enter()
  .append('h2')
    .text(function(d) {
      return d;
    });

If you add this code to your lab source code, you will see that it generate four <h2> tags on the page with the data from our array used as text. There are few new things in this code example: data, enter, and a new use of text.

The data method associates each element of our array with an HTML element, some or all of which may not exist yet. By applying the data method to an empty selection, we are telling D3 to look for <h2> elements that correspond to each entry of our dataset. After running the data method, we use the enter method to refer to all of the <h2> elements that need to be created to match up with entries in our dataset. Calling append here creates these elements, and then we use the text method to set their text. Instead of passing a single text value (which certainly would work), we use a function here to make the text depend on the data element that matches with each element.

While this is probably somewhat confusing, playing with D3’s data joins is probably the best way to learn how they work.

Problem 1

Add an <h2> element to the page’s HTML source (not using D3). Write a comment to explain what happens when you run the example data join above with this <h2> element in place. Once you have finished your explanation, remove the <h2> element you added to the HTML source.

Problem 2

Add the following content to the body of your HTML document, just above the <script> tag inside the body:

<ul id="favorite_foods"></ul>

You can select this element with this D3 code:

var problem2_list = d3.select('#favorite_foods');

Next, make a JavaScript array foods that lists your five favorite foods. Use a data join to add each of these foods as a list item inside of the <ul> you just added.

Problem 3

You can use data joins to set more than just the inner text of an element with D3. We’ll take one step closer to a real data visualization by inserting scaled images using D3.

  1. Find an image you would like to add to your page and save it to your datavis-d3 directory (I recommend the 151 kitten).
  2. Next, make a JavaScript array image_sizes that holds at least four distinct image sizes.
  3. Use a data join to insert an <img> tag for each entry in your image_sizes array.
  4. Call .attr('src', 'name_of_your_image_file.jpg') to complete each img tag. Verify that you see the appropriate number of images (they will all be the same size at this point).
  5. Use the attr method again to set the width property, but this time use a function for the second parameter. Your function can return the data value associated with each image tag. You now have the world’s worst bar chart!
  6. Can you add another attr call to make each image half as tall as it is wide? Hint: use the "height" attribute.

Problem 4

The last detail of D3 joins you should learn today is indexing. If you pass a function to a D3 method like text, it can take one or two parameters. The first (and possibly only) parmeter is the data item associated with each element. If you have a second parameter, this gives you the index of the data element. This is often useful for creating numbered elements or data series. Use the following data array and a two-parameter text method to generate numbered section headings on the page. The text of each heading should have the form “1. Section Name”. You’ll need to use the <h4> tag, since we already have headings of other types.

var section_names = ["Introduction",
                     "Overview",
                     "Implementation",
                     "Evaluation",
                     "Related Work",
                     "Conclusion"];

Once you have completed part B, commit your changes and push them to GitHub.

Part C: Generating Scalable Vector Graphics

While it is sometimes useful to generate normal HTML elements with D3, the core of our visualizations will likely be built using scalable vector graphics (SVG). SVG graphics are built from a series of shapes and lines, not a grid of pixels; this fits well with what we need to build visualizations. It is easier to think of a visualization like a scatter plot as a colleciton of circles generated on a page, rather than a grid of pixels, some of which are colored blue while others are white. SVG also scales smoothly without any loss of quality; you can take advantage of this to make your visualizations zoomable with virtually no additional effort. While SVG is different from the images we produces in GIMP in CSC 151, the idea of composing shapes to build a single image is similar to the Drawings as Values model of image making.

SVG images are often embedded within HTML documents. They go inside of the <svg> tag, and each shape inside the image is also a tag. Working through the following examples will help you learn the basics of the SVG format, give you a quick preview of some of the interesting SVG features you may want to learn more about when you build your own visualizations, and give you some additional practice using D3 with data joins.

Problem 1

The following JavaScript code will add a 200x200 SVG image to your page using D3.

var svg = d3.select('body').append('svg')
  .attr('width', 200)
  .attr('height', 200);

Copy and paste this code into your lab javascript file. From now on, you can invoke D3 methods on the svg variable to add elements to the image you just added to the page.

Use D3 to dynamically generate the following image, which uses the <circle> SVG tag, along with the fill and fill-opacity attributes. You may want to refer to the W3CSchools SVG Tutorial. If you’re not sure where to start with SVG, you can look at the source of this static SVG image as a hint (right click on the image and choose “Inspect” in most browsers). Because you are generating this graphic with D3, you should only need to make changes to your JavaScript code; solutions that require modifications to the HTML source will not be accepted!

Problem 2

D3 data joins work just as well for SVGs as they do for HTML elements. Use a D3 data join to make a line of circles spaced 60 pixels apart with radii drawn from the following data array:

var radii = [5, 10, 15, 20, 25, 30, 25, 20, 15, 10, 5];

When you are finished, your image should look something like this:

Hint: The y-position of each circle is the same and the radius depends on the data element. What determines the x-position?

Problem 3

The previous problem uses an example with a single value for each datum. While we can “visualize” this incredibly simple dataset, most interesting datasets have many values organized into records. In JavaScript, an easy way to represent this is with an array of objects:

var p3data = [
  { r: 5, color: 'rgb(255, 0, 0)' },
  { r: 10, color: 'rgb(0, 255, 0)' },
  { r: 15, color: 'rgb(0, 0, 255)' },
  { r: 5, color: 'rgb(128, 128, 128)' },
  { r: 25, color: 'rgb(255, 0, 255)' },
  { r: 10, color: 'rgb(192, 255, 0)' },
  { r: 5, color: 'rgb(0, 128, 128)' }];

Now, if you bind these data to a D3 selection you can write a function to access any property in each record. For example, the following snippet will use the r property of each record as the radius for a circle:

...
  .attr('r', function(d) { return d.r; });

Use this dataset to produce a new version of the image from problem 3 that uses the r field as the radius and the color field to set the fill color of each circle. The result should look something like this:

Problem 4

One of the major advantage of SVG as a format for web-based visualizations is the ability to quickly and easily update the display in response to user interaction. You used JavaScript event handlers to add interactive functionality to your calculator in the previous lab, but now we’ll use D3’s event handlers to make an interactive SVG graphic.

The following JavaScript code will track mouse movement inside of an SVG element held in the variable p4svg and log the mouse position to the console:

p4svg.on('mousemove', function() {
  var coord = d3.mouse(this); // Get the mouse position relative to the SVG element
  console.log('Mouse is at ' + coord[0] + ', ' + coord[1]);
});

See if you can recreate the following interactive graphic using D3, which uses the SVG <rect> tag to draw rectangles. If you get stuck, you can look at the source code for this page.