This realization mainly illustrates the interaction of two choropleth maps with two line charts using d3.dispatch.
Last active
July 20, 2019 16:48
-
-
Save cuckooland/4d10d67c71eb3d108e6b72b4a32d8175 to your computer and use it in GitHub Desktop.
'Yoland Bresson' module (RTM) using d3.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
height: 1040 | |
scrolling: false | |
border: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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)} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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