VISUALIZATION ON THE WEB

Using D3.js

(click for print version)


Alfie Abdul-Rahman

WHAT YOU'LL LEARN


The Basics


HTML, CSS, SVG, & JavaScript


More Advanced


D3 -
Data Driven Documents

Who am I?

Dr Alfie Abdul-Rahman

Research Associate

Oxford e-Research Centre

Outline



Hello World! in D3

Getting started with HTML, CSS, SVG, & JavaScript
Manipulating elements in the browser with JavaScript
Introduction to drawing in the browser with D3
Accessing your data from files with D3
Interaction & animation
Worked through examples

Hello World! in D3

Hello World! in D3

You can access the code for this here.

Getting started

  • We use WebStorm as our IDE as:
    • It has pre-installed JavaScript plugins
    • Provides automatic code completion, on-the-fly code analysis, refactoring support, etc.
    • Free license for:
      • Students and teachers
      • Education and training
    • Note: WebStorm has already been installed on the machines in the Room 379.

Getting started

  • Open WebStorm and choose Create New Project
  • Specify the project name and location. In the Project type option, select Empty Project

Getting started

  • In the project that you have created, created a new directory called assets containing three other directories: css, data, and js
  • Download D3 into the directory lib inside js.
  • The lib directory would contain all your external libraries

Getting started

You can now start to create your first Hello World! in D3

Hello World! in D3

We start with the HTML, where we have:

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
  <!-- Reference the D3 library -->
  <!-- Note: Change the path to where you have saved your D3 library -->
  <script type="text/javascript" src="assets/js/lib/d3/d3.min.js"></script>
 </head>
 <body>
  <!-- A <div></div> to hold my SVG -->
  <div id="helloWorldCanvas"></div>
  <!-- Here is where I call my D3 code to create my Hello World! -->
  <!-- Note: Change the path to where you have saved your JavaScript -->
  <script type="text/javascript" src="assets/js/helloWorld.js"></script>
 </body>
</html>

You can save this as HelloWorld.html

Breakdown of HelloWorld.html

Defines the document type to be HTML

<!DOCTYPE html>

The content between <html> and </html> describes the HTML document

<!DOCTYPE html>
<html lang="en">
 ...
</html>

The content between <head> and </head> gives us information about the document

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
 </head>
 ...
</html>

Breakdown of HelloWorld.html

The text between <title> and </title> provides a title for the document

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
 </head>
 ...
</html>

The content between <body> and </body> holds the visible page content

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
 </head>
 <body>
  ...
 </body>
</html>

Breakdown of HelloWorld.html

<div> and </div> defines a section in an HTML document

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
 </head>
 <body>
  <!-- A <div></div> to hold my SVG -->
  <div id="helloWorldCanvas"></div>
 </body>
</html>

HTML elements are keywords (element names) surrounded by angle brackets

<element>content</element>

Breakdown of HelloWorld.html

<script> and </script> tag is used to define a client-side script, such as JavaScript

<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Hello World!</title>
  <!-- Reference the D3 library -->
  <!-- Note: Change the path to where you have saved your D3 library -->
  <script type="text/javascript" src="assets/js/lib/d3/d3.min.js"></script>
 </head>
 <body>
  <!-- A <div></div> to hold my SVG -->
  <div id="helloWorldCanvas"></div>
  <!-- Here is where I call my D3 code to create my Hello World! -->
  <!-- Note: Change the path to where you have saved your JavaScript -->
  <script type="text/javascript" src="assets/js/helloWorld.js"></script>
 </body>
</html>

Hello World! in D3

Now we can start writing the Hello World! in D3


// create the SVG container
var svg = d3.select("#helloWorldCanvas")
            .append("svg")
            .attr("width", 500)
            .attr("height", 300);

// add the text that we want to display
svg.append("text")
    .attr("x", 150).attr("y", 57)
    .attr("fill", "red")
    .attr("font-size", "25px").attr("font-family", "sans-serif")
    .text("Hello World!");

// now let's add a circle to accompany the text
svg.append("circle")
    .style("fill", "#27aae1")
    .attr("cx", 100).attr("cy", 50)
    .attr("r", 15);

You can save this as helloWorld.js

Our Hello World! in D3

First We Create The Container


var svg = d3.select("#helloWorldCanvas")
            .append("svg")
            .attr("width", 500)
            .attr("height", 300);
  • select(): select the first matching element
  • append(): create a new element, add it to the container as its last child and return back a new selection with the appended elements
  • attr(): get (or set) attribute values

For more information check out D3 API

Then We Render The Text


// create the SVG container
var svg = d3.select("#helloWorldCanvas")
            .append("svg")
            .attr("width", 500)
            .attr("height", 300);

// add the text we want to display
svg.append("text")
    .attr("x", 150).attr("y", 57)
    .attr("fill", "red")
    .attr("font-size", "25px")
    .attr("font-family", "sans-serif")
    .text("Hello World!");
  • append(): create a new element, add it to the container as its last child and return back a new selection with the appended elements
  • attr(): get (or set) attribute values

And Later We Append The Circle


// create the SVG container
var svg = d3.select("#helloWorldCanvas")
            .append("svg")
            .attr("width", 500)
            .attr("height", 300);

// add the text we want to display
svg.append("text")
    .attr("x", 150).attr("y", 57)
    .attr("fill", "red")
    .attr("font-size", "25px")
    .attr("font-family", "sans-serif")
    .text("Hello World!");

// now let's add a circle
svg.append("circle")
    .style("fill", "#27aae1")
    .attr("cx", 100).attr("cy", 50)
    .attr("r", 15);
  • style(): sets the CSS style property

For more information check out D3 API

Getting started

So in WebStorm you would have something that looks like this:

