Data visualization workshop

Shirley Wu

(@sxywu)

You will learn:

SVG Elements

rect
x: x-coordinate of top-left
y: y-coordinate of top-left
width
height

circle
cx: x-coordinate of center
cy: y-coordinate of center
r: radius

text
x: x-coordinate
y: y-coordinate
dx: x-coordinate offset
dy: y-coordinate offset
text-anchor: horizontal text alignment

Hi!

path
d: path to follow

Moveto, Lineto, Curveto, Arcto

SVG Paths

Scales

  d3.scaleLinear()
    .domain([min, max]) // input
    .range([min, max]); // output

scale: mapping from

data attributes (domain)

to display (range)

 

 

date → x-value

value → y-value

value → opacity

etc.

Scales

// get min/max
var height = 600;
var data = [
    {date: new Date('01-01-2015'), temp: 0},
    {date: new Date('01-01-2017'), temp: 3}
];

var min = d3.min(data, d => d.date);
var max = d3.max(data, d => d.date);

// or use extent, which gives back [min, max]
var extent = d3.extent(data, d => d.date);

var yScale = d3.scaleLinear()
  .domain([min, max])
  .range([height, 0]);

Scales

// continuous
d3.scaleLinear()
d3.scaleLog()
d3.scaleTime()

// ordinal
d3.scaleBand()

Scales I use often:

Selection & Data

  <svg>
    <rect />
    <rect />
    <rect />
    <rect />
    <rect />
  </svg>
  <script>
    const height = 600;
    const rectWidth = 100;
    const data = [100, 250, 175, 200, 120];
    
    d3.selectAll('rect')
    	.data(data)
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

Selection & Data

5 rectangle elements

Select all rectangle elements that exist

  <svg>
    <rect />
    <rect />
    <rect />
    <rect />
    <rect />
  </svg>
  <script>
    const height = 600;
    const rectWidth = 100;
    const data = [100, 250, 175, 200, 120];
    
    d3.selectAll('rect')
    	.data(data)
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

"Bind" data to the selections

Selection & Data

  <svg>
    <rect />
    <rect />
    <rect />
    <rect />
    <rect />
  </svg>
  <script>
    const height = 600;
    const rectWidth = 100;
    const data = [100, 250, 175, 200, 120];
    
    d3.selectAll('rect')
    	.data(data)
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

Loop through each rectangle selection

Get passed in (data, index)

Selection & Data

  <svg>
    <rect />
    <rect />
    <rect />
    <rect />
    <rect />
  </svg>
  <script>
    const height = 600;
    const rectWidth = 100;
    const data = [100, 250, 175, 200, 120];
    
    d3.selectAll('rect')
    	.data(data)
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

Enter-append

  <svg></svg>
  <script>
    var rectWidth = 100;
    var height = 300;
    var data = [100, 250, 175, 200, 120];
    
    var svg = d3.select('svg');
    svg.selectAll('rect')
    	.data(data)
    	.enter().append('rect')
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

Wut, no rectangle elements?!

So what are we even selecting?

A: an empty selection

So how are those bars appearing?

Enter-append

  <svg></svg>
  <script>
    var rectWidth = 100;
    var height = 300;
    var data = [100, 250, 175, 200, 120];
    
    var svg = d3.select('svg');
    svg.selectAll('rect')
    	.data(data)
    	.enter().append('rect')
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

Magic ✨

Enter-append

  <svg></svg>
  <script>
    var rectWidth = 100;
    var height = 300;
    var data = [100, 250, 175, 200, 120];
    
    var svg = d3.select('svg');
    svg.selectAll('rect')
    	.data(data)
    	.enter().append('rect')
    	.attr('x', (d, i) => i * rectWidth)
    	.attr('y', d => height - d)
    	.attr('width', rectWidth)
    	.attr('height', d => d)
    	.attr('fill', 'blue')
    	.attr('stroke', '#fff');

  </script>

