Intro to InfoVis using d3.js

John Alexis Guerra Gómez

Use spacebar and the arrows to advance slides

http://infovis.co/infovisIntro

Outline

  1. What is Infovis?
  2. Types of Visualization
  3. Infovis Basics
  4. Hands-On with D3.js

What is Information Visualization?

Data

  • Health records
  • Financial transactions
  • Facebook Social Graph
  • Tweets
  • Wikipedia edits

Data (II)

  • We know how to:
  • Store it (big table, NoSql, etc)
  • Process it (map-reduce, parallel computing)
  • But how to make sense of it?

How to make sense of data?

Data Mining/Machine Learning

Information Visualization

Visual Analytics

Traditional

  • Query for known patterns
  • Display results using traditional techniques

Pros:
  • Many solutions
  • Easier to implement

Cons:
  • Can’t search for the unexpected

Data Mining/ML

  • Based on statistics
  • Black box approach
  • Output outliers and correlations
  • Human out of the loop

Pros:
  • Scalable

Cons:
  • Analysts have to make sense of the results
  • Makes assumptions on the data

InfoVis

  • Visual Interactive Interfaces
  • Human in the loop

Pros:
  • Visual bandwidth is enormous
  • Experts decided what to search for
  • Identify unknown patterns and errors in the data

Cons
  • Scalability can be an issue

Why should we visualize?

Anscombe's quartet

Anscombe's quartet

Anscombe's visualized

In Infovis we look for insights

  • Deep understanding
  • Meaningful
  • Non obvious
  • Actionable

Types of Visualization

  • Infographics
  • Scientific Visualization (sciviz)
  • Information Visualization (infovis, datavis)

Infographics

Scientific Visualization

  • Inherently spatial
  • 2D and 3D

Information Visualization

Infovis Basics

Visualization Mantra

  • Overview first
  • Zoom and Filter
  • Details on Demand

Perception Preference

Adapted from from:Tamara Munzner Book Chapter

Data Types

1-D Linear Document Lens, SeeSoft, Info Mural
2-D Map GIS, ArcView, PageMaker, Medical imagery
3-D World CAD, Medical, Molecules, Architecture
Multi-Var Spotfire, Tableau, GGobi, TableLens, ParCoords,
Temporal LifeLines, TimeSearcher, Palantir, DataMontage, LifeFlow
Tree Cone/Cam/Hyperbolic, SpaceTree, Treemap, Treeversity
Network Gephi, NodeXL, Sigmajs

Visualization Examples

1-D linear

Wordcloud

https://www.jasondavies.com/wordcloud/

2-D Maps

Datamaps

Library for drawing maps with data and D3

http://datamaps.github.io/

US Gridmap

http://blockbuilder.org/kristw/2f628465e36f9821325d

Map of Colombia

http://blockbuilder.org/john-guerra/43c7656821069d00dcbc

Force map of Colombia

http://blockbuilder.org/john-guerra/4db5024e00a82f10a2dc

3-D World

http://bl.ocks.org/mbostock/4183330

Multivariate data

Examples

  • Census data
  • Experiment results
  • List of people taking this course
  • Almost everything actually

Small multiples + coordinated views

http://vis.stanford.edu/projects/immens/demo/splom/

More coordinated views

http://vis.stanford.edu/projects/immens/demo/brightkite/

Parallel Coordinates

http://blockbuilder.org/mbostock/1341021http://bl.ocks.org/syntagmatic/raw/3150059/

Temporal Data

Examples

  • Twitter streams
  • Credit card purchases
  • Event logs
  • Many more

Line Charts

http://blockbuilder.org/mbostock/3884955

Stacked charts

http://blockbuilder.org/mbostock/3885211

Stream graphs

https://euro2012.twitter.com/

Horizon chart

http://blockbuilder.org/mbostock/1483226

Horizon charts

https://square.github.io/cubism/demo/

Calendar view

http://bl.ocks.org/mbostock/4063318

Trees

Hands-On Tutorial

Follow tutorial here https://goo.gl/KZTCsG

Step 1: Selections


d3.select("#chart01")
    .append("p")
    .text("hola mundo");

d3.selectAll("p")
    .style("font-size", "23pt");

Step 2 Binding data


var myData = [ 22, 15, 20];

var chart = d3.select("#chart02");

var items = chart.selectAll("p")
    .data(myData);