Setup

  • You may also need to set-up a web server if your D3 code is using external datafiles due to some browsers' restrictions
  • Some of the web servers you can use:
    • Node.js (http://nodejs.org/):
      • Install Node.js and run npm install http-server -g
      • Type http-server and this will start a http server from the current directory
    • Python (http://www.python.org/)
      • Install Python and type python -m SimpleHTTPServer 8080
  • However if you are using WebStorm, Node.js is included in the IDE

Introducing D3

D3 (Data-Driven Documents):

  • A JavaScript library that utilizes SVG, HTML, and CSS for creating data visualization on the web
  • Created by Mike Bostock, Vadim Ogievetsky, and Jeff Heer
  • With D3, you can:
    • Easily load your data into the browser
    • Inspect and manipulate your data through the Document Object Model (DOM)
    • Include interaction and animation in your visualization easily
  • Download D3 (v3.5.17) (https://github.com/d3/d3/releases/tag/v3.5.17)

Why D3 for Data Visualization?

  • It works well on the web - no plug-ins!
  • It integrates well with existing web technologies like the DOM and CSS
  • You can design and build almost any visualization you can imagine
  • There is excellent documentation, examples, and community available online

Introducing Developer Tools

  • Every modern browser has devtools
  • Can view source code of an HTML page by right-click on the page then select View page source
  • See the DOM by the inspecting the element
  • For example in Chrome or Safari, right-click on the page then select Inspect element

Example: Developer Tools

Introducing DOM

DOM (Document Object Model):

  • Hierarchical structure of HTML
  • Each pair of bracketed tags is an element
  • Refer to elements' relative relationships to each other in terms of: parent, child, sibling, ancestor, and descendant
  • Defines the objects and properties of all HTML element as well as the methods (or interface) to access them

Example: HTML

<!DOCTYPE html>
<html>
 <!-- The content between the <html> and </html> describes the web page -->
 <head>
  <!-- <head></head> handles all the head elements -->
  <!-- <head></head> also provides the information about the page -->
  <!-- Tags that can be inside <head> element: <title>, <style>, <meta>,
       <link>, <script>, <noscript>, <base> -->
  <!-- <title></title> gives the title of the page -->
  <title>Visual Analytics</title>
 </head>
 <body>
  <!-- The content between the <body></body> is the visible page content -->
  <h1>Visual Analytics</h1>
  <p class="desc">Examples of <em>visualisation</em>:</p>
  <!-- Valid class or id: Must start with a letter and can be
       followed by letter, digits, hypens, and underscores -->
  <div id="iAmUnique1" class="desc">
   <ul>
    <li>Parallel coordinates</li>
    <li>Scatter plots</li>
   </ul>
  </div>
 </body>
</html>

Introducing DOM

DOM (Document Object Model):

  • In the previous example:
    • body is the parent element to its children - h1, p, and div (which are siblings to each other)
    • And all elements are descendants of html

Example: DOM

Introducing SVG

SVG (Scalable Vector Graphics):

  • A text-based image format that is defined using markup code same as HTML
  • You can include SVG code directly inside any HTML document or
  • Insert it dynamically into the DOM using JavaScript
  • SVG is XML-based therefore all elements must have a closing tag:
<element></element>   <!-- Element with closing tag -->
<element/>            <!-- Element with self-closing tag -->
  • Coordinate system starts at the top-left corner
0,0

SVG Elements and Shapes

  • An SVG image begins with <svg> element
  • The width and height of the SVG image can be defined by the width and height attributes
  • A simple SVG example:

<svg width="100%" height="100%">
 <rect x="85" y="5" width="40" height="40" fill="rgba(255, 0, 0, 1.0)" />
</svg>

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Rectangle <rect>
    • <svg width="50" height="50">
       <rect x="0" y="0" width="50" height="50" fill="blue" />
      </svg>
    • To generate this rectangle using D3:
    • // Assuming that you have already created the svg container
      svg.append("rect")
          .attr("x", 0).attr("y", 0)
          .attr("width", 50).attr("height", 50)
          .style("fill", "blue");
      

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Circle <circle>
    • <svg width="50" height="50">
       <circle cx="25" cy="25" r="25" fill="red" />
      </svg>
    • To generate this circle using D3:
    • // Assuming that you have already created the svg container
      svg.append("circle")
          .attr("cx", 25).attr("cy", 25)
          .attr("r", 25)
          .style("fill", "red");
      

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Ellipse <ellipse>
    • <svg width="50" height="50">
       <ellipse cx="25" cy="25" rx="15" ry="10" fill="green" />
      </svg>
    • To generate this ellipse using D3:
    • // Assuming that you have already created the svg container
      svg.append("ellipse")
          .attr("cx", 25).attr("cy", 25)
          .attr("rx", 15).attr("ry", 10)
          .style("fill", "green");
      

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Line <line>
    • <svg width="50" height="50">
       <line x1="5" y1="5" x2="40" y2="40" stroke="purple" stroke-width="8" />
      </svg>
    • To generate this line using D3:
    • // Assuming that you have already created the svg container
      svg.append("line")
          .attr("x1", 5).attr("y1", 5)
          .attr("x2", 40).attr("y2", 40)
          .style("stroke", "purple")
          .style("stroke-width", 8);
      

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Polyline <polyline>
    • <svg width="50" height="50">
       <polyline fill="none" stroke="orange" stroke-width="3" points="05,30 15,30 15,20 25,20 25,10 35,10" />
      </svg>
    • To generate this polyline using D3:
    • // Assuming that you have already created the svg container
      svg.append("polyline")
          .attr("points", "05,30 15,30 15,20 25,20 25,10 35,10")
          .style("fill", "none")
          .style("stroke", "orange")
          .style("stroke-width", 3);
      

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Polygon <polygon>
    • <svg width="50" height="50">
       <polygon fill="pink" stroke="purple" stroke-width="4" points="05,30 15,10 25,30" />
      </svg>
    • To generate this polyline using D3:
    • // Assuming that you have already created the svg container
      svg.append("polygon")
          .attr("points", "05,30 15,10 25,30")
          .style("fill", "pink")
          .style("stroke", "purple")
          .style("stroke-width", 4);
      

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Path <path> and the commands that are available are:
      • M: moveto
      • L: lineto
      • H: horizontal lineto
      • V: vertical lineto
      • C: curveto
      • S: smoth curveto
      • Q: quadratic Bézier curve
      • T: smooth quadratic Bézier curveto
      • A: elliptical Arc
      • Z: closepath
    • You can also defined the commands in lower cases. Upper cases are for absolute positioned while lower cases are for relative positioned

SVG Elements and Shapes

  • Pre-defined shapes in SVG:
    • Path <path>
    • <svg width="200" height="205">
       <path d="M100 0 L25 200 L175 200 Z" />
      </svg>
    • To generate this path using D3:
    • // Assuming that you have already created the svg container
      svg.append("path")
          .attr("d", "M100 0 L25 200 L175 200 Z");
      

SVG Elements and Shapes

  • SVG also renders text using <text> element. For example:
  • <svg width="125" height="25">
     <text x="10" y="20" fill="red" font-size="20">I love SVG!</text>
    </svg>
  • To generate this text using D3:
  • // Assuming that you have already created the svg container
    svg.append("text")
        .attr("x", 10)
        .attr("y", 20)
        .style("fill", "red")
        .style("font-size", 20)
        .text("I love SVG");
    

SVG Elements and Shapes

  • Or you can even rotate the text
  • <svg width="125" height="40">
     <text x="10" y="20" fill="red" font-size="20" transform="rotate(30 20,40)">I love SVG!</text>
    </svg>
  • To generate this text using D3:
  • // Assuming that you have already created the svg container
    svg.append("text")
        .attr("x", 10)
        .attr("y", 20)
        .style("fill", "red")
        .style("font-size", 20)
        .attr("transform", "rotate(30 20, 40)")
        .text("I love SVG");
    

SVG Elements and Shapes

  • You can apply transformation to all SVG shapes using the transform attribute
  • There are five transformation functions:
    • translate()
    • rotate()
    • scale()
    • skew()
    • matrix()
  • When you perform the transformation matrix, you are not transforming the SVG shapes but actually the coordinate system of the SVG shapes

SVG Elements and Shapes

  • You can also combine transformations by putting multiple transformation functions inside the transform attribute
  • For example
  • <svg width="200" height="175">
     
     <rect x="20" y="20" width="50" height="50" style="fill: none; stroke: blue" />
     
     <rect x="20" y="20" width="50" height="50"  style="fill: none; stroke: green"
           transform="translate(70, 70) rotate(40)" />
    </svg>
  • It is important to remember that the sequence of the transformations matter

SVG Elements and Shapes

  • To generate the previous shapes using D3:
  • // Assuming that you have already created the svg container
    svg.append("rect")
        .attr("x", 20).attr("y", 20)
        .attr("width", 50).attr("height", 50)
        .style("fill", "blue");
    
    svg.append("rect")
        .attr("x", 20).attr("y", 20)
        .attr("width", 50).attr("height", 50)
        .style("fill", "green")
        .attr("transform", "translate(70, 70) rotate(40)");
    

SVG Elements and Shapes

  • You can group together your SVG shapes using <g> element
  • With the <g> element, you can transform the group of shapes as though it was a single shape (unlike the svg element where you cannot apply the transformation function on it)
  • You can also style the grouped elements or re-use them later in your program treating them as a single element

SVG Elements and Shapes

  • <g> element. Here is a simple example:
  • <svg width="200" height="200">
     <g>
      <rect x="20" y="20" width="50" height="50" style="fill: blue" />
      <text x="85" y="85" style="fill:red; font-size: 20px;">I love SVG!</text>
      <rect x="100" y="100" width="50" height="50" style="fill: green" />
     <g>
    </svg>
    I love SVG!

SVG Elements and Shapes

  • To generate the previous shapes using D3:
  • // Assuming that the svg container has been created
    var group = svg.append("g");
    
    group.append("rect")
        .attr("x", 20).attr("y", 20)
        .attr("width", 50).attr("height", 50)
        .style("fill", "blue");
    
    group.append("text")
        .attr("x", 85)
        .attr("y", 85)
        .style("fill", "red")
        .style("font-size", "20px")
        .text("I love SVG!");
    
    group.append("rect")
        .attr("x", 100).attr("y", 100)
        .attr("width", 50).attr("height", 50)
        .style("fill", "green");
    

SVG Elements and Shapes

  • <g> element with a transform attribute
  • <svg width="200" height="200">
     <g transform="rotate(45, 50, 60)">
      <rect x="20" y="20" width="50" height="50" style="fill: blue" />
      <text x="85" y="85" style="fill:red; font-size: 20px;">I love SVG!</text>
      <rect x="100" y="100" width="50" height="50" style="fill: green" />
     <g>
    </svg>
    I love SVG!

SVG Elements and Shapes

  • To generate the previous shapes using D3:
  • // Assuming that the svg container has been created
    var group = svg.append("g")
                    .attr("transform", "rotate(45, 50, 60)");
    
    group.append("rect")
        .attr("x", 20).attr("y", 20)
        .attr("width", 50).attr("height", 50)
        .style("fill", "blue");
    
    group.append("text")
        .attr("x", 85)
        .attr("y", 85)
        .style("fill", "red")
        .style("font-size", "20px")
        .text("I love SVG!");
    
    group.append("rect")
        .attr("x", 100).attr("y", 100)
        .attr("width", 50).attr("height", 50)
        .style("fill", "green");
    

SVG Elements and Shapes

  • And you can also define styling with the <g> element
  • <svg width="200" height="200">
     <g style="stroke: orange; stroke-width: 4px;" transform="rotate(45, 50, 60)">
      <rect x="20" y="20" width="50" height="50" style="fill: blue" />
      <text x="85" y="85" style="fill:red; font-size: 20px;">I love SVG!</text>
      <rect x="100" y="100" width="50" height="50" style="fill: green" />
     <g>
    </svg>
    I love SVG!
  • A disadvantage of the <g> element is that it has no x and y attributes. You can only re-position the <g> element using the transform attribute through the translate function

SVG Elements and Shapes

  • To generate the previous shapes using D3:
  • // Assuming that the svg container has been created
    var group = svg.append("g")
                    .attr("transform", "rotate(45, 50, 60)")
                    .style("stroke", "orange");
    
    group.append("rect")
        .attr("x", 20).attr("y", 20)
        .attr("width", 50).attr("height", 50)
        .style("fill", "blue");
    
    group.append("text")
        .attr("x", 85)
        .attr("y", 85)
        .style("fill", "red")
        .style("font-size", "20px")
        .text("I love SVG!");
    
    group.append("rect")
        .attr("x", 100).attr("y", 100)
        .attr("width", 50).attr("height", 50)
        .style("fill", "green");
    

Styling SVG Elements and Shapes

  • Default style is black fill with no stroke
  • You can use SVG properties such as fill, stroke, and opacity to style your SVG elements and shapes
  • For example using inline styles:
<rect x="85" y="5" width="40" height="40" fill="red" stroke="blue"
      stroke-width="5" />
  • Or through the CSS approach:
<rect x="85" y="5" width="40" height="40" class="mySvgStyle" />
.mySvgStyle {
    fill: red;
    stroke: blue;
    stroke-width: 5;
}

Example: SVG


<svg width="800" height="220">
 <!-- The order when the elements are called determines the ordering of the
    objects -->
 <rect x="250" y="10" width="500" height="200" stroke-width="1"
       stroke="black" fill="white"></rect>
 <!-- <g> groups the shapes together allowing you to transform and style the
    whole group as a single shape -->
 <g transform="scale(3) translate(100, 10)">
  <circle cx="25" cy="25" r="20" fill="rgba(127, 201, 127, 0.7)"
          stroke="rgba(127, 201, 127, 0.5)" stroke-width="5"></circle>
  <ellipse cx="65" cy="25" rx="35" ry="15" fill="rgba(190, 174, 212, 0.7)"
           stroke="rgba(190, 174, 212, 0.5)" stroke-width="5"></ellipse>
  <rect x="85" y="5" width="40" height="40" fill="rgba(253, 192, 134, 0.7)"
        stroke="rgba(253, 192, 134, 0.5)" stroke-width="5"></rect>
 </g>
</svg>

Introducing JavaScript

  • Scripting language for manipulating the DOM after a page has been loaded in the browser
  • Type the code directly in the JavaScript console or
  • Load the script to the browser using a web page
  • Two ways to reference the scripts:
    • Directly in the HTML, between two script tags
    <body>
     <script type="text/javascript">
      alert("Hello, world!");
     </script>
    </body>
    • Stored in a separate file with a .js suffix, and then referenced in the HTML
    <head>
     <title>Page Title</title>
     <script type="text/javascript" src="script.js"></script>
     </head>

Example: JavaScript


// Declaring a variable of an array of objects
var myChocolate = [{"type": "milk", "cocoa_butter": 15, "quantity": 13 },
                   {"type": "sweet", "cocoa_butter": 18, "quantity": 8 }];

// A function to loop through the array and display the cocoa butter content
function displayCocoaButter() {
 var index;
 // Loop through the array of objects
 for (index = 0; index < myChocolate.length; index++) {
  // Create a <p> element
  var _p = document.createElement("p");
  // Get the cocoa butter content and add it to the <text> node
  var _text = document.createTextNode("Cocoa butter content: " +
  myChocolate[index].cocoa_butter);
  // Add the created <text> node to the <p> element
  _p.appendChild(_text);
  // Add the created <p> element to <body>
  document.body.appendChild(_p);
 }
}

Breakdown: JavaScript

  • var allows us to declare a variable called myChocolate


// Declaring a variable of an array of objects
var myChocolate = [{"type": "milk", "cocoa_butter": 15, "quantity": 13 },
                   {"type": "sweet", "cocoa_butter": 18, "quantity": 8 }];
  • Here, myChocolate is an array of objects consisting of two items
  • Each item in the object contains three elements:
    • type,
    • cocoa_butter, and
    • quantity

Breakdown: JavaScript

  • function operator helps us to define a function called displayCocoaButter


// A function to loop through the array and display the cocoa butter content
function displayCocoaButter() {
 var index;
 // Loop through the array of objects
 for (index = 0; index < myChocolate.length; index++) {
  // Create a <p> element
  var _p = document.createElement("p");
  // Get the cocoa butter content and add it to the <text> node
  var _text = document.createTextNode("Cocoa butter content: " +
  myChocolate[index].cocoa_butter);
  // Add the created <text> node to the <p> element
  _p.appendChild(_text);
  // Add the created <p> element to <body>
  document.body.appendChild(_p);
 }
}
  • In our function, we are:
    • Looping through myChocolate array and
    • Creating a <p> element to display the cocoa_butter value

Example: JavaScript

You can access the code for this here.

JavaScript functions in D3

  • If you are new to JavaScript functions, here's a quick introduction:
  • 
    function functionName(variableName) {
     return variableName;
    }
  • The function operator helps us to define a function called functionName
  • functionName takes a variable variableName and returns the same variable

JavaScript functions in D3

  • If the functionName is missing, it is called an anonymous function
  • Anonymous function allows us to make the code more concise when the function declared will only be used once
  • For example in the D3 Text Operator
  • .text( function (d) {
     return d;
    });

JavaScript functions in D3

  • Our function can contain more than one line or just a simple return, for example we could do something like this:
  • 
    var sampleText = ["apple", "car", "dress"];
    
    var p = d3.select("body")
                .selectAll("p")
                .data(sampleText)
                .enter()
                .append("p")
                .text(function(d) {
                    var tempText;
                    tempText = "red " + d;
                    tempText = tempText + "!";
                    return tempText;
                });

Variables available in D3 Operators

  • There are three variables that are available in D3 operators:
    • d, this, and i
  • The variable d is available for use in the anonymous function
  • .text( function (d) {
     return d;
    });
  • The variable d is given to us by D3, and it refers to the current __data__ attribute of the specific processed element
  • this refers to the current DOM element that is being evaluated
  • While i refers to the index of the current HTML element that is being evaluated in the selected elements selection

Introducing CSS

CSS (Cascading Style Sheets):

  • The visual representation of the document structure
  • Consists of selectors and properties
selector {
    property: value;
}

selectorA, selectorB {
    property: value;
}
  • For example:
p {
    font-size: 24px;
}

h1, .desc {
    color: #F179D1;
    font-weight: bold;
}

CSS-Style Selectors

Type selectors: match DOM elements with the same name

p  /* Selects all paragraphs */

Descendant selectors: match elements contained by another elements

div p  /* Selects p elements contained in a div */

Class selectors: match elements of any type assigned to a specific class


.desc            /* Selects elements with class "desc" */
.desc.highlight  /* Selects hightlighted desc */

ID selectors: match the element with the specific ID

#iAmUnique1  /* Selects element with ID "iAmUnique1" */

Combination: combined to target specific elements

div.desc  /* Selects divs with class "desc" only */

Referencing Styles

Three ways to apply CSS styles rules to HTML document:

  • Embed the CSS in the HTML

<head>
 <style type="text/css">
  h1 { font-size: 16px; }
 </style>
</head>
  • For example:
<!DOCTYPE html>
<html>
 <head>
  <title>Visual Analytics</title>
  <!-- Embedding the CSS in the HTML -->
  <style type="text/css">
   h1 { font-size: 16px; }
  </style>
 </head>
 <body>
  <h1>Visual Analytics</h1>
 </body>
</html>

Referencing Styles

Three ways to apply CSS styles rules to HTML document:

  • Reference an external stylesheet from the HTML

<head>
 <link rel="stylesheet" href="style.css">
</head>
  • For example:
<!DOCTYPE html>
<html>
 <head>
  <title>Visual Analytics</title>
  <!-- Reference an external stylesheet from the HTML -->
  <link rel="stylesheet" href="style.css">
 </head>
 <body>
  <h1>Visual Analytics</h1>
 </body>
</html>

Referencing Styles

Three ways to apply CSS styles rules to HTML document:

  • Attach inline styles
<h1 style="color: red; font-size: 24px;">An example of inline style</h1>
  • For example:
<!DOCTYPE html>
<html>
 <head>
  <title>Visual Analytics</title>
 </head>
 <body>
  <!-- Attach inline styles -->
  <h1 style="color: red; font-size: 24px;">Visual Analytics</h1>
 </body>
</html>

Using CSS Style Rule in D3

Our style properties:

.myStyle {
    fill: red;
    font-size: 25px;
    font-family: sans-serif;
}

Our JavaScript:


var svg = d3
        .select("#helloWorldCanvas")
        .append("svg")
        .attr("width", 500)
        .attr("height", 300);

// add the text we want to display
svg.append("text")
    .attr("x", 150).attr("y", 57)
    .attr("class", "myStyle")
    .text("Hello World!");

// now let's add a circle
svg.append("circle")
    .style("fill", "#27aae1")
    .attr("cx", 100).attr("cy", 50)
    .attr("r", 15);

Loading Data

Get my data into D3...

Loading Data

  • D3 has built-in methods to help load plain text, CSV, XML, TSV, and JSON files
  • Example of loading CSV data
  • 
    d3.csv("chocolate.csv", function(csvData) {
        console.log(csvData);
    });
  • Example of loading JSON data
  • 
    d3.json("chocolate.json", function(jsonData) {
        console.log(jsonData);
    });
  • Note: d3.csv() and d3.json() are asynchronous methods, where the rest of your code is executed as your browser waits for the file to be downloaded.

    A callback, represented by the function call is what is executed when the data has been retrieved successfully.

Loading Data: chocolate.csv

  • Assume that you have a CSV file with some chocolate data in it (chocolate.csv):
name,manufacturer,price,rating
Dairy Milk,Cadbury,45,2
Galaxy,Nestle,42,3
Lindt,Lindt,80,4
Hershey,Hershey,40,1
Dolfin,Lindt,90,5
Bournville,Cadbury,70,2
  • You can convert the file in an array of objects with d3.csv()

d3.csv("chocolate.csv", function(csvData) {
    // retrieve and display the first row of data
    console.log(csvData[0]);
});
>> {name: "Dairy Milk", manufacturer: "Cadbury", price: "45", rating: "2"}
  • The property names for the data objects are defined by the headers of the CSV file
  • If you are planning to use d3.csv() this way, your CSV file has to contain a header row

chocolates.csv

name,manufacturer,price,rating
Dairy Milk,Cadbury,45,2
Galaxy,Nestle,42,3
Lindt,Lindt,80,4
Hershey,Hershey,40,1
Dolfin,Lindt,90,5
Bournville,Cadbury,70,2

Processing chocolates.csv...

function loadAndDisplayBrands(placement, w, h) {
 var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
 d3.select(placement).html("");

 d3.csv("assets/data/chocolates.csv", function(data) {
  var svg = d3.select(placement).append("svg").attr("width", width)
              .attr("height", height).append("g")
              .attr("transform",
                    "translate(" + margins.left + "," + margins.top + ")");

  data.forEach(function(d, i) {
    svg.append("text").attr("x", 50).attr("y", i * 30)
        .attr("fill", "darkblue").attr("font-size", "20px")
        .attr("font-family", "sans-serif").text(d.name);
  });
 });
}

loadAndDisplayBrands("body", 400, 400);

Closer examination

  • We are processing our chocolates.csv in a function that expects three arguments
function loadAndDisplayBrands(placement, w, h) {
    ...
}
  • Here we declare our variables and assign values to them
function loadAndDisplayBrands(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
}
  • We select the container for the SVG object and set the inner HTML content to be empty (i.e., clear the container)
function loadAndDisplayBrands(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
    d3.select(placement).html("");
}

Closer examination

  • We load our input data using d3.csv
function loadAndDisplayBrands(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
    d3.select(placement).html("");

    d3.csv("assets/data/chocolates.csv", function(data) {
        ...
    });
}

Closer examination

  • Once the input data has been uploaded, we find the container for the SVG object and append new svg element to the container
  • We define the container's width and height as well as group all of our content by appending the g element to our svg
function loadAndDisplayBrands(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
    d3.select(placement).html("");

    d3.csv("assets/data/chocolates.csv", function(data) {
        var svg = d3.select(placement).append("svg").attr("width", width)
                    .attr("height", height).append("g")
                    .attr("transform",
                        "translate(" + margins.left + "," + margins.top + ")");
    });
}
  • We then move our svg by re-positioning it using the transform attribute

Closer examination

  • We then iterate through our uploaded chocolate data
  • For each value in the array, we create a text element and display its brand
function loadAndDisplayBrands(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
    d3.select(placement).html("");

    d3.csv("assets/data/chocolates.csv", function(data) {
        var svg = d3.select(placement).append("svg").attr("width", width)
                    .attr("height", height).append("g")
                    .attr("transform",
                        "translate(" + margins.left + "," + margins.top + ")");

        data.forEach(function(d, i) {
            svg.append("text").attr("x", 50).attr("y", i * 30)
                .attr("fill", "darkblue").attr("font-size", "20px")
                .attr("font-family", "sans-serif").text(d.name);
        });
    });
}

Brands in chocolates.csv

Loading Data: chocolates.json

  • JSON allows you to define nested data. Assume that you have a JSON file with some chocolate data in it (chocolate.json)
{ "chocolates": [
    { "name": "Dairy Milk", "manufacturer": "Cadbury", "price": 45, "rating": 2 },
    { "name": "Galaxy", "manufacturer": "Nestle", "price": 42, "rating": 3 },
    { "name": "Lindt", "manufacturer": "Lindt", "price": 80, "rating": 4 },
    { "name": "Hershey", "manufacturer": "Hershey", "price": 40, "rating": 1 },
    { "name": "Dolfin", "manufacturer": "Lindt", "price": 90, "rating": 5 },
    { "name": "Bournville", "manufacturer": "Cadbury", "price": 70, "rating": 2 }]
}
  • This is how we get access to it...
// this is how we process it
d3.json("assets/data/chocolate.json", function (data) {
    var chocolates = data.chocolates;
    console.log(chocolates[0]);
});
>> {name: "Dairy Milk", manufacturer: "Cadbury", price: "45", rating: "2"}
  • d3.json() allows us to deal with nested data easily

Drawing shapes using D3

Drawing shapes using D3

D3's shape generators provide easy SVG path data.


var placement = "#canvas",
    width = 400,
    height = 400;

var svg = d3.select(placement).append("svg")
            .attr("width", width)
            .attr("height", height)
            .append("g");

svg.append("circle")
    .style("fill", "#27aae1")
    .attr("cx", 40)
    .attr("cy", 50)
    .attr("r", 15);

svg.append("path")
    .attr("d", d3.svg.symbol()
    .size(200)
    .type("cross"))
    .style("fill", "#27aae1")
    .attr("transform", "translate(90,50)");

Let's examine it closer

  • Here, we are first creating the SVG element to place all of our shapes
  • In our code, we will find the container with the id canvas and append a new svg element to the container

    var placement = "#canvas",
    width = 400,
    height = 400;

var svg = d3.select(placement).append("svg")
            .attr("width", width)
            .attr("height", height)
            .append("g");
  • The svg element that we have just created will have a width and height of 400
  • We then group all of our shapes by appending the g (group) element to our svg

Let's examine it closer

  • We then append a circle element to our svg

    svg.append("circle")
    .style("fill", "#27aae1")
    .attr("cx", 40)
    .attr("cy", 50)
    .attr("r", 15);
  • cx and cy define the x and y coordinates of the circle while r defines the radius of the circle
  • cx, cy, and r are required for drawing the circle element
  • If we have not specified the style method, a black circle would have been drawn

Let's examine it closer

  • We can use the path element to draw rectangles, circles, ellipses, polylines, polygons, straight lines, and curves

    svg.append("path")
    .attr("d", d3.svg.symbol()
		.size(200)
		.type("cross")
    )
    .style("fill", "#27aae1")
    .attr("transform", "translate(90,50)");
  • Here we used D3 symbol generator to generate the different shapes such as crosses, squares, triangles, and diamonds

D3: Data Joins

Through this piece of code, we can draw a circle. But just one.


var svg = d3.select("body")
            .append("svg")
            .attr("width", 400)
            .attr("height", 400)
            .append("g");

svg.append("circle")
    .attr("r", 20)
    .attr("cx", 30)
    .attr("cy", 40)
    .style("fill", "#27aae1");
      

But what if we want to draw a circle for every data point?


var svg = d3.select("body").append("svg")
            .attr("width", 400)
            .attr("height", 400)
            .append("g");

var data = [{x:160, y:190}, {x:30, y:200},
            {x:300, y:100}];

svg.selectAll("circle").data(data)
    .enter().append("circle")
    .attr("r", 10)
    .attr("cx", function(d) {
            return d.x;
    })
    .attr("cy", function(d) {
            return d.y;
    })
    .style("fill", "#F179D1");
      

svg.selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 10)
  .attr("cx", function(d) {
       return d.x;
  })
  .attr("cy", function(d) {
       return d.y;
  })
  .style("fill", "#F179D1");
svg.selectAll("circle")

How can I select something that doesn't yet exist? We are saying this:
"I want all circles to correspond to data"

Now we take the data, enter it and append a circle for each data item.

.data(data).enter()

Next we set the circle's attributes:

.attr("r", 10)

We can access each individual data item property using a function which can pass through a data element d.


var data = [{x:160, y:190},
            {x:30, y:200},
            {x:300, y:100}];

.attr("cy", function(d) {
	return d.y;
})

The Enter/Update/Exit Pattern

Enter-Exit-Update

Revisit: A Circle for Every Data Point

  • We first define our coordinates

    var data = [{x:160, y:190}, {x:30, y:200}, {x:300, y:100}];
  • Then we use data() to iterate through each data point
  • For each data point, we:
    • Create a circle
    • Specify its radius, x and y center coordinates, and colour

svg.selectAll("circle").data(data)
    .enter().append("circle")
    .attr("r", 10)
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .style("fill", "#F179D1");
  • Some notes:
    • selectAll() will return us empty references to all circles (though they don’t exist yet)
    • data() binds the data to the elements to be created
    • enter() returns a placeholder reference to the new element
    • append() helps us to add a circle to the DOM.

Enter/update pattern


var placement = "#canvas",
    width = 400, height = 400;

var svg = d3.select(placement)
            .append("svg")
            .attr("width", width)
            .attr("height", height)
            .append("g");

update([{x:30, y:60}]);

function update(data) {
 var rect = svg.selectAll("rect")
                .data(data);

 rect.enter().append("rect")
    .style("fill", "#fff")
    .attr("height", 20)
    .attr("width", 0)
    .transition()
    .attr("width", 25);

 rect.attr("x", function(d) {
        return d.x; })
    .attr("y", function(d) {
        return d.y; });

 rect.exit().attr("width", 25)
    .transition()
    .attr("width", 0)
    .remove();
}
Here's the pattern:
  • Tell D3 to refer to all rectangle objects as data, then:
    • Enter our data;
    • Update the data that's changed;
    • Exit the join.
  • Remember:
    • enter() is new items
    • exit() is deleted items
    • Otherwise, we're updating

Scales and Axes

Scales: Mapping values to axes

  • D3 allows us to perform data transformations easily by mapping an input domain to an output range
  • So an input of 1000 gets us 100. What about 500?
  • Transforms like this are a really common operation in visualization

Scales: D3 Scale Linear

var myScale = d3.scale.linear();
  • This would allow us to construct a new linear scale with the default domain [0, 1] to range [0, 1] (i.e., a 1:1 mapping)

var myScale = d3.scale.linear();

myScale(1);     // 1

myScale(2);     // 2

Scales: D3 Scale Linear

We can define our own domain


var myDomainScale = d3.scale.linear().domain([0, 1000]);
  • This will not change the range, but we are now mapping the domain of 0 to 1000 onto 0 and 1

var myDomainScale = d3.scale.linear().domain([0, 1000]);

myDomainScale(1);     // 0.001

myDomainScale(2);     // 0.002

Scales: D3 Scale Linear

We can also define our own range


var myDomainRangeScale = d3.scale.linear()
                            .domain([0, 1000])
                            .range([0, 100]);

myDomainRangeScale(1);      // 0.1

myDomainRangeScale(10);     // 1

This is much easier than writing our own transformation functions

Scales: D3 Max

  • It is easy if we know in advance what is the maximum possible value in our input data. In our previous example, it was 1000
  • However, what if we do not know the maximum value
  • D3 has a max function that helps us to calculate the maximum value in an array

var inputData = [0, 50, 150, 500, 300, 1000, 900];

var maxInputData = d3.max(inputData);       // 1000
  • This means that we can re-write our previous code to

var inputData = [0, 50, 150, 500, 300, 1000, 900];

var myDomainRangeScale = d3.scale.linear()
                            .domain([0, d3.max(inputData)])
                            .range([0, 100]);

Scales: D3 Min

  • Instead of hard-coding our minimum domain value, we can use d3.min() that calculates the minimum value in an array
  • Using d3.min() we can now re-write our previous code to

var inputData = [0, 50, 150, 500, 300, 1000, 900];

var myDomainRangeScale = d3.scale.linear()
                            .domain([d3.min(inputData), d3.max(inputData)])
                            .range([0, 100]);

Scales: Mapping values to axes

Scales are functions that map values from an input domain of values to an output range of values.
 

Quantitative
(see http://bit.ly/2iTNqUu)
Linearf(x) = x + a
Logf(x) = log(x)
Powerf(x) = x ^ 2
Identityf(x) = x
Ordinal
(see http://bit.ly/2io4j6m)
This code setup:

var myScale = d3.scale.ordinal()
          .domain(["Health", "IT", "Energy"])
          .rangePoints([0, some_length]);
implies the following:

Scales: Mapping values to axes

  • Quantitative scales have a continuous domain, such as real numbers, times, and dates
  • Ordinal scales are for non-quantitative and discrete domains, such as categories, names, and colours
  • With D3 scales we can easily re-size our input data to fit into our pre-defined SVG coordinate space and we no longer need to re-size our SVG coordinate space to fit our input data

D3 Axes

  • Once you have your input data and scales, you can easily add axes to your graph using D3 Axis Component
  • D3 Axis Component helps you to
    • Draw the vertical axis line, the vertical axis ticks as well as the appropriate spacing between the axis ticks
    • Also draw the horizontal axis line, the horizontal axis ticks as well as the appropriate spacing between the axis ticks

D3 Axes

  • To define an axis in D3, we can simply type
var xAxis = d3.svg.axis();
  • If we remember back our D3 scale, we can re-scale the default axis by passing the scale variable to the scale function

var inputData = [0, 50, 150, 500, 300, 1000, 900];

var axisScale = d3.scale.linear()
                    .domain([d3.min(inputData), d3.max(inputData)])
                    .range([0, 400]);

var xAxis = d3.svg.axis()
                .scale(axisScale);

Adding Axis to our Graph


// create our SVG container and viewport
var svg = d3.select("body")
            .append("svg")
            .attr("width", 400)
            .attr("height", 400)

var inputData = [0, 50, 150, 500, 300, 1000, 900];

// create the scale for the axis
var axisScale = d3.scale.linear()
                    .domain([d3.min(inputData), d3.max(inputData)])
                    .range([0, 400]);

// create the axis
var xAxis = d3.svg.axis()
                .scale(axisScale);
  • Now to add the axis we use the .call() operator

var xAxisGroup = svg.append("g")
                    .call(xAxis);

Enter/update and scales

Apple stock prices. Yes - I wish I had invested, too.

Linear Logarithmic Power

Creating an area chart

D3's shape generators to the rescue!

// x: our x scale. y: our y scale
var area = d3.svg.area()
            .interpolate("monotone")
            .x(function(d) {
                return x(d.date);
            })
            .y0(height)
            .y1(function(d) {
                return y(d.price);
            });

Now create an SVG path:


     focus.append("path")
     .datum(data)
     .attr("class", "fc area")
     .attr("d", area);
 

The real-world D3 workflow

  • Grab the data
  • Analyze the data to create the scales
  • Use the scales applied to data to create shapes
  • Add the shapes to the DOM
  • Attempt fancy stuff last

Let's Create a Scatter Plot

Scatter Plot in D3

  • Let's first load up our data

function loadAndDisplayData(placement, w, h) {
    d3.json("assets/data/chocolate.json", function (data) {
    });
}
  • Then we start creating our SVG container and their variables

function loadAndDisplayData(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
    d3.select(placement).html("");

    d3.json("assets/data/chocolate.json", function (data) {
        data = data.chocolates;

        var svg = d3.select(placement).append("svg").attr("width", width)
                    .attr("height", height).append("g")
                    .attr("transform",
                    "translate(" + margins.left + "," + margins.top + ")");
    });
}
  • Now let's remember back our scales and axes

Scatter Plot in D3


function loadAndDisplayData(placement, w, h) {
    var width = w, height = h, margin = {top: 30, right: 10, bottom: 10, left: 10};
    d3.select(placement).html("");

    d3.json("assets/data/chocolate.json", function (data) {
        data = data.chocolates;

        var svg = d3.select(placement).append("svg").attr("width", width)
                    .attr("height", height).append("g")
                    .attr("transform",
                    "translate(" + margins.left + "," + margins.top + ")");

        var x = d3.scale.linear()
                    .domain(d3.extent(data, function (d) {
                        return d.price;
                    }))
                    .range([0, width - margin.left - margin.right]);
        var y = d3.scale.linear()
                    .domain(d3.extent(data, function (d) {
                        return d.rating;
                    }))
                    .range([height - margin.top - margin.bottom, 0]);
    });
}
  • We are creating the scales using the min and max values in the array
  • d3.extent(array[,accessor]) returns the minimum and maximum value in the given array using natural order. This is the same as calling d3.min() and d3.max() simultaneously.

Scatter Plot in D3

  • Now we add the axis for our scatter plot

function loadAndDisplayData(placement, w, h) {
    ...

    var x = d3.scale.linear()
            .domain(d3.extent(data, function (d) {
                return d.price;
            }))
            .range([0, width - margin.left - margin.right]);
    var y = d3.scale.linear()
            .domain(d3.extent(data, function (d) {
                return d.rating;
            }))
            .range([height - margin.top - margin.bottom, 0]);

    var xAxis = d3.svg.axis().scale(x).orient("bottom").tickPadding(2);
    var yAxis = d3.svg.axis().scale(y).orient("left").tickPadding(2);

    svg.append("g").attr("class", "x axis").attr("transform",
                "translate(0," + y.range()[0] + ")").call(xAxis);
    svg.append("g").attr("class", "y axis").call(yAxis);

    svg.append("text").attr("fill", "#414241").attr("text-anchor", "end")
        .attr("x", width / 2).attr("y", height - 35).text("Price in pence (£)");
    });
}

Scatter Plot in D3

  • Once the axes have been added, we can now start plotting our graph

function loadAndDisplayData(placement, w, h) {
    var width = w, height = h,
        margin = {"left": 40, "right": 30, "top": 30, "bottom": 30};
    var colors = d3.scale.category10();
    d3.select(placement).html("");

    d3.json("assets/data/chocolate.json", function (data) {
        data = data.chocolates;
        ...

        var chocolate = svg.selectAll("g.node")
                            .data(data, function (d) { return d.name; });

        var chocolateEnter = chocolate.enter().append("g").attr("class", "node")
                                    .attr('transform', function (d) {
                                        return "translate(" + x(d.price) +
                                                    "," + y(d.rating) + ")";
                               });

        chocolateEnter.append("circle").attr("r", 5).attr("class", "dot")
                .style("fill", function (d) { return colors(d.manufacturer); });


        chocolateEnter.append("text").style("text-anchor", "middle").attr("dy", -10)
                .text(function (d) { return d.name; });
    });
}

Giving us this

You can access all the code for this here, with commentary!

Now that we have our base, we can do all other sorts of exciting stuff! Like include mouse overs, zoom, brushing, and animation!

Mouse Events

I want to click on things...

Mouse Event Types


myItem.on("mouseover",function (d) {
        // do something on mouseover
    }).on("mouseout", function (d) {
        // do something on mouseout
    }).on("click", function (d) {
        // do something on click
    }).on("mousemove", function(d) {
        // do something on mouse move
    }).on("mousedown", function(d) {
        // do something on mouse down
    }).on("mouseup", function(d) {
        // do something on mouse up (a mouse 'click' is
        // a 'mousedown' and 'mouseup' event)
    });

Attaching mouse events to our plot...

var chocolateEnter = chocolate.enter().append("g").attr("class", "node")
    .attr('transform', function (d) {
        return "translate(" + x(d.price) + "," + (height + 100) + ")";
    });
...

For each node, we have a circle and a text item.

// add a circle
chocolateEnter.append("circle")
            .attr("r", 5)
            .attr("class", "dot")
            .style("fill", function (d) {
                return colors(d.manufacturer);
            });

// add text
chocolateEnter.append("text")
            .style("text-anchor", "middle")
            .attr("dy", -10)
            .text(function (d) {
                return d.name;
            })

Attaching mouse events to our plot...

chocolateEnter.on("mouseover",function (d) {
        d3.select(this).style("stroke-width", "1px").style("stroke", "white");
    }).on("mouseout", function (d) {
        d3.select(this).style("stroke", "none");
    }).on("click", function(d) {
        alert("Hi, you clicked on " + d.name);
    });

See here for example source code

Putting it together: Apple Stocks

Zooming

Show me things, close up...


Zooming

In D3, it is pretty easy once you start thinking about things in terms of transforms and scales


var zoom = d3.behavior.zoom() // we first define our zoom behaviour
            .x(x) // assign our x scale
            .y(y) // assign our y scale
            .scaleExtent([1, 5]) // how far we can scale in or out
            .on("zoom", function() { // what happens when we zoom
                area.x(function(d) { return x(d.date) * d3.event.scale; })
                    .y1(function(d) { return y(d.price) * d3.event.scale; });

                myPath.attr("d", area);              // change the path's data
                mySVG.select('.y.axis').call(yAxis); // update the axes
                mySVG.select('.x.axis').call(xAxis); //
            });

// finally make our main chart aware of the zoom function
mySVG.call(zoom);

Brushing

Select items and tell me more...

What is brushing

Essentially the selection of data elements!

Focus and Context brushing

Focus and Context brushing - how?

D3 has brushes built-in. Create one like so:


// create a d3 brush object and tell it about our x scale
var brush = d3.svg.brush().x(xScale).on("brush", brushed);
We'll come to that brushed function soon. Add the brush to the DOM as an SVG rectangle:

context.append("g")
        .attr("class", "x brush")
        .call(brush)
        .selectAll("rect")
        .attr("y", -6)
        .attr("height", brush_height);

Here's the brushed function that's called upon brushing.

The context's domain is used to define the focus's domain


function brushed() {
    xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
    focus.select(".area").attr("d", area);
    focus.select(".x.axis").call(xAxis);
}

brush.extent() returns the [min, max] domain values:

Animation

Make things dance...

D3 has a transition framework and does the hard stuff for you

  • Chain a transition() before a modification:
    element.transition().style("fill","red");
  • Bouncing balls example

    
    function call_me_every_two_seconds() {
     circles.transition().duration(1000).ease('cubic-in-out')
      // interpolate y-position
      .attr('cy', function(d) { return 20 + (Math.random() * 80); })
      // interpolate colour
      .style("fill",function() {
       return "hsl(" + Math.random() * 360 + ",100%,50%)";
      });
    }
    

Animation final notes

  • Experiment with easing!
    .ease('cubic-in-out')
    .ease('bounce-out')
  • Not everything can be interpolated
    • For example, we cannot interpolate the creation of an element (i.e., whether it exists of not)
  • Sometimes animation may not be useful
  • In visualization, utility comes first

For more information, see: http://bit.ly/2i2acoG

Having said that: Some fun with animations

Connect4 anyone?

All with D3 and transitions... check it out at http://goo.gl/jEMIUe

How do we do it?

Transitions of course!


var circle = connect4_svg.append("circle")
                        .attr("class", "row-" + lastRow + " token-" + tokenCount)
                        .attr("r", 25).style("fill", function () {
                         return red ? "#e74c3c" : "#f1c40f"
                        }).attr("cx", 0).attr("cy", -100);

d3.select("circle.token-" + tokenCount)
    .transition()
    .duration(100)
    .delay((tokenCount + 1) * 500)
    .attr("cx", item.x);

When finished, we remove everything, row by row:


    emptyGrid: function () {
    for (var rowIndex = 5; rowIndex >= 0; rowIndex--) {
        d3.selectAll("circle.row-" + rowIndex)
            .transition().duration(1000)
            .delay(25000 - (rowIndex * 50)).ease("elastic").attr("cy", 600);
    }
}

Life-cycle of Transition

  • When the transition is scheduled
  • The start of the transition
  • The transition runs
  • When the transition finishes

Practical Assignment

Parallel Coordinates Plots

You are required to take a data set with a large number of variables and render it using parallel coordinates plots.

We have covered all that will be needed to implement the parallel coordinates plots in these lectures...


There are three levels:

  • Level 1: Simple Adaptation.
  • Level 2: Competent Tool Development.
  • Level 3: Problem Solving with a Personal Design.


Please see the course materials for more information (see here).

Acknowledgements

Dr Eamonn Maguire

Dr Simon Walton

There are a huge number of examples online. Check them out. The best way to learn about D3 is to play with it.

Further Reading

  • Mike Dewar, Getting Started with D3, O'Reilly Media, 2012
  • Scott Murray, Interactive data visualization for the web, O'Reilly, 2013
  • David Flanagan, JavaScript: The definitive guide, O'Reilly, 2011
  • John Resig and Bear Bibeault, Secrets of the JavaScript Ninja, Manning, 2013

Also check out the following links...