Skip to content

Instantly share code, notes, and snippets.

@cuckooland
Last active Jul 20, 2019
Embed
What would you like to do?
'Yoland Bresson' module (RTM) using d3.js
license: gpl-3.0
height: 1040
scrolling: false
border: false

This realization mainly illustrates the interaction of two choropleth maps with two line charts using d3.dispatch.

.Blues .q0-3{fill:rgb(222,235,247)}
.Blues .q1-3{fill:rgb(158,202,225)}
.Blues .q2-3{fill:rgb(49,130,189)}
.Blues .q0-4{fill:rgb(239,243,255)}
.Blues .q1-4{fill:rgb(189,215,231)}
.Blues .q2-4{fill:rgb(107,174,214)}
.Blues .q3-4{fill:rgb(33,113,181)}
.Blues .q0-5{fill:rgb(239,243,255)}
.Blues .q1-5{fill:rgb(189,215,231)}
.Blues .q2-5{fill:rgb(107,174,214)}
.Blues .q3-5{fill:rgb(49,130,189)}
.Blues .q4-5{fill:rgb(8,81,156)}
.Blues .q0-6{fill:rgb(239,243,255)}
.Blues .q1-6{fill:rgb(198,219,239)}
.Blues .q2-6{fill:rgb(158,202,225)}
.Blues .q3-6{fill:rgb(107,174,214)}
.Blues .q4-6{fill:rgb(49,130,189)}
.Blues .q5-6{fill:rgb(8,81,156)}
.Blues .q0-7{fill:rgb(239,243,255)}
.Blues .q1-7{fill:rgb(198,219,239)}
.Blues .q2-7{fill:rgb(158,202,225)}
.Blues .q3-7{fill:rgb(107,174,214)}
.Blues .q4-7{fill:rgb(66,146,198)}
.Blues .q5-7{fill:rgb(33,113,181)}
.Blues .q6-7{fill:rgb(8,69,148)}
.Blues .q0-8{fill:rgb(247,251,255)}
.Blues .q1-8{fill:rgb(222,235,247)}
.Blues .q2-8{fill:rgb(198,219,239)}
.Blues .q3-8{fill:rgb(158,202,225)}
.Blues .q4-8{fill:rgb(107,174,214)}
.Blues .q5-8{fill:rgb(66,146,198)}
.Blues .q6-8{fill:rgb(33,113,181)}
.Blues .q7-8{fill:rgb(8,69,148)}
.Blues .q0-9{fill:rgb(247,251,255)}
.Blues .q1-9{fill:rgb(222,235,247)}
.Blues .q2-9{fill:rgb(198,219,239)}
.Blues .q3-9{fill:rgb(158,202,225)}
.Blues .q4-9{fill:rgb(107,174,214)}
.Blues .q5-9{fill:rgb(66,146,198)}
.Blues .q6-9{fill:rgb(33,113,181)}
.Blues .q7-9{fill:rgb(8,81,156)}
.Blues .q8-9{fill:rgb(8,48,107)}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Average price per square meter of new housing in France's metropolitan regions</title>
<link rel="stylesheet" href="colorbrewer-Blues.css">
<style>
#section
{
width: 950px;
max-width: 100%;
margin: auto;
}
.box.map {
width: 472px;
height: 386px;
display: inline-block;
vertical-align: middle;
}
.box.chart {
width: 450px;
height: 386px;
display: inline-block;
vertical-align: middle;
}
.area {
stroke: black;
stroke-width: .5px;
}
.area.selected {
stroke-width: 2px;
}
rect.selected {
stroke: black;
stroke-width: .5px;
stroke-dasharray: 4,2,2,2;
}
.line.faded,
.legendItem.faded {
opacity: 0.1;
}
div.tooltip {
position: absolute;
opacity:0.8;
z-index:1000;
text-align:left;
border-radius:4px;
padding:8px;
color:#fff;
background-color:#000;
font: 12px sans-serif;
max-width: 300px;
height: 40px;
pointer-events: none;
}
#svg {
display: block;
margin: auto;
}
.date_player {
margin-bottom: 1em;
}
.dateLabel {
font: 15px "Helvetica Neue";
fill: #000055;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
.xLocLine,
.yLocLine {
stroke-width: 0.2;
stroke: #000;
stroke-dasharray: 4,2,2,2;
}
.xDragLine {
stroke-width: 3;
stroke: #777;
cursor: col-resize;
opacity: 0;
}
.xDragLine.dragged {
opacity: 0.1;
}
.legend {
margin-left:40px;
}
.legendItem {
float:left;
margin-right:1em;
font: 0.7em "Helvetica Neue";
cursor: default;
}
</style>
<script src="http://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="section">
<h3>Average price per square meter of new housing in France's metropolitan regions</h3>
<div class="date_player">
<label for="rDateSelect">Date : </label> <select id="rDateSelect" class="region dateSelect"></select>
<input id="rStart" class="start rDateSelect" type="button" value="Play">
</div>
<div>
<div class="refSection">
<label for="rRefSelect1">Reference : </label> <select id="rRefSelect1" class="region refSelect"></select>
</div>
<div id="rMap1" class="box map region rDateSelect rRefSelect1">
</div>
<div id="rChart1" class="box chart region rDateSelect rRefSelect1">
</div>
</div>
<div>
<div class="refSection">
<label for="rRefSelect2">Reference : </label> <select id="rRefSelect2" class="region refSelect"></select>
</div>
<div id="rMap2" class="box map region rDateSelect rRefSelect2">
</div>
<div id="rChart2" class="box chart region rDateSelect rRefSelect2">
</div>
</div>
<br>
<div>
Graphical part of <a href="https://blog.jytou.fr/2019/05/25/my-yolland-bresson-module-part-1/">'Yoland Bresson' module</a>.
</div>
</div>
<script>
var regionsAttribs = {
class: 'region',
scale: 1900,
center: [2.554071, 46.679229],
geojson: 'regions-avant-redecoupage-2015.geojson',
};
var regionsAttribsEn = {
name: 'Region',
csv: 'YolandBresson-Regions-en.csv'
};
var attribsEn = {
PRICE: 'Price',
PRICE_M2: 'Price per m²',
START: 'Start',
STOP: 'Stop'
}
build_YB(Object.assign({}, regionsAttribs, regionsAttribsEn, attribsEn));
function build_YB(config) {
var DATE_DURATION = 750, REF_DURATION = 750;
var DATE_LABEL_CLASS = 'dateLabel', TOOLTIP_CLASS = 'tooltip';
var START_BUTTON_CLASS = 'start';
var DATE_EVENT = 'dateChange', REF_EVENT = 'refChange', AREA_EVENT = 'areaChange';
var parseTime = d3.timeParse("%d/%m/%Y");
var areaFilter = e => (e.CODE.length != 0);
var refFilter = e => (e.CODE.length == 0);
var dateColumnFilter = e => (parseTime(e) != null);
var f = d3.format('.2f');
var dispatch = d3.dispatch(DATE_EVENT, REF_EVENT, AREA_EVENT);
// Append a DIV for the tooltip
var tooltip = d3.select("body").append("div")
.attr("class", TOOLTIP_CLASS)
.style("opacity", 0);
d3.text(config.csv, function (error, text) {
if (error) {
throw error;
}
d3.selectAll('.csvContent.' + config.class).html(text);
});
d3.csv(config.csv, function (rows) {
buildGraphs(rows);
});
function buildGraphs(rows) {
var selectedDateIndex = 0;
var areaRows = rows.filter(areaFilter);
var refRows = rows.filter(refFilter);
var areaDates = d3.keys(areaRows[0]).filter(dateColumnFilter);
initDateSelector()
initStartButton();
buildRefGraphs();
function initDateSelector() {
var dateSelect = d3.select('.dateSelect.' + config.class);
dateSelect.selectAll("option")
.data(areaDates)
.enter().append("option")
.text(function (d) { return d; })
.attr('value', function (d) { return d; });
dateSelect.property('selectedIndex', 0);
dateSelect.on('change', function () {
selectedDateIndex = this.selectedIndex;
dispatch.call(DATE_EVENT, this, config.class);
});
dispatch.on(DATE_EVENT + '.' + config.class, function (areaClass) {
if (areaClass == config.class) {
d3.selectAll('.dateSelect.' + config.class).each(function (d, i) {
d3.select(this).property('selectedIndex', selectedDateIndex);
});
}
});
}
function initStartButton() {
var dateSelect = d3.select('.dateSelect.' + config.class);
var dateSelectId = dateSelect.attr('id');
var startButton = d3.selectAll('.' + START_BUTTON_CLASS + '.' + dateSelectId);
startButton.on("click", function () {
if (startButton.attr('value') == config.STOP) {
startButton.attr('value', config.START);
}
else {
startButton.attr('value', config.STOP);
// Increment date selection index every 750 ms
var interval = d3.interval(function (elapsed) {
var dateCount = d3.selectAll("#" + dateSelectId + ">option").size();
// Increment date selection index or go back to beginning
selectedDateIndex = (selectedDateIndex + 1) % dateCount;
dispatch.call(DATE_EVENT, this, config.class);
// Check if 'Stop' has been clicked
if (startButton.attr('value') == config.START) {
interval.stop();
}
}, DATE_DURATION);
}
});
}
function buildRefGraphs() {
var chartsHeight = 225;
var dateSelect = d3.select('.dateSelect.' + config.class);
var dateSelectId = dateSelect.attr('id');
d3.selectAll('.refSelect.' + config.class).each(function (d, i) {
var selectedRefIndex = i;
var refSelectId = d3.select(this).attr('id');
initReferenceSelector.call(this);
createChoroplethMap();
createLineChart();
function initReferenceSelector() {
d3.select(this).selectAll("option")
.data(refRows)
.enter().append("option")
.text(function (d) { return d.NAME; })
.attr('value', function (d) { return d.NAME; });
d3.select(this).property('selectedIndex', selectedRefIndex);
d3.select(this).on('change', function () {
selectedRefIndex = this.selectedIndex;
dispatch.call(REF_EVENT, this, refSelectId);
});
}
function createChoroplethMap() {
var refMapChart = d3.select('.map.' + config.class + '.' + refSelectId + '.' + dateSelectId);
var mapId = refMapChart.attr('id');
var mapChartWidth = 450, mapChartHeight = 386;
var colorScaleAxisId = mapId + 'ColorScaleAxis';
// Create a path object to manipulate geo data
var path = d3.geoPath();
// Define projection property
var projection = d3.geoConicConformal() // Lambert-93
.center(config.center) // Center on Paris
.scale(config.scale)
.translate([mapChartWidth / 2 - 50, mapChartHeight / 2]);
path.projection(projection); // Assign projection to path object
// Create the DIV that will contain our map
var svg = d3.select('#' + mapId).append("svg")
.attr("width", mapChartWidth)
.attr("height", mapChartHeight)
.attr("class", "Blues");
// Append the group that will contain our paths
var mapGroup = svg.append("g");
// Load GeoJSON data and build paths to append to group
d3.json(config.geojson, function (req, geojson) {
mapGroup.selectAll("path")
.data(geojson.features)
.enter().append("path")
.attr('id', function (d) { return config.class + mapId + d.properties.code; })
.attr("d", path);
// Set colors on map
updateMapColors();
// Initialize date label
svg.append("text")
.attr("class", DATE_LABEL_CLASS + ' ' + config.class)
.attr("text-anchor", "end")
.attr("y", mapChartHeight - 24)
.attr("x", mapChartWidth)
.text(getSelectedDate());
var colorScale = svg.append('g')
.attr('transform', 'translate(385, 30)');
colorScale.selectAll('.colorbar')
.data(d3.range(9))
.enter().append('svg:rect')
.attr('y', function (d) { return d * 25 + 'px'; })
.attr('height', '25px')
.attr('width', '25px')
.attr('x', '0px')
.attr("class", function (d) { return "q" + (8 - d) + "-9"; });
var maxPrice = getMaxPrice();
var legendScale = d3.scaleLinear()
.domain([0, maxPrice])
.range([9 * 25, 0]);
var colorScaleAxis = d3.axisRight(legendScale);
svg.append("g")
.attr("id", colorScaleAxisId)
.attr('transform', 'translate(410, 30)')
.call(colorScaleAxis)
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("x", -9 * 12.5)
.attr("y", -40)
.attr("dy", "0.71em")
.attr("text-anchor", "middle")
.text(config.PRICE + " (" + getSelectedRef() + ")");
// Refresh prices on date selection
dispatch.on(DATE_EVENT + '.' + mapId, function (areaClass) {
if (areaClass == config.class) {
updateMapColors();
d3.selectAll('.' + DATE_LABEL_CLASS + '.' + areaClass).text(getSelectedDate());
}
});
// Refresh prices on reference selection
dispatch.on(REF_EVENT + '.' + mapId, function (refSelectIdEvent) {
if (refSelectIdEvent == refSelectId) {
setTimeout(function () {
updateMapColors();
}, REF_DURATION);
updateColorScaleAxis();
}
});
// React when area selection changes
dispatch.on(AREA_EVENT + '.' + mapId, function (areaClass, areaCode) {
if (areaClass == config.class) {
svg.select('.area.a' + areaCode).classed('selected', true);
var areaRow = areaRows.filter(r => r.CODE == areaCode)[0];
svg.select('rect.q' + getQuantile()(getPrice(areaRow)) + '-9').classed('selected', true);
}
});
initTooltipCallbacks();
function updateColorScaleAxis() {
// Scale the range of the data again
legendScale.domain([0, getMaxPrice()]);
var t = d3.transition()
.duration(REF_DURATION)
.ease(d3.easeLinear);
// Change the y axis
svg.select('#' + colorScaleAxisId)
.transition(t)
.call(colorScaleAxis);
svg.select("#" + colorScaleAxisId + ">text")
.text(config.PRICE + " (" + getSelectedRef() + ")");
}
function getQuantile() {
var maxPrice = getMaxPrice();
// Build 'quantile' - which maps an input domain to a discrete range, that is 0...max(prices) to 1...9
var quantile = d3.scaleQuantile()
.domain([0, maxPrice])
.range(d3.range(9));
return quantile;
}
function updateMapColors() {
var quantile = getQuantile();
areaRows.forEach(function (areaRow, i) {
d3.select('#' + config.class + mapId + areaRow.CODE)
// Use 'colorbrewer.css' colors through class attribute
.attr("class", "area q" + quantile(getPrice(areaRow)) + "-9 a" + areaRow.CODE);
});
}
function initTooltipCallbacks() {
areaRows.forEach(function (areaRow) {
d3.select('#' + config.class + mapId + areaRow.CODE)
.on("mouseover", function () {
tooltip.transition()
.duration(200)
.style("opacity", .8);
tooltip.html("<b>" + config.name + " : </b>" + areaRow.NAME + "<br>"
+ "<b>" + config.PRICE_M2 + ": </b>" + f(getPrice(areaRow)) + " (" + getSelectedRef() + ")")
.style("left", (d3.event.pageX + 30) + "px")
.style("top", (d3.event.pageY - 30) + "px");
mouseOverArea(config.class, areaRow.CODE);
})
.on("mouseout", function () {
tooltip.transition()
.duration(200)
.style("opacity", 0);
mouseOutArea(config.class, areaRow.CODE);
});
});
}
});
}
function createLineChart() {
var refLineChart = d3.select('.chart.' + config.class + '.' + refSelectId + '.' + dateSelectId);
var chartId = refLineChart.attr('id');
var data = areaRows.map(function (row) {
var entries = d3.entries(row);
return { 'name': row.NAME, 'code': row.CODE, 'values': entries.filter(e => dateColumnFilter(e.key)) };
});
// Set the dimensions of the chart
var margin = { top: 30, right: 20, bottom: 30, left: 50 },
lineChartWidth = 450 - margin.left - margin.right;
// Create the svg that will contain our chart
var lineChart = d3.select('#' + chartId);
var svg = lineChart.append("svg")
.attr("width", lineChartWidth + margin.left + margin.right)
.attr("height", chartsHeight + margin.top + margin.bottom);
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Set the ranges
var x = d3.scaleTime().rangeRound([0, lineChartWidth]);
var y = d3.scaleLinear().rangeRound([chartsHeight, 0]);
var z = d3.scaleOrdinal(d3.schemeCategory20);
// Scale the range of the data
x.domain(d3.extent(data[0].values, function (d) { return parseTime(d.key); }));
y.domain([0, getMaxPrice()]);
z.domain(data.map(function (d) { return d.name; }));
// Define the line
var line = d3.line()
.x(function (d) { return x(parseTime(d.key)); })
.y(function (d) { return y(+d.value / getRefValue(d.key)); });
// Add the X Axis
g.append("g")
.attr("transform", "translate(0," + chartsHeight + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
var yAxis = d3.axisLeft(y);
g.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text(config.PRICE + " (" + getSelectedRef() + ")");
// Add the 'line' for each area.
g.selectAll(".gLine")
.data(data)
.enter().append("g")
.attr("class", "gLine")
.append("path")
.attr("class", function (d) { return "line a" + d.code; })
.attr("d", function (d) { return line(d.values); })
.style("stroke", function (d) { return z(d.name); })
.on("mouseover", function (d) { mouseOverArea(config.class, d.code); })
.on("mouseout", function (d) { mouseOutArea(config.class, d.code); });
g.append('line')
.attr("class", "xLocLine")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", chartsHeight)
.attr("transform", "translate(" + x(parseTime(getSelectedDate())) + ",0)");
var xDates = areaDates.map(d => x(parseTime(d)));
g.append('line')
.attr("class", "xDragLine")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 0)
.attr("y2", chartsHeight)
.attr("transform", "translate(" + x(parseTime(getSelectedDate())) + ",0)")
.attr("opacity", 0)
.call(d3.drag()
.on("start", function (d) {
var lineChart = d3.selectAll(".box.chart." + config.class);
lineChart.select('.xDragLine').classed("dragged", true);
lineChart.select('.xLocLine').style("display", "none");
})
.on("drag", function (d) {
var lineChart = d3.selectAll(".box.chart." + config.class);
lineChart.select('.xDragLine').attr("transform", "translate(" + d3.event.x + ",0)");
var newDateIndex = d3.bisect(xDates, d3.event.x);
if (newDateIndex == areaDates.length) {
newDateIndex = areaDates.length - 1;
}
else if (newDateIndex > 0 && (d3.event.x - xDates[newDateIndex - 1]) < (xDates[newDateIndex] - d3.event.x)) {
newDateIndex--;
}
if (selectedDateIndex != newDateIndex) {
selectedDateIndex = newDateIndex;
dispatch.call(DATE_EVENT, this, config.class);
}
})
.on("end", function (d) {
var lineChart = d3.selectAll(".box.chart." + config.class);
lineChart.select('.xDragLine').classed("dragged", false);
lineChart.select('.xLocLine').style("display", null);
dispatch.call(DATE_EVENT, this, config.class);
}));
g.append('line')
.attr("class", "yLocLine")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", lineChartWidth)
.attr("y2", 0)
.style("display", "none");
// Add the legend
var legend = d3.select('#' + chartId)
.append("div")
.attr("class", "legend")
.selectAll(".legendItem")
.data(data);
legend.enter().append("div")
.attr("class", function (d) { return "legendItem a" + d.code; })
.html(function (d) { return d.name; })
.style("color", function (d) { return z(d.name); })
.on("mouseover", function (d) { mouseOverArea(config.class, d.code); })
.on("mouseout", function (d) { mouseOutArea(config.class, d.code); });
// Refresh lines location
dispatch.on(DATE_EVENT + '.' + chartId, function (areaClass) {
if (areaClass == config.class) {
var xLoc = x(parseTime(getSelectedDate()));
svg.selectAll('.xLocLine').attr("transform", "translate(" + xLoc + ",0)");
svg.selectAll('.xDragLine').filter(d => !d3.select(this).classed("dragged")).attr("transform", "translate(" + xLoc + ",0)");
}
});
// Refresh chart on reference selection
dispatch.on(REF_EVENT + '.' + chartId, function (refSelectIdEvent) {
if (refSelectIdEvent == refSelectId) {
updateChart();
}
});
// React when area selection changes
dispatch.on(AREA_EVENT + '.' + chartId, function (areaClass, areaCode) {
if (areaClass == config.class) {
lineChart.selectAll('.line').classed("faded", true);
lineChart.selectAll('.line.a' + areaCode).classed("faded", false);
lineChart.selectAll('.legendItem').classed("faded", true);
lineChart.selectAll('.legendItem.a' + areaCode).classed("faded", false);
lineChart.selectAll('.yLocLine')
.attr("transform", "translate(0," + y(getPrice(areaRows.filter(r => (r.CODE == areaCode))[0])) + ")")
.style("display", null);
}
});
function updateChart() {
// Scale the range of the data again
y.domain([0, getMaxPrice()]);
var t = d3.transition()
.duration(REF_DURATION)
.ease(d3.easeLinear);
// Change the lines
svg.selectAll(".line")
.transition(t)
.attr("d", function (d) { return line(d.values); });
// Change the y axis
svg.select(".y.axis")
.transition(t)
.call(yAxis);
svg.select(".y.axis>text")
.text(config.PRICE + " (" + getSelectedRef() + ")");
}
}
function getSelectedDate() { return areaDates[selectedDateIndex]; }
function getSelectedRef() { return refRows[selectedRefIndex].NAME; }
function getRefValue(date) { return refRows.find(e => e.NAME == getSelectedRef())[date]; }
function getPrice(areaRow) {
var selectedDate = getSelectedDate();
var refValue = getRefValue(selectedDate);
return +areaRow[selectedDate] / refValue;
}
function getMaxPrice() {
var maxPrice = d3.max(areaRows, function (row) {
var priceEntries = d3.entries(row).filter(e => dateColumnFilter(e.key));
return d3.max(priceEntries, function (e) {
var refValue = getRefValue(e.key);
return e.value / refValue;
});
});
return maxPrice;
}
function mouseOverArea(areaClass, areaCode) {
dispatch.call(AREA_EVENT, this, areaClass, areaCode);
}
function mouseOutArea(areaClass, areaCode) {
var mapChart = d3.selectAll(".box.map." + areaClass);
mapChart.select('.area.a' + areaCode).classed('selected', false);
mapChart.selectAll('rect').classed('selected', false);
var lineChart = d3.selectAll(".box.chart." + areaClass);
lineChart.selectAll('.line').classed('faded', false);
lineChart.selectAll('.legendItem').classed('faded', false);
lineChart.selectAll('.yLocLine').style("display", "none");
}
});
}
}
}
</script>
</body>
</html>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
NAME CODE 01/01/2000 01/01/2001 01/01/2002 01/01/2003 01/01/2004 01/01/2005 01/01/2006 01/01/2007 01/01/2008 01/01/2009 01/01/2010 01/01/2011 01/01/2012 01/01/2013 01/01/2014 01/01/2015
Euros 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
Ounce of gold 257.69 242.7 293.13 411.74 508.77 553.63 758.4 955.22 1282.5 1358.5 1625.3 2186.72 2144.31 1876.63 1681.88 1287.04
Ounce of silver 4.62 3.58 4.73 5.66 8.71 8.71 15.07 17.82 22.06 20.92 26.51 48.72 39.83 31.87 25.24 17.75
RSA (units of welfare) 389.1 398.2 405.6 411.7 417.9 425.4 433.1 440.9 447.9 454.6 460.1 467 474.9 492.9 509.3 524.2
Billionth of M3 4723.3 5026.8 5430.3 5805.9 6172.4 6581.6 7120.5 7825.2 8786.1 9400 9325.4 9308 9484 9759 9898 10438
ALSACE 42 1679 1750 1799 1894 2037 2269 2440 2615 2684 2732 2784 3078 3131 3129 3300 3238
AQUITAINE 72 1766 1858 1813 2137 2342 2529 2871 2992 2991 3137 3284 3469 3580 3613 3603 3624
AUVERGNE 83 1546 1691 1706 1960 2068 2468 2458 2621 2443 2511 2642 2954 2940 2926 2941 3046
BASSE NORMANDIE 25 1811 1808 1854 2082 2091 2424 2670 2887 3210 3196 3416 3446 3601 3682 3763 3369
BOURGOGNE 26 1573 1642 1732 1881 2028 2217 2443 2497 2635 2722 2776 2789 2811 2861 2949 3024
BRETAGNE 53 1696 1753 1858 1985 2121 2283 2460 2739 2776 2769 2862 3123 3096 3166 3209 3213
CENTRE-VAL DE LOIRE 24 1680 1732 1840 2025 2107 2346 2466 2666 2807 2808 2967 3078 3183 3176 3078 3034
CHAMPAGNE-ARDENNE 21 1585 1536 1619 1771 1940 2172 2228 2673 2788 2739 2977 3168 3172 3305 3151 3314
CORSE 94 1632 1643 1750 1955 2196 2382 2591 3005 2990 3012 3199 3168 3376 3499 3439 3581
FRANCHE COMTE 43 1422 1511 1651 1829 2005 2160 2354 2375 2391 2572 2706 2716 2839 2776 2952 2874
HAUTE NORMANDIE 23 1786 1806 1810 1988 2063 2329 2622 2821 2910 2872 2960 3182 3163 3210 3148 3229
ILE DE FRANCE 11 2671 2810 2991 2977 3213 3690 4126 4133 4141 4176 4550 4932 4842 4676 4707 4754
LANGUEDOC-ROUSSILLON 91 1630 1747 1971 2162 2332 2716 2901 3103 3138 3226 3262 3366 3490 3612 3562 3699
LIMOUSIN 74 1548 1554 1655 1754 1967 2329 2492 2579 2555 2515 2627 2642 2623 2530 2664 2866
LORRAINE 41 1554 1577 1664 1770 1949 2109 2203 2304 2365 2408 2532 2684 2645 2690 2678 2931
MIDI-PYRENEES 73 1678 1773 1954 2055 2258 2525 2747 2936 3016 3042 3297 3378 3418 3441 3375 3377
NORD-PAS-DE-CALAIS 31 1796 1812 1835 1939 2183 2417 2675 2894 3007 3013 3165 3226 3079 3157 3157 3227
PAYS DE LA LOIRE 52 1792 1810 1942 2083 2381 2620 2719 2905 3033 3001 3160 3416 3366 3437 3444 3496
PICARDIE 22 1802 1788 1831 1929 1938 2258 2433 2812 2818 2984 3033 3385 3186 3104 2991 3113
POITOU-CHARENTES 54 1728 1787 2102 2019 2162 2367 2520 2903 3008 3025 3537 3454 3591 3813 3725 3640
PROVENCE-ALPES-COTE D AZUR 93 2148 2345 2578 2805 3191 3565 3739 3968 3951 3870 4024 4184 4328 4233 4006 4186
RHONE-ALPES 82 1780 1859 2010 2250 2579 2873 3110 3440 3349 3376 3551 3733 3709 3713 3759 3851
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment