Data Visualization 

with Responsive D3.js

http://taurusj.slides.com/taurusj/deck-1-5-9

Slides:

github:

git clone https://github.com/taurusj-k/strangeLoop

2004

2017

Front End Developer
Flash Developer

consulting

Data visualization back then

The vinzer project

Flash.Is.Dead

Why was it so popular?

  • Simple to use and animate
  • Vector graphic & streaming video
  • Browsers were simply NOT there to compete

What is D3?

Data

Driven

Documents

D3 is NOT

  • Chart Library

  • render things for you

  • for IE8 & below (use sizzle for compatibility)

 

D3 is...

  • a combination of building blocks (modules)
  • great tool for mapping data to the DOM
  • transform the data
  • Smooth transition between UI states

D3 in the web

There are many websites built based on D3

    D3 Set up

*I use bower to install all packages

<script src="https://d3js.org/d3.v4.min.js"></script>
  • Minified version
  • NPM
npm install d3
<script src="https://d3js.org/d3.v4.min.js">
<script src="https://d3js.org/d3-time.v1.min.js"></script>
<script>
  var day = d3.timeDay(new Date);
</script>
  • Default bundle
import { extent } from "d3-array";

    Using modules vs default bundle

  • Module
  • Function

What do you need to know ?

Let's make a chart

 Initial visualization decision based on data

  • What is independent variable?
  • What is dependent variable?
State Population
Vermont 625741
Rhode Island 1052567
New Hampshire 1316470
Massachusetts 6547629
Maine 1328361
Connecticut 3574097

define SVG viewport

define axis

Bind data

SVG rectangles

Scale data

0

6.500.000

5.500.000

4.500.000

3.500.000

2.500.000

1.500.000

500.000

set domain

names of the  states

set range

width of the chart  

choose data representation

bar chart

Find data

Sections

Data

Selections

Data Join

SVG

Scales

Axis

Responsive

Complete chart

Final result is here

Population of New England by state

Data

Selections

Data Join

SVG

Scale

Axis

Responsive

<svg width="300" height="300">
  <rect x="25" y="50" width="50" height="200" />
  <rect x="75" y="50" width="50" height="200" />
  <rect x="125" y="50" width="50" height="200" />
</svg>

Data

Data can be represented in many ways

as an array
use length of myData to create rectangles 
var myData = [25, 75, 125];
or as SVG rectangles that use myData for x
renders like this
Human readable data interchange

D3 and JSON

 JS object
 JSON object
var myData = {x: 5, "y!": 5};
var myData = {"x": 5, "y": 5};

vs.

both of them contain a “key: value” pair

key in JSON must be enclosed in double quotes.

in JS object key needs "" only when you use a special character

Why is that so important to keep data in json object?

D3 and JSON

  • XMLHttpRequest
  • text file
  • JSON
  •  HTML
  • XML
  • CSV file
  •  TSV file

D3 can read all these formats, as long and you can construct an array

var myData = [{
            "name": " Massachusetts",
            "population": 6547629
          }];
myData.name;
in console:

// Massachusetts

3

1316470

New Hampshire

6547629

Massachusetts

19378102

New York

2

1

myData = [{
            "name": " Massachusetts",
            "population": 6547629
          },
          {
            "name": "New Hampshire",
            "population": 1316470
          },
          {
            "name": "New York",
            "population": 19378102
          }];

Selections

Data Join

SVG

Scale

Axis

Responsive

Data

Selection

select();

Selects elements and attaches data

selects only the 1st matching element in the DOM

d3.select("p");
d3.selectAll("p");

selects All matching elements in traversal order

d3.select(".chart").selectAll("p");

 Selections are nested.

Very similar to JQuery, D3 uses selector chaining

<div class="chart">
    <p>Chart will be here</p>
    <p>Soon...</p>
</div>
d3.selectAll("p");//[ [<p>], [<p>] ]
//console
zi {_groups: Array[1], _parents: Array[1]}

group

selection

p

Selection: subclass of array 

d3.selectAll("p");

selection returns a  group containing the selected element

Selection can use regular JS methods

.size();
.length();
d3.selectAll("circle").attr("r", 25);

element

class/id

attribute

d3.select(".row:first-child");
d3.select("input[type=checkbox]")
  .checked(true);

CSS3 as a selection tool

var tooltip = d3.select(".tooltip");
tooltip.on("mouseover", function(d,i) { alert("mouseover"); });

D3 can capture user events

d3.select("body").attr("class", "container").style("color", "red");
d3.attr();
d3.style();
d3.property();
d3.classed();
d3.html();
d3.text();

set or modify attributes, styles, properties, HTML or text content

Selection

Chart will be here

soon...

Chart will be here

soon...

before
after
.call();

calls a function on the selection

Control Flow:

function showAlert() {
  alert("hi");
}
var callSelection = d3.select("body").call(showAlert);

Elements can be added or removed from a selection

.append();

a new element as a last child of the selection 

Selection

d3.select("body")
    .append("div")
    .text("added a new div");
.insert();

similar to append, but can be inserted before specified element.

d3.insert("p", "div")
  .text("I am before div");
.remove();

once the selection is removed you can't put it back

svg.remove("circle");

Chart Milestone: Selection

Data Joins

SVG

Scale

Axis

Responsive

Data

Selections

<div>25</div>

<div>75</div>

<div>125</div>

Data Join

25
75
125
myData
var myData = [25,75,125];

 can represent 1 datum of the array with a div  

var myData = [1,25,75,45,150];

 bigger array == more divs  

<div>1</div>
<div>25</div>
<div>75</div>
<div>45</div>
<div>150</div>
var myData = [1,25];

 smaller array == less divs  

<div>1</div>
<div>25</div>

Data Join Terminology

var myData = [1,25,75,45,150];
var myData = [1,25];

Entered 1, 25, 75

var myData = [1,25,75];

Updated 1, 25, 75 and Entered  45, 150

Updated 1, 25 and Removed  75, 45, 150

<div>1</div>
<div>2</div>
<div>3</div>

Data Join

join specifies array of data with current selection . In d3 d stands for datum and i for the index

.data();

 Binds 1 datum with 1 element in the selection

1
2
3
myData
var myData = [1,2,3];
d3.selectAll('div').data(myData);
 returns a new update selection,  means all data points were successfully bound to the elements
selection.data();

Create a data join

1. Select an element that you want to bind to 

var dataJoin = d3.selectAll('div');
var myData = [1,2,3];
dataJoin.data(myData);

2. Attach data to a selected element

Data Join

var myData = [1,2,3];
d3.selectAll('div').data(myData);
no changes in the DOM,  why?

If you run this code in the console: 

Data Join

Data stored in __data__ property.

Data Join States

Elements for which the data previously existed

.data();
Update

Create a new element

.enter();
Enter

 Remove  an element 

 

.exit();
Exit
Different types of selections based on a state

Data Join

Once new selection was returned we can call .enter() or .exit()

var bar = d3.selectAll(".bar").data(data);
bar.enter().append("div").attr("class", "bar");

Data Join

exit selection is determined by the previous selection. Extremely useful for animation. When animation is done -> remove an element.

bar.exit().remove();

General Visualization Pattern

d3.selectAll('div')
.data(data)
.enter()
.append("div");

check Mike Bostock's General Update Pattern

 select the element

bind the data

create a placeholder
return a new selection containing the element

DOM Elements Based On Data

var data = [10,20,30];

1. Define the data

d3.selectAll(".bar")
       .data(data)
       .enter()
       .append("div")
       .text(function(d, i){
         return d;
       }); 

2. Select the element

3. Bind data to the element

4. Create a DOM element

5. Output the data on the screen

Chart Milestone

var data = [
    {name: 'Vermont', value: 625741},
    {name: 'Rhode Island', value: 1052567},
    {name: 'New Hampshire', value: 1316470},
    {name: 'Massachusetts', value: 6547629},
    {name: 'Maine', value: 1328361},
    {name: 'Connecticut', value: 3574097}
];
d3.select("body")
   .data(data)
   .enter()
   .append("div")
   .text(function(d){
      return d.name;
});

SVG

Scale

Axis

Responsive

Data

Selections

Data Join

<svg xmlns="http://www.w3.org/2000/svg"
     width="500" height="40" viewBox="0 0 500 40">

  <text x="0" y="35" font-family="Verdana" font-size="35">
    Hello, out there
  </text>
</svg>

SVG

SVG stands for scalable vector graphic, defined in XML file.

 

Hello, out there

<svg width="300" height="300">
  <rect x="50" y="50" width="150" height="150"/>
</svg>
<svg width="300" height="300">
  <path d="M0,0 150,0 150,50 0,50" style="fill:red;" />
</svg>

SVG basic shapes

Path  is a shape to build all shapes. "d" attribute has "path mini language

renders as

renders as

Static SVG

<svg width="300" height="300">
    <rect x="50" width="20" y="150" height="200"></rect>
    <rect x="100" width="20" y="150" height="200"></rect>
    <rect x="150" width="20" y="150" height="200"></rect>
</svg>
Basic STATIC rectangle in HTML
Renders as

what is you need to update an attribute for each rect?

SVG & <g>

<g> element  used for grouping elements and transforming them as a unit

<svg width="300" height="300">
  <rect x="50" width="20" y="150" height="200" fill="red"></rect>
  <rect x="100" width="20" y="150" height="200" fill="red"></rect>
</svg>
all children will get the style
<svg width="300" height="300">
  <g fill="red">
     <rect x="50" width="20" y="150" height="200"></rect>
     <rect x="100" width="20" y="150" height="200"></rect>
  </g>
</svg>
We can wrap rectangles into  <g> element, apply styles to it  

D3 can render SVG

D3 can generate SVG basic shapes on the fly

var svg = d3.select("body")
            .append("svg")
            .attr("width", "300")
            .attr("height", "300");
define SVG container
svg.append("rect")
      .attr("x", 150)
      .attr("width", 50)
      .attr("y", 150)
      .attr("height", 200)
      .attr("fill", "pink");
attach rectangle via D3
Renders as
var svg = d3.select("body")
            .append("svg")
            .attr("width", "300")
            .attr("height", "300");

svg.append("g").attr("fill", "red")
               .append("circle")
               .attr("cx", "50")
               .attr("cy", "50")
               .attr("r", "25");

D3 & <g>

Use D3  to create elements and apply styles on the fly

Renders as

SVG viewport

D3 and SVG coordinate space

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

var circle = svg.append("circle")
                .attr("cy", 5)
                .attr("cx", 5)
                .attr("r", 25);
SVG generated via D3 starts drawing from the top left corner
Basic shape is visible Only within SVG dimensions

y

x

Circle starts drawing x:5 y:5 of SVG viewport

0

100

100

50

0

100

100

50

svg.append("g")
    .attr("transform", "translate(50,50)")
    .append("circle")
    .attr("cx", "50")
    .attr("cy", "50")
    .attr("r", "25");

translate moves svg by 50 pixels

D3 & SVG group transform

apply "transform", "translate(50,50)"

to move a circle 50px down and 50 px to the right

define a new coordinate system by applying transform property

50

50

0

600

500

SVG viewport

0

600

500

new drawing area

SVG + margin

0

Margin conversion

apply margin conversion to SVG viewport to avoid cutting elements
var margin = {top: 20, right: 20, bottom: 30, left: 60},
    width = 600 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
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 + ")");

Margin Conversion Via D3

Define margin, width and height

apply attribute transform to <g> element to give a space to render svg elements

data = [{"x": 5, "y": 5},{"x": 50, "y": 50},{"x": 100, "y": 100}];

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

svg.selectAll(".bar")
   .data(data)
   .enter().append("rect")
                .attr("rx", 5)
                .attr("ry", 5)
                .attr("x", function(d) { return d.x; })
                .attr("width", 20)
                .attr("y", function(d) { return d.y; })
                .attr("height", 50);

Dynamic SVG

D3 can generate SVG based on data

Chart Milestone: Draw SVG

Scales

Axis

Responsive

Data

Selections

Data Join

SVG

think of maps 100 km: 1 point

Scales

maps a dimension of abstract data to a visual representation

DOMAIN is what we are mapping From :100km

RANGE is what we are mapping TO: 1 point

Scales

var data = [625741, 1052567, 1316470];

range

domain

0

5

10

domain:[625741, 1052567, 1316470];
range:[0,5,10];

Scales

how to find min and max of the data points? 
return array of min and max 
.extent();
.max();
returns max number
var data = [1, 2, 3, 5, 20, 57, 1];
d3.extent(data); //[1, 57]
var data = [1, 2, 3, 5, 20, 57, 1];
d3.max(data); //[57]

Types of scales

Continuous (Linear, Power, Log, Identity, Time)

Ordinal (Band, Point, Category)

map a continuous, quantitative input domain to a continuous output range.
discrete input domains, such as names or categories.

Other (Sequential Quantize Quantile Threshold)

Continuous Scale: Linear

var data = [
    {name: 'Rhode Island', value: 1052567},
    {name: 'Maine', value: 1328361}
];
for continuous numerical input
1052567           1328361

range

domain

0

500

height of SVG

Linear Scale

var y = d3.scaleLinear()
    .range([500, 0]);
for continuous numerical domain
with the unit domain [0, 1], the unit range [0, 1],
d3.scaleLinear();
can be any number
.domain();
output range is continuous and numeric
.range();

Linear Scale

Range: use fixed numbers or define the range based on screen size
var data = [
    {name: 'Rhode Island', value: 1052567},
    {name: 'Maine', value: 1328361}
];
var y = d3.scaleLinear()
    .range([500, 0]);

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

