These are the materials for my workshop on creating interactive data visualizations with D3! We will be using the following two tools to works through these exercises:
And please do not hesitate to reach out to me directly via email at jondinu@gmail.com or over twitter @clearspandex
Throughout this workshop, you will learn how to make an interactive map of AirBnB listings in SF to better understand the companies impact on the city.
The data comes from Inside AirBnB and it is available below under a Creative Commons CC0 1.0 Universal (CC0 1.0) "Public Domain Dedication" license.
Inside AirBnB is an independent, non-commercial set of tools and data that allows you to explore how Airbnb is really being used in cities around the world.
Github repo: https://github.com/Jay-Oh-eN/interactive-data-viz
Archival event link: Open Data Science Conference SF 2015
forked from Jay-Oh-eN's block: Civic Impact through Data Visualization: Final
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.8/d3.min.js"></script>
<style>
body, html {
width: 100%;
height: 100%;
}
​
svg {
width:50%;
height:100%;
float: left;
}
​
circle.airbnb {
fill: #e00007;
opacity: 0.6;
}
​
.axis {
font-family: arial;
font-size: 0.7em;
}
text {
fill: black;
}
.label {
font-size: 2em;
}
path {
fill: none;
stroke: black;
stroke-width: 2px;
}
.tick {
fill: none;
stroke: black;
}
circle {
opacity: 0.9;
stroke: none;
fill: red;
}
.line {
fill: none;
stroke: #e00007;
stroke-width: 1px;
}
</style>
<script>
function draw(geo_data) {
"use strict";
​
/*
D3.js setup code
*/
​
var margin = 75,
width = 750 - margin,
height = 780 - margin;
​
// https://github.com/mbostock/d3/wiki/Time-Formatting
var format = d3.time.format("%Y-%m-%d");
var projection = d3.geo.mercator()
.center([-122.433701, 37.767683])
.scale(230000)
.translate([width / 1.95, height / 1.74]);
var path = d3.geo.path()
.projection(projection);
​
var map = d3.select('#map').selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', '#eee')
.style('stroke', 'black')
.style('stroke-width', 1);
​
map.datum(function(d) {
var normalized = d.properties.neighbourhood
.replace(/ /g, '_')
.replace(/\//g, '_');
​
d.properties.neighbourhood = normalized;
return d;
});
​
map.attr('class', function(d) {
return d.properties.neighbourhood;
});
​
d3.json("https://jay-oh-en.github.io/interactive-data-viz/data/airbnb/listing_count.json", function(data) {
var listings_extent = d3.extent(d3.values(data));
​
var bubbles = d3.select('#map').append("g")
.attr("class", "bubble")
.selectAll("circle")
.data(geo_data.features)
.enter()
.append("circle")
.attr('class', 'airbnb');
​
bubbles.datum(function(d) {
d.count = data[d.properties.neighbourhood];
return d;
});
​
var radius = d3.scale.pow().exponent(1.2)
.domain(listings_extent)
.range([3, 25]);
​
bubbles
.attr("cx", function(d) { return path.centroid(d.geometry)[0]; })
.attr("cy", function(d) { return path.centroid(d.geometry)[1]; })
.attr("r", function(d) { return radius(d.count); });
​
d3.csv('https://jay-oh-en.github.io/interactive-data-viz/data/airbnb/neighborhood_reviews_timeseries.csv',
function(timeseries) {
var field = "Mission";
​
// maximum reviews
var max_y = d3.max(timeseries, function(d) {
var max = 0;
​
d3.values(d).forEach(function(i) {
if (+i && (+i > max)) {
max = +i;
}
});
​
return max;
});
​
// Create y-axis scale mapping price -> pixels
var measure_scale = d3.scale.linear()
.range([height, 100])
.domain([0, max_y]);
​
// Create D3 axis object from measure_scale for the y-axis
var measure_axis = d3.svg.axis()
.scale(measure_scale)
.orient("right");
​
// Append SVG to page corresponding to the D3 y-axis
d3.select('#chart').append('g')
.attr('class', 'y axis')
.attr("transform", "translate(" + (width - 40) + " ,0)")
.call(measure_axis);
​
// add label to y-axis
d3.select(".y.axis")
.append("text")
.attr('class', 'label')
.text("Reviews per week")
.attr("transform", "translate(45,300) rotate(90)");
​
var drawChart = function(field) {
d3.select('#chart').select('.x.axis').remove();
d3.select('#chart').select('#chart path').remove();
​
d3.select('#heading')
.text(field);
​
// remove missing values
timeseries = timeseries.filter(function(d) {
return d[field];
});
​
// get min/max dates
var time_extent = d3.extent(timeseries, function(d){
return format.parse(d['timestamp']);
});
​
// Create x-axis scale mapping dates -> pixels
var time_scale = d3.time.scale()
.range([0, width - 50])
.domain(time_extent);
​
// Create D3 axis object from time_scale for the x-axis
var time_axis = d3.svg.axis()
.scale(time_scale)
.tickFormat(d3.time.format("%b '%y"));
​
// Append SVG to page corresponding to the D3 x-axis
d3.select('#chart').append('g')
.attr('class', 'x axis')
.attr('transform', "translate(0," + height + ")")
.call(time_axis);
// define the values to map for x and y position of the line
var line = d3.svg.line()
.x(function(d) { return time_scale(format.parse(d['timestamp'])); })
.y(function(d) { return measure_scale(+d[field]); });
​
// append a SVG path that corresponds to the line chart
d3.select('#chart').append("path")
.datum(timeseries)
.attr("class", "line")
.attr("d", line);
};
​
drawChart(field);
​
var mover = function(d) {
var neigh = d.properties.neighbourhood;
d3.select('#map path.' + neigh).style('fill', 'black');
drawChart(neigh);
};
​
var mout = function(d) {
var neigh = d.properties.neighbourhood;
d3.select('path.' + neigh).style('fill', '#eee');
}
​
map.on("mouseover", mover);
map.on("mouseout", mout);
​
bubbles.on('mouseover', mover);
bubbles.on('mouseout', mout);
});
});
}
</script>
</head>
<body>
<svg id="map"></svg>
<svg id="chart">
<text x="50%" y="50" id="heading" font-size="2em" text-anchor="middle" font-family="futura">SF</text>
</svg>
<script>
d3.json("https://jay-oh-en.github.io/interactive-data-viz/data/airbnb/neighbourhoods.geojson", draw);
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.8/d3.min.js