1 of 19

Interactive Data Visualization

Advanced D3 Layouts

http://romain.vuillemot.net/

@romsson

2 of 19

Advanced Layouts

3 of 19

Histogram

var histogram = d3.histogram()� .value(function(d) { return d.date; })� .domain(x.domain())� .thresholds(x.ticks(d3.timeWeek));�� var bins = histogram(data);�� y.domain([0, d3.max(bins, function(d) { return d.length; })]);�� var bar = svg.selectAll(".bar")� .data(bins)� .enter().append("g")� .attr("class", "bar")� .attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.length) + ")"; });�� bar.append("rect")� .attr("x", 1)� .attr("width", function(d) { return x(d.x1) - x(d.x0) - 1; })� .attr("height", function(d) { return height - y(d.length); });�� bar.append("text")� .attr("dy", ".75em")� .attr("y", 6)� .attr("x", function(d) { return (x(d.x1) - x(d.x0)) / 2; })� .attr("text-anchor", "middle")� .text(function(d) { return formatCount(d.length); });

4 of 19

Grouped bar chart

5 of 19

Stacked chart

6 of 19

Node-link Graph & Treemap

{� "nodes": [� {"id": "Myriel", "group": 1},� {"id": "Napoleon", "group": 1},� {"id": "Mlle.Baptistine", "group": 1},� {"id": "Mme.Magloire", "group": 1},� {"id": "CountessdeLo", "group": 1},� {"id": "Mme.Hucheloup", "group": 8}� ],� "links": [� {"source": "Napoleon", "target": "Myriel", "value": 1},� {"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},� {"source": "Mme.Magloire", "target": "Myriel", "value": 10},� {"source": "Mme.Magloire", "target": "Mlle.Baptistine", "value": 6},� {"source": "CountessdeLo", "target": "Myriel", "value": 1},� {"source": "Geborand", "target": "Myriel", "value": 1}� {"source": "Mme.Hucheloup", "target": "Enjolras", "value": 1}� ]�}

7 of 19

Node-link Graph

function ticked() {� link� .attr("x1", function(d) { return d.source.x; })� .attr("y1", function(d) { return d.source.y; })� .attr("x2", function(d) { return d.target.x; })� .attr("y2", function(d) { return d.target.y; });�� node� .attr("cx", function(d) { return d.x; })� .attr("cy", function(d) { return d.y; });� }�});�

8 of 19

Pie Chart

var pie = d3.pie()� .sort(null)� .value(function(d) { return d.population; });��var path = d3.arc()� .outerRadius(radius - 10)� .innerRadius(0);��var label = d3.arc()� .outerRadius(radius - 40)� .innerRadius(radius - 40);��d3.csv("data.csv", function(d) {� d.population = +d.population;� return d;�}, function(error, data) {� if (error) throw error;�� var arc = g.selectAll(".arc")� .data(pie(data))� .enter().append("g")� .attr("class", "arc");�� arc.append("path")� .attr("d", path)� .attr("fill", function(d) { return color(d.data.age); });

9 of 19

Pie Chart

var arc = d3.arc();��arc({� innerRadius: 0,� outerRadius: 100,� startAngle: 0,� endAngle: Math.PI / 2�}); // "M0,-100A100,100,0,0,1,100,0L0,0Z"

var arc = d3.arc()� .innerRadius(0)� .outerRadius(100)� .startAngle(0)� .endAngle(Math.PI / 2);�

10 of 19

Treemap

var treemap = d3.treemap()� .tile(d3.treemapResquarify)� .size([width, height])� .round(true)� .paddingInner(1);�� var root = d3.hierarchy(data)� .eachBefore(function(d) { d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })� .sum(sumBySize)� .sort(function(a, b) { return b.height - a.height || b.value - a.value; });�� treemap(root);�� var cell = svg.selectAll("g")� .data(root.leaves())� .enter().append("g")� .attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; });�� cell.append("rect")� .attr("id", function(d) { return d.data.id; })� .attr("width", function(d) { return d.x1 - d.x0; })� .attr("height", function(d) { return d.y1 - d.y0; })� .attr("fill", function(d) { return color(d.parent.data.id); });�

11 of 19

Treemap

var d3Hierarchy = require("d3-hierarchy")

var treemap = d3Hierarchy.treemap()

.size([10, 10])

.padding(10);

var stratify = d3Hierarchy.stratify()

.id(function(d) { return d.id; })

.parentId(function(d) { return d.parent_id; });

var root = stratify([{"id": "root", "parent_id": ""}, {"id": "child", "parent_id": "root"}]);

var tree = treemap(root);

tree.leaves()

Array (1 item)

0: Node

data: Object {id: "child", parent_id:"root"}

depth: 1

height: 0

id: "child"

parent: Node {children: , data: , depth:0, height: 1, id: "root", …}

x0: 5

x1: 5

y0: 10

y1: NaN

12 of 19

Stacked bar chart

var keys = data.columns.slice(1);�� g.append("g")� .selectAll("g")� .data(d3.stack().keys(keys)(data))� .enter().append("g")� .attr("fill", function(d) { return z(d.key); })� .selectAll("rect")� .data(function(d) { return d; })� .enter().append("rect")� .attr("x", function(d) { return x(d.data.State); })� .attr("y", function(d) { return y(d[1]); })� .attr("height", function(d) { return y(d[0]) - y(d[1]); })� .attr("width", x.bandwidth());

State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over�AL,310504,552339,259034,450818,1231572,1215966,641667�AK,52083,85640,42153,74257,198724,183159,50277�AZ,515910,828669,362642,601943,1804762,1523681,862573�AR,202070,343207,157204,264160,754420,727124,407205�CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496�CO,358280,587154,261701,466194,1464939,1290094,511094�CT,211637,403658,196918,325110,916955,968967,478007

13 of 19

Transition Grouped Stacked

d3.selectAll("input").on("change", swap);

function swap() {� this.value == "grouped" ? grouped(): stacked();� }

https://blockbuilder.org/romsson/f49647330eeeebdd3487fcc3b0222d00

14 of 19

Streamgraph

var n = 20, // number of layers� m = 200, // number of samples per layer� k = 10; // number of bumps per layer��var stack = d3.stack().keys(d3.range(n)).offset(d3.stackOffsetWiggle),� layers0 = stack(d3.transpose(d3.range(n).map(function() { return bumps(m, k); }))),� layers1 = stack(d3.transpose(d3.range(n).map(function() { return bumps(m, k); }))),� layers = layers0.concat(layers1);

var area = d3.area()� .x(function(d, i) { return x(i); })� .y0(function(d) { return y(d[0]); })� .y1(function(d) { return y(d[1]); });��svg.selectAll("path")� .data(layers0)� .enter().append("path")� .attr("d", area)� .attr("fill", function() { return z(Math.random()); });�

15 of 19

Hierarchical Edge Bundling

var diameter = 960,� radius = diameter / 2,� innerRadius = radius - 120;��var cluster = d3.cluster()� .size([360, innerRadius]);��var line = d3.radialLine()� .curve(d3.curveBundle.beta(0.85))� .radius(function(d) { return d.y; })� .angle(function(d) { return d.x / 180 * Math.PI; });��var svg = d3.select("body").append("svg")� .attr("width", diameter)� .attr("height", diameter)� .append("g")� .attr("transform", "translate(" + radius + "," + radius + ")");���

var link = svg.append("g").selectAll(".link"),� node = svg.append("g").selectAll(".node");�� var root = packageHierarchy(classes)� .sum(function(d) { return d.size; });�� cluster(root);�� link = link� .data(packageImports(root.leaves()))� .enter().append("path")� .each(function(d) { d.source = d[0], d.target = d[d.length - 1]; })� .attr("class", "link")� .attr("d", line);�� node = node� .data(root.leaves())� .enter().append("text")� .attr("class", "node")� .attr("dy", "0.31em")� .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + (d.y + 8) + ",0)" + (d.x < 180 ? "" : "rotate(180)"); })� .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })� .text(function(d) { return d.data.key; })