items.enter().append("p")
    .style("font-size", function (d) {
        return d + "pt";
    })
    .text("hola mundo");

Step 3 Remove and Update


var myData = [ 22, 15, 20];

var chart = d3.select("#chart03");

var redraw = function (myData) {
    var items = chart.selectAll("p")
        .data(myData);    
    items.enter().append("p");
    
    items
        .style("font-size", function (d) {
            return d + "pt";
        })
        .text("hola mundo");
    
    items.exit().remove();
};


redraw(myData);

Step 4 SVG


var myData = [ 22, 15, 200];
var HEIGHT = 40;

var chart = d3.select("#chart04")
    .append("svg")
    .attr("width", 400)
    .attr("height", 400);

var redraw = function (myData) {
    var items = chart.selectAll("rect")
        .data(myData);    
    items.enter()
        .append("rect")
        .attr("width", 0);
    
    items
        .attr("x", 0)
        .attr("y", function (d,i) {
            return i * (HEIGHT + 1);
        })
        .attr("width", function (d) {
            return d;
        })    
        .attr("height", HEIGHT);
    
    items.exit().remove();
};


redraw(myData);

Step 5 Transitions


var myData = [ 22, 15, 200];
var HEIGHT = 20;

var chart = d3.select("#chart05")
    .append("svg")
    .attr("width", 400)
    .attr("height", 400);

var redraw = function (myData) {
    var items = chart.selectAll("rect")
        .data(myData);    
    items.enter()
        .append("rect")
        .attr("width", 0);
    
    items
        .transition()
        .duration(1000)
        .attr("x", 0)
        .attr("y", function (d,i) {
            return i * (HEIGHT + 1);
        })
        .attr("width", function (d) {
            return d;
        })    
        .attr("height", HEIGHT);
    
    items.exit().remove();
};


redraw(myData);

Step 6 Scales


var myData = [1,2,3];
var HEIGHT = 20;

var wScale = d3.scale
    .linear()
    .domain([0, d3.max(myData)])
    .range([0, 400]);
var colScale = d3.scale
    .category20();


var chart = d3.select("#chart06")
    .append("svg")
    .attr("width", 400)
    .attr("height", 400);

var redraw = function (myData) {
    var items = chart.selectAll("rect")
        .data(myData);    
    items.enter()
        .append("rect")
        .attr("width", 0)
        .style("fill", function (d) {
            return colScale(d);
        });
    
    items
        .transition()
        .duration(1000)
        .attr("x", 0)
        .attr("y", function (d,i) {
            return i * (HEIGHT + 1);
        })
        .attr("width", function (d) {
            return wScale(d);
        })    
        .attr("height", HEIGHT);
    
    items.exit().remove();
};


redraw(myData);

Step 7 Axis


var myData = [1,2,3,50,4,3,2,1];
var HEIGHT = 20;
var margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;


var wScale = d3.scale
    .linear()
    .domain([0, d3.max(myData)])
    .range([0, width]);
var yScale = d3.scale
    .linear()
    .domain([0, myData.length])
    .range([0, height]);
var xAxis = d3.svg.axis()
    .scale(wScale)
    .tickSize(5)
    .tickSubdivide(true)
    .orient("top");
var yAxis  = d3.svg.axis()
    .scale(yScale)
    .ticks(4)
    .orient("left");

var colScale = d3.scale
    .category20();


var chart = d3.select("#chart07")
    .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 + ")");


// Add the x-axis.
chart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + 0 + ")")
    .call(xAxis);

// Add the x-axis.
chart.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(0,0)")
    .call(yAxis);


var redraw = function (myData) {
    wScale.domain([0,d3.max(myData)]);
    yScale.domain([0,myData.length]);
    var items = chart.selectAll("rect")
        .data(myData);    
    items.enter()
        .append("rect")
        .attr("width", 0)
        .style("fill", function (d) {
            return colScale(d);
        });
    
    items
        .transition()
        .duration(1000)
        .attr("x", 0)
        .attr("y", function (d,i) {
            return yScale(i);
        })
        .attr("width", function (d) {
            return wScale(d);
        })    
        .attr("height", HEIGHT);
    
    items.exit().remove();
    
    chart.select(".x.axis")
        .transition()
        .duration(1000)
        .call(xAxis);
    
    chart.select(".y.axis")
        .transition()
        .duration(1000)
        .call(yAxis);    
};