Let's make a flower

Type of petal: parental guidance rating

Size of flower: IMDb rating out of 10

Number of petals: number of IMDb votes

 

  1. Create scale (scaleLinear and scaleQuantize)
  2. Get data ready, and use the first movie data
  3. Create number of petals based on data
  4. Rotate and scale petals

 

Starter code

Complete code

Hint: use SVG transform's translate, rotate, and scale (in that order)

nesting elements

Nest elements when you want to apply the same styles and transforms to a group of elements

<g> element that consist of petals and leaves, has translate and scale applied

<path> element for each petal with rotate applied

nesting elements

const data = [
  {fill: 'red', circle: [2, 25, 50]},
  {fill: 'blue', circle: [10, 100, 150]},
];

const svg = d3.select("body").append("svg")
  .attr("width", 960)
  .attr("height", 500)

const groups = svg.selectAll('g')
  .data(data).enter().append('g')
  .attr('fill', d => d.fill)
  .attr('transform', 'translate(10,10)');

const circles = groups.selectAll('circle')
  .data(d => d.circle).enter().append('circle')
  .attr('cx', d => d)
  .attr('r', 10);

nesting elements

const data = [
  {fill: 'red', circle: [2, 25, 50]},
  {fill: 'blue', circle: [10, 100, 150]},
];

const svg = d3.select("body").append("svg")
  .attr("width", 960)
  .attr("height", 500)

const groups = svg.selectAll('g')
  .data(data).enter().append('g')
  .attr('fill', d => d.fill)
  .attr('transform', 'translate(10,10)');

const circles = groups.selectAll('circle')
  .data(d => d.circle).enter().append('circle')
  .attr('cx', d => d)
  .attr('r', 10);

Use <g> element to nest child elements *in SVG, only <g> elements can have children

nesting elements

const data = [
  {fill: 'red', circle: [2, 25, 50]},
  {fill: 'blue', circle: [10, 100, 150]},
];

const svg = d3.select("body").append("svg")
  .attr("width", 960)
  .attr("height", 500)

const groups = svg.selectAll('g')
  .data(data).enter().append('g')
  .attr('fill', d => d.fill)
  .attr('transform', 'translate(10,10)');

const circles = groups.selectAll('circle')
  .data(d => d.circle).enter().append('circle')
  .attr('cx', d => d)
  .attr('r', 10);
const data = [
  {fill: 'red', circle: [2, 25, 50]},
  {fill: 'blue', circle: [10, 100, 150]},
];

const svg = d3.select("body").append("svg")
  .attr("width", 960)
  .attr("height", 500)

const groups = svg.selectAll('g')
  .data(data).enter().append('g')
  .attr('fill', d => d.fill)
  .attr('transform', 'translate(10,10)');

const circles = groups.selectAll('circle')
  .data(d => d.circle).enter().append('circle')
  .attr('cx', d => d)
  .attr('r', 10);

nesting elements

const data = [
  {fill: 'red', circle: [2, 25, 50]},
  {fill: 'blue', circle: [10, 100, 150]},
];

const svg = d3.select("body").append("svg")
  .attr("width", 960)
  .attr("height", 500)

const groups = svg.selectAll('g')
  .data(data).enter().append('g')
  .attr('fill', d => d.fill)
  .attr('transform', 'translate(10,10)');

const circles = groups.selectAll('circle')
  .data(d => d.circle).enter().append('circle')
  .attr('cx', d => d)
  .attr('r', 10);

nesting elements

make all the flowers!

Type of petal: parental guidance rating

Size of flower: IMDb rating out of 10

Number of petals: number of IMDb votes

 

  1. Create scales like before
  2. Create group containers for each flower
  3. For each flower, calculate number of petals and draw them like before

 

Starter code

Complete code

Data visualization workshop

By Shirley Wu

Data visualization workshop

Developed for Nodefest Tokyo, updated for JSConf Asia.

  • 5,696