//console
y(600000);
285.7142857142857
Domain: from 0 to the maximum value of data.value

Ordinal scale: band

it's an ordinal scale that takes discrete input such as names, numeric output

Rhode Island

Maine

var data = [
    {name: 'Rhode Island', value: 1052567},
    {name: 'Maine', value: 1328361}
];

1

2

output

Band scales:

constructs a scale with an empty domain and range
d3.scaleBand();
takes discrete values such as names of the states. 
.domain();
output range is continuous and numeric
.range();
d3.scaleBand()
    .domain(["Rhode Island", "Maine"])
    .range([0, 100]);
it's an ordinal scale that takes discrete input such as names, numeric output

Band scales

output separated into equal "bands" with some padding in between
for blank space between bands
.padding(0.02);

Rhode Island

Maine

d3.scaleBand()
    .domain(["Rhode Island", "Maine"])
    .range([0, 100]);

Band

Band

Band scales

how to set up domain and range from the data?
var data = [
    {name: 'Rhode Island', value: 1052567},
    {name: 'Maine', value: 1328361}
];
var x = d3.scaleBand()
    .range([0, width])
    .padding(0.02);

x.domain(data.map(function(d) { return d.name; }));
Range: use fixed numbers or define the range based on screen size
Domain: names of the state

Category scales:

Constructs a new ordinal scale with a range categorical colors
.schemeCategory10
.schemeCategory20
.schemeCategory20b
.schemeCategory20c
1. Category scales use d3-scale-chromatic.module 
2. Use set of colors provided by Color Brewer

Category scales

D3 ordinal scale with a range of categorical colors
var color = d3.scaleOrdinal(d3.schemeCategory10);

svg.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    .attr("fill", function(d,i) { return color(i % data.length); });

Chart milestone: Scale SVG

Axis

Responsive

Data

Selections

Data Join

SVG

Scale

Axis

Earlier I claimed that D3 is not good for rendering things for you...
Well, D3 can provide axis for you!
1. Set up a container for axis (SVG  viewport)
2. Define a scale with a range & domain.
3. Append axis to svg

Axis

var margin = {top: 20, right: 20, bottom: 30, left: 60},
    width = 600 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
1. Set up a container for axis (SVG  viewport)

Axis

var x = d3.scaleBand().range([0, width]).padding(0.02);

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

var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
2. Use scale to transform the data for axis

Axis

  svg.append("g")
      //add classes for styling
      .attr("class", "x_axis")
      //move xAxis: x: 0 y:from the top down to the height
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);
 
 svg.append("g")
      //add classes for styling
      .attr("class", "y_axis")
      //we don't want to shift up or down
      .call(yAxis);
3. Call axis when you create SVG

Axis

Axis component  has some useful methods:
.axisTop();

D3 reserves the right to override the number of each tick if the number won't come out clean after scaling

.axisBottom();
.axisLeft();
.axisRight();
.axis.ticks();

Chart Milestone: Add Axis

Let's make it responsive

Data

Selection

Data Join

SVG

Scales

Axis

Responsive charts

most visualizations are built for large screens

the refugee project or mayoral race campaign

 

Responsive charts

Option one: Make SVG responsive

preserveAspectRatio="xMinYMin meet" viewBox="0 0 800 960"

Option two: SVG re-draw depends on the screen size

//get dimensions based on window size
updateDimensions(window.innerWidth);

function updateDimensions(winWidth) {
  width = winWidth - margin.left - margin.right;
  height = .5 * width; //aspect ratio is 0.5
}

Responsive charts

Render the chart on resize
1. Create a render function that re-calculates height and width based on screen size
function render() {
    /* Define a new width and height */
    ...
}

window.addEventListener('resize', Chart.render);
2. Add an event listener to re-render the chart when event is fired

Responsive charts

DON'T use fixed height
Use aspect ratio instead of px  
height = .4 * width; //aspect ratio is 0.4
use breakpoints to modify visualization
var breakPoint = 768;
// in render function  
if(width < breakPoint) {
    ...
} else {
    ...
}

Final result is here

Where to go from here?

Load data from the server using Python

Try D3 layouts
Hierarchy

Force

Map

References

Mike Bostock's website
Mike Bostock's blocks
Building Responsive Data Visualizations with D3.js
DashingD3js Screencasts
D3.js tutorials

Thanks!

Iuliia Amodeo Konivetska

taurusj.k2@gmail.com

@taurusJ_K

Data Visualization with responsive D3.js

By Iuliia Konivetska

Data Visualization with responsive D3.js

Strange loop workshop

  • 1,205