redraw(myData);

Step 8 Download data


var HEIGHT = 20;
var margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;


var wScale = d3.scale
    .linear()
    .range([0, width]);
var yScale = d3.scale
    .linear()
    .range([0, height]);
var xAxis = d3.svg.axis()
    .scale(wScale)
    .tickSize(5)
    .tickSubdivide(true)
    .orient("top");
var yAxis  = d3.svg.axis()
    .scale(yScale)
    .ticks(4)
    .orient("left");

var colScale = d3.scale
    .category20();


var chart = d3.select("#chart08")
    .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 + ")");


// Add the x-axis.
chart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + 0 + ")")
    .call(xAxis);

// Add the x-axis.
chart.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(0,0)")
    .call(yAxis);


var redraw = function (myData, xAttr, yAttr) {
    myData.forEach(function (d) {
        d[xAttr] = +d[xAttr];
        d[yAttr] = +d[yAttr];
    });
    
    var xAcc = function (d) {return d[xAttr];};
    var yAcc = function (d) {return d[yAttr];};
    
    wScale.domain([0, d3.max(myData, xAcc )]);
    yScale.domain([0, d3.max(myData, yAcc )]);
    
    
    var items = chart.selectAll("circle")
        .data(myData, function (d) { return d.id; } );    
    items.enter()
        .append("circle")
        .attr("cx", 0)
        .style("fill", function (d) {
            return colScale(d.id);
        });
    
    items
        .transition()
        .duration(1000)
        .attr("cy", function (d) {
            return yScale(d[yAttr]);
        })
        .attr("cx", function (d) {
            return wScale(d[xAttr]);
        })    
        .attr("r", 3);
    
    items.exit().remove();
    
    chart.select(".x.axis")
        .transition()
        .duration(1000)
        .call(xAxis);
    
    chart.select(".y.axis")
        .transition()
        .duration(1000)
        .call(yAxis);    
};

d3.json("https://api.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=53ecac56654e39cf9d2cf52fbecdfc6b&extras=count_views%2C+count_faves%2C+count_comments&format=json&nojsoncallback=1",
        function (error, data) {
            redraw(data.photos.photo, "count_views", "count_comments");
        })

Step 9 Events


var HEIGHT = 20;
var margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var FlickrUtils = {};

FlickrUtils.getImgForPhoto = function (ID, server_ID, farm_ID, secret) {
	//TODO escape variables for security
	// return "https://farm" + farm_ID + "\.staticflickr.com/" + server_ID+ "/" + ID + "_" + secret + "_m.jpg";
    var u =  "https://farm" + Math.floor(farm_ID);
    u +=  "." + "staticflickr.com/" + server_ID+ "/" + ID + "_" + secret + "_m.jpg";
    return u;
};

FlickrUtils.getUrlForPhoto = function (ID, owner_ID) {
    //TODO escape variables for security
    return "https://www.flickr.com/photos/" + owner_ID + "/" + ID;
};


var wScale = d3.scale
    .linear()
    .range([0, width]);
var yScale = d3.scale
    .linear()
    .range([0, height]);
var xAxis = d3.svg.axis()
    .scale(wScale)
    .tickSize(5)
    .tickSubdivide(true)
    .orient("top");
var yAxis  = d3.svg.axis()
    .scale(yScale)
    .ticks(4)
    .orient("left");

var colScale = d3.scale
    .category20();


var chart = d3.select("#chart09")
    .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 + ")");


// Add the x-axis.
chart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + 0 + ")")
    .call(xAxis);

// Add the x-axis.
chart.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(0,0)")
    .call(yAxis);


var redraw = function (myData, xAttr, yAttr) {
    myData.forEach(function (d) {
        d[xAttr] = +d[xAttr];
        d[yAttr] = +d[yAttr];
    });
    
    var xAcc = function (d) {return d[xAttr];};
    var yAcc = function (d) {return d[yAttr];};
    
    wScale.domain([0, d3.max(myData, xAcc )]);
    yScale.domain([0, d3.max(myData, yAcc )]);
    
    
    var items = chart.selectAll("circle")
        .data(myData, function (d) { return d.id; } );    
    items.enter()
        .append("circle")
        .attr("cx", 0)
        .style("fill", function (d) {
            return colScale(d.id);
        })
        .on("mouseover", function (d) {
            d3.select("#image09")
            .attr("src", FlickrUtils.getImgForPhoto(d.id, 
                                                    d.server, 
                                                    d.farm, 
                                                    d.secret));
        });
    
    items
        .transition()
        .duration(1000)
        .attr("cy", function (d) {
            return yScale(d[yAttr]);
        })
        .attr("cx", function (d) {
            return wScale(d[xAttr]);
        })    
        .attr("r", 10);
    
    items.exit().remove();
    
    chart.select(".x.axis")
        .transition()
        .duration(1000)
        .call(xAxis);
    
    chart.select(".y.axis")
        .transition()
        .duration(1000)
        .call(yAxis);    
};