16 of 19

Geomap & Shape files

var projection = d3.geo.mercator()� .translate([width / 2, height / 2])� .scale((width - 1) / 2 / Math.PI);��var path = d3.geo.path()� .projection(projection);��d3.json("/mbostock/raw/4090846/world-50m.json", function(error, world) {�� g.append("path")� .datum({type: "Sphere"})� .attr("class", "sphere")� .attr("d", path);�� g.append("path")� .datum(topojson.merge(world, world.objects.countries.geometries))� .attr("class", "land")� .attr("d", path);�� g.append("path")� .datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))� .attr("class", "boundary")� .attr("d", path);�});�

17 of 19

Geomap & Shape files

{"type":"Topology","objects":{"countries":�{"type":"GeometryCollection",�"Bbox":[-180,-89.99892578124998,180.00000000000003,83.59960937500006],�"geometries":[{"type":"Polygon","id":533,"arcs":[[0]]},{"type":"Polygon","id":4,"arcs":[[1,2,3,4,5,6,7]]},{"type":"MultiPolygon","id":24,"arcs":[[[8,9,10,11]],[[12,13,14]]]},{"type":"Polygon","id":660,"arcs":[[15]]},{"type":"Polygon","id":8,"arcs":[[16,17,18,19,20]]},{"type":"MultiPolygon","id":248,"arcs":[[[21]],[[22]],[[23]]]},{"type":"Polygon","id":20,"arcs":[[24,25]]},{"type":"MultiPolygon","id":784,"arcs":[[[26]],[[27]],[[28]],[[29]],[[30,31,32,33,34],[35]]]},{"type":"MultiPolygon","id":32,"arcs":[[[36]],[[37,38]],[[39]],[[40,41,42,43,44,45]]]},{"type":"MultiPolygon","id":51,"arcs":[[[46]],[[47,48,49,50,51],[52]]]},{"type":"Polygon","id":16,"arcs":[[53]]},{"type":"MultiPolygon","id":10,"arcs":[[[54]],[[55]],[[56]],[[57]],[[58]],[[59]],[[60]],[[61]],[[62]],[[63]],[[64]],[[65]],[[66]],

18 of 19

Real-life pipeline to build merge geo datasets..

#!/bin/bash��mkdir -p build��# Download.�curl -z build/estados.zip -o build/estados.zip http://mapserver.inegi.org.mx/MGN/mge2010v5_0.zip�curl -z build/municipios.zip -o build/municipios.zip http://mapserver.inegi.org.mx/MGN/mgm2010v5_0.zip��# Decompress.�unzip -od build build/estados.zip�unzip -od build build/municipios.zip��# Compute the scale and translate for 960×600 inset by 10px.�TRANSFORM=$(shp2json build/Entidades_2010_5.shp \� | ndjson-map -r d3=d3-geo 'p = d3.geoIdentity().reflectY(true).fitExtent([[10, 10], [960 - 10, 600 - 10]], d), "d3.geoIdentity().reflectY(true).scale(" + p.scale() + ").translate([" + p.translate() + "])"' \� | tr -d '"')��# shp2json - convert shapefiles to GeoJSON.�# ndjson-map - map property names and coerce numeric properties.�# geoproject - scale and translate to fit in 960×500.�# geo2topo - convert GeoJSON to TopoJSON.�# toposimplify - simplify TopoJSON.�# topoquantize - quantize TopoJSON.�geo2topo -n \� states=<(shp2json -n build/Entidades_2010_5.shp \� | ndjson-map 'd.properties = {state_code: +d.properties.CVE_ENT, state_name: d.properties.NOM_ENT}, d' \� | geoproject -n ${TRANSFORM}) \� municipalities=<(shp2json -n build/Municipios_2010_5.shp \� | ndjson-map 'd.properties = {state_code: +d.properties.CVE_ENT, mun_code: +d.properties.CVE_MUN, mun_name: d.properties.NOM_MUN}, d' \� | geoproject -n ${TRANSFORM}) \� | toposimplify -p 1 \� | topoquantize 1e5 \� > mx.json

19 of 19

Geomap & Shape files

Change projections

Change zoom level

Merge wit actual dataset

Make interactive

https://observablehq.com/@neocartocnrs/cheat-sheet-topojson