|
<!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> |
|
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("http://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('http://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("http://jay-oh-en.github.io/interactive-data-viz/data/airbnb/neighbourhoods.geojson", draw); |
|
</script> |
|
</body> |
|
</html> |