Skip to content

Instantly share code, notes, and snippets.

@martgnz
Last active October 28, 2017 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martgnz/33a92675c1d79b8babb1 to your computer and use it in GitHub Desktop.
Save martgnz/33a92675c1d79b8babb1 to your computer and use it in GitHub Desktop.
Scatterplot with voronoi
license: mit
border: none

In the richest neighbourhood of Barcelona the square meter costs as five times as in the poorest, according to the City Council data (I, II). While not surprising, this chart reflects a city where living in a different neighbourhood could mean to have a decade less of life expectancy.

The blue neighbourhoods are above the average rent in the city (100) while the orange ones are below.


This chart is based on the Mike Bostock's scatterplot example with three additional features:

  • Initial transition
  • Voronoi geometry
  • Tooltip
neighbourhood square_meter avg_rent population category
la Nova Esquerra de l'Eixample 3016 108.6074883 57828 Above average
Sant Andreu 2178 79.31502779 56280 Below average
la Sagrada Família 2869 97.54734832 51725 Below average
la Vila de Gràcia 3174 109.4856165 50730 Above average
el Raval 2401 60.34892586 49225 Below average
Sant Gervasi-Galvany 3882 195.6369876 46628 Above average
les Corts 3536 124.8330448 46126 Above average
l'Antiga Esquerra de l'Eixample 3407 125.113012 41843 Above average
Sants 2560 82.63341605 41104 Below average
el Poble Sec-AEI Parc Montjuïc 2310 71.03776283 41060 Below average
Sant Antoni 2863 102.4993756 38299 Above average
el Camp de l'Arpa del Clot 2418 76.53341177 38035 Below average
el Guinardó 2416 86.36245519 35790 Below average
el Camp d'en Grassot i Gràcia Nova 2846 101.0165697 34429 Above average
el Poblenou 2942 89.58562087 33176 Below average
el Carmel 1794 54.36306126 32181 Below average
el Fort Pienc 2917 99.0156076 31754 Below average
la Marina de Port 2130 70.87074121 30099 Below average
el Putxet i el Farró 3237 142.176868 28883 Above average
la Sagrera 2475 74.3352552 28827 Below average
el Clot 2452 76.927349 27154 Below average
Horta 2269 83.11608796 26543 Below average
la Prosperitat 1973 56.31277114 26320 Below average
el Baix Guinardó 2541 83.63582616 25676 Below average
Vilapicina i la Torre Llobeta 2200 71.09250467 25530 Below average
Sant Gervasi-la Bonanova 3809 189.6496129 25251 Above average
Sarrià 4447 196.0949537 24582 Above average
Porta 2058 61.32308182 24442 Below average
Sants-Badal 2623 76.59941777 24344 Below average
la Maternitat i Sant Ramon 3371 118.6951403 23653 Above average
el Besòs i el Maresme 2008 52.99717326 23202 Below average
Sant Pere. Santa Caterina i la Ribera 3202 91.1662735 22821 Below average
Provençals del Poblenou 2603 76.09687579 20158 Below average
la Bordeta 2233 71.4039915 18449 Below average
el Barri Gòtic 3192 103.6470802 16327 Above average
les Tres Torres 4488 223.9591926 16087 Above average
Hostafrancs 2501 77.2462479 15894 Below average
les Roquetes 1745 50.37004861 15836 Below average
la Barceloneta 3490 82.06256924 15571 Below average
Vallcarca i els Penitents 3118 103.9404584 15479 Above average
el Turó de la Peira 2014 51.56666501 15307 Below average
el Parc i la Llacuna del Poblenou 2630 93.74057899 14513 Below average
la Salut 2769 113.5134406 13223 Above average
Diagonal Mar i el Front Marítim del Poblenou 4391 150.1269651 13125 Above average
el Bon Pastor 1973 71.77947065 12734 Below average
Verdun 1909 55.6117274 12296 Below average
Pedralbes 4946 243.8916348 11784 Above average
la Teixonera 1999 69.01920286 11257 Below average
Ciutat Meridiana 1360 43.18796005 10537 Below average
la Trinitat Vella 1677 53.5123763 10418 Below average
la Font de la Guatlla 2475 77.77798268 10307 Below average
la Font d'en Fargues 2702 108.5587272 9482 Above average
la Vila Olímpica del Poblenou 3987 151.6065616 9367 Above average
Can Baró 2435 74.17492741 8916 Below average
el Coll 2374 83.14100505 7170 Below average
Sant Genís dels Agudells 1943 74.76051214 6918 Below average
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<style>
text {
font-family: sans-serif;
font-size: 12px;
}
.dot {
stroke: black;
}
.voronoiWrapper {
-webkit-cursor: crosshair;
cursor: crosshair;
}
.popover {
pointer-events: none;
}
.tooltip--value {
font-family: "Menlo", monospace;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
</style>
<body>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var margin = { top: 20, right: 20, bottom: 20, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xAxis = d3.axisBottom(x).tickFormat(function(d) {
return d + "€";
});
var yAxis = d3.axisLeft(y);
var svg = d3
.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.square_meter = +d.square_meter;
d.avg_rent = +d.avg_rent;
d.population = +d.population;
});
x
.domain(
d3.extent(data, function(d) {
return d.square_meter;
})
)
.nice();
y
.domain(
d3.extent(data, function(d) {
return d.avg_rent;
})
)
.nice();
svg
.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Average second hand m²");
svg
.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".7em")
.style("text-anchor", "end")
.text("Average family rent");
// pack the circles on their own group
var dotsGroup = svg.append("g").attr("class", "dotsWrapper");
dotsGroup
.selectAll(".dot")
.data(data)
.enter()
.append("circle")
.attr("class", function(d, i) {
return "dot neighbourhood-" + i;
})
.attr("r", 5)
.attr("cx", function(d) {
return x(d.square_meter);
})
.attr("cy", height)
.style("opacity", 0)
.style("fill", function(d) {
return color(d.category);
});
// setup the transition to make the circles appear
dotsGroup
.selectAll(".dot")
.transition()
.duration(1000)
.delay(function(d, i) {
return i * 20;
})
.style("opacity", 1)
.attr("cx", function(d) {
return x(d.square_meter);
})
.attr("cy", function(d) {
return y(d.avg_rent);
});
// start the voronoi
var voronoi = d3
.voronoi()
.x(function(d) {
return x(d.square_meter);
})
.y(function(d) {
return y(d.avg_rent);
})
.extent([[0, 0], [width, height]]);
var voronoiGroup = svg.append("g").attr("class", "voronoiWrapper");
voronoiGroup
.selectAll("path")
.data(voronoi.polygons(data))
.enter()
.append("path")
.attr("d", function(d, i) {
return "M" + d.join("L") + "Z";
})
.attr("class", function(d, i) {
return "voronoi neighbourhood-" + i;
})
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", showTooltip)
.on("mouseout", removeTooltip);
function showTooltip(d, i) {
var element = d3.selectAll(".neighbourhood-" + i);
var elementNode = d3.selectAll(".neighbourhood-" + i).node();
$(elementNode).popover({
placement: "auto top",
container: "body",
trigger: "manual",
html: true,
content: function() {
return (
"<strong>" +
d.data.neighbourhood +
"</strong>" +
"<br /><span class='tooltip--value'>Avg. m²: " +
d.data.square_meter +
"€</span>" +
"<br /><span class='tooltip--value'>Avg. rent: " +
d.data.avg_rent +
"</span>"
);
}
});
$(elementNode).popover("show");
// vertical line
svg
.append("g")
.attr("class", "guide")
.append("line")
.attr("x1", element.attr("cx"))
.attr("x2", element.attr("cx"))
.attr("y1", +element.attr("cy"))
.attr("y2", height)
.style("stroke", element.style("fill"))
.style("stroke-dasharray", "5,5")
.style("opacity", 0)
.style("pointer-events", "none")
.transition()
.duration(100)
.style("opacity", 0.5);
// horizontal line
svg
.append("g")
.attr("class", "guide")
.append("line")
.attr("x1", +element.attr("cx"))
.attr("x2", 0)
.attr("y1", element.attr("cy"))
.attr("y2", element.attr("cy"))
.style("stroke", element.style("fill"))
.style("stroke-dasharray", "5,5")
.style("opacity", 0)
.style("pointer-events", "none")
.transition()
.duration(200)
.style("opacity", 0.5);
}
function removeTooltip() {
$(".popover").each(function() {
$(this).remove();
});
d3
.selectAll(".guide")
.transition()
.duration(100)
.style("opacity", 0)
.remove();
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment