|
<!DOCTYPE html> |
|
<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 |
|
// http://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("http://jay-oh-en.github.io/interactive-data-viz/data/airbnb/listings.csv", draw); // draw is the callback func |
|
</script> |
|
</body> |
|
</html> |