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: Exercise 1
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.8/d3.min.js"></script>
<style>
// the above is an import of the d3.js library
circle {
opacity: 0.408576;
}
body {
font-family: futura;
}
h2.title {
color: black;
text-align: center;
}
.axis {
font-family: arial;
font-size: 0.7em;
}
text {
fill: black;
}
.label {
font-size: 2em;
}
path {
fill: none;
stroke: black;
stroke-width: 1px;
}
.tick {
fill: none;
stroke: black;
}
.line {
fill: none;
stroke: #4eb0bb;
stroke-width: 1px;
}
</style>
<script>
function draw(data) {
"use strict";
​
/*
D3.js setup code
*/
​
// set margins according to Mike Bostock's margin conventions
// https://bl.ocks.org/mbostock/3019563
// We set it accordinly so we can acually see the axis labels
var margin = {top: 25, right: 40, bottom: 150, left: 75};
// set height and width of chart
var width = 1400 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
// so we are subtracting a small margin to make like a little window within the other window
// specify the radius of our circles and the
// column we want to plot
var radius = 3,
field = 'San Francisco',
y_field = "price",
x_field = "number_of_reviews",
review_rate = 'reviews_per_month';
// Append the title for the graph
d3.select("body")
.append("h2")
.text(field + " Listings")
.attr('class', 'title');
// append the SVG tag with height and width to accommodate for margins
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append('g')
.attr('class','chart')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// the 'g' tag is a way to group elements
// remove missing values
data = data.filter(function(d) {
return d[y_field];
});
// for each element, pass it through our function. If it is true, keep it, if no then throw it away. (eg if it has a nan in that column, through the whole observation away.)
​
// bind our data to svg circles for the scatter plot
svg.selectAll("circle") // there are not any circles yet..but this still works for some odd reason
.data(data) //using .data here, instead of .datum(). This is a big array of objects so we can bind them all at once. You could do this with a for-loop, but this is a more d3.way (akin to pythonic vs numpy). This does a join() function of sorts
.enter() // this is specifying the TYPE of join (inner vs outer)
.append("circle")
// maximum price
var max_y = d3.max(data, function(d) {
return +d[y_field]; // the + turns a str to an int/float (no diff in JS)
});
​
// get min/max review count
var review_extent = d3.extent(data, function(d){
return +d[x_field]; // returns the min and the max in an array (no tuples)
});
var month_review_extent = d3.extent(data, function(d) {
return +d[review_rate];
});
​
// Create x-axis scale mapping dates -> pixels
var review_scale = d3.scale.linear()// this is the created object
.range([0, width])
.domain(review_extent); // these things are just mutating it
​
// Create y-axis scale mapping price -> pixels
var measure_scale = d3.scale.linear()
.range([height, 0])
.domain([0, 1200]);
// // Create a scale for monthly reviews
var avg_review_scale = d3.scale.log()
.range([1, 5])
.domain([0,4]);
​
// Create D3 axis object from time_scale for the x-axis
var x_axis = d3.svg.axis()
.scale(review_scale);
​
// Create D3 axis object from measure_scale for the y-axis. This is needed for the axis to appear
var measure_axis = d3.svg.axis()
.scale(measure_scale)
.orient("left");
​
// Append SVG to page corresponding to the D3 x-axis
svg.append('g')
.attr('class', 'x axis')
.attr('transform', "translate(0," + height + ")")
.call(x_axis);
​
// Append SVG to page corresponding to the D3 y-axis
svg.append('g')
.attr('class', 'y axis')
.call(measure_axis);
​
// add label to y-axis
d3.select(".y.axis")
.append("text")
.attr('class', 'label')
.text("Price (dollar/sq-ft)")
.attr("transform", "rotate(-90, -49, 0) translate(-400, 0)");
//not sure what the transform actually does??????
​
// add label to x-axis
d3.select(".x.axis")
.append("text")
.attr('class', 'label')
.text("Number of Reviews")
.attr("transform", "rotate(0) translate(400, 48)");
// based on the data bound to each svg circle,
// change its center-x (cx) and center-y (cy)
// coordinates
d3.selectAll('circle')
.attr('cx', function(d) {
return review_scale(+d[x_field]);
})
.attr('cy', function(d) {
return measure_scale(+d[y_field]);
}) // this is d3 magic. Notices that we're plugging elements into a variable...
.attr('r', function(d) {
return d[review_rate]; // return avg_review_scale(d+[review_rate]);
})
.style('fill', function(d) {
switch (d['room_type']) {
case 'Entire home/apt':
return 'red';
case 'Private room':
return 'green'
case 'Shared room':
return 'blue';
default:
return 'gray';
}
});
}
</script>
</head>
<body>
<script>
/*
Use D3 to load the CSV file and pass
the contents of it to the draw function.
*/
d3.csv("https://jay-oh-en.github.io/interactive-data-viz/data/airbnb/listings.csv", draw); // draw is the callback func
</script>
</body>
</html>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.8/d3.min.js