d3.json("https://api.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=53ecac56654e39cf9d2cf52fbecdfc6b&extras=count_views%2C+count_faves%2C+count_comments&format=json&nojsoncallback=1",
        function (error, data) {
            redraw(data.photos.photo, "count_views", "count_comments");
        })

Step 10 Extras


var HEIGHT = 20;
var margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = 500 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var FlickrUtils = {};

FlickrUtils.getImgForPhoto = function (ID, server_ID, farm_ID, secret) {
	//TODO escape variables for security
	// return "https://farm" + farm_ID + "\.staticflickr.com/" + server_ID+ "/" + ID + "_" + secret + "_m.jpg";
    var u =  "https://farm" + Math.floor(farm_ID);
    u +=  "." + "staticflickr.com/" + server_ID+ "/" + ID + "_" + secret + "_m.jpg";
    return u;
};

FlickrUtils.getUrlForPhoto = function (ID, owner_ID) {
    //TODO escape variables for security
    return "https://www.flickr.com/photos/" + owner_ID + "/" + ID;
};


var wScale = d3.scale
    .linear()
    .range([0, width]);
var yScale = d3.scale
    .linear()
    .range([0, height]);
var xAxis = d3.svg.axis()
    .scale(wScale)
    .tickSize(5)
    .tickSubdivide(true)
    .orient("top");
var yAxis  = d3.svg.axis()
    .scale(yScale)
    .ticks(4)
    .orient("left");

var colScale = d3.scale
    .category20();


var chart = d3.select("#chart10")
    .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 + ")");


// Add the x-axis.
chart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + 0 + ")")
    .call(xAxis);

// Add the x-axis.
chart.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(0,0)")
    .call(yAxis);


var redraw = function (myData, xAttr, yAttr) {
    myData.forEach(function (d) {
        d[xAttr] = +d[xAttr];
        d[yAttr] = +d[yAttr];
    });
    
    var xAcc = function (d) {return d[xAttr];};
    var yAcc = function (d) {return d[yAttr];};
    
    wScale.domain([0, d3.max(myData, xAcc )]);
    yScale.domain([0, d3.max(myData, yAcc )]);
    
    
    var items = chart.selectAll("circle")
        .data(myData, function (d) { return d.id; } );    
    items.enter()
        .append("circle")
        .attr("cx", 0)
        .style("fill", function (d) {
            return colScale(d.id);
        })
        .on("mouseover", function (d) {
            d3.select("#image10")
            .attr("src", FlickrUtils.getImgForPhoto(d.id, 
                                                    d.server, 
                                                    d.farm, 
                                                    d.secret));
        });
    
    items
        .transition()
        .duration(1000)
        .attr("cy", function (d) {
            return yScale(d[yAttr]);
        })
        .attr("cx", function (d) {
            return wScale(d[xAttr]);
        })    
        .attr("r", 10);
    
    items.exit().remove();
    
    chart.select(".x.axis")
        .transition()
        .duration(1000)
        .call(xAxis);
    
    chart.select(".y.axis")
        .transition()
        .duration(1000)
        .call(yAxis);    
};

d3.json("https://api.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=53ecac56654e39cf9d2cf52fbecdfc6b&extras=count_views%2C+count_faves%2C+count_comments&format=json&nojsoncallback=1",
        function (error, data) {

            redraw(data.photos.photo, "count_views", "count_comments");
            d3.select("#attrSelect")
            .on("change", function (d) {
                redraw(data.photos.photo, this.value, "count_comments");
            });            
        })

Resources

D3.js Website

Gallery

Tutorials

API Reference

GitHub Project

Thank You

Questions?

John Alexis Guerra Gómez

johnguerra.co
@duto_guerra