forked from Jay-Oh-eN's block: Civic Impact through Data Visualization: Final
forked from mansweet's block: Civic Impact through Data Visualization: Final
forked from Jay-Oh-eN's block: Civic Impact through Data Visualization: Final
forked from mansweet's block: Civic Impact through Data Visualization: Final
<!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://cdn.rawgit.com/hopelessoptimism/interactive-data-viz/master/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://cdn.rawgit.com/hopelessoptimism/interactive-data-viz/master/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://cdn.rawgit.com/hopelessoptimism/interactive-data-viz/master/data/airbnb/neighbourhoods.geojson", draw); | |
</script> | |
</body> | |
</html> |