A StreamGraph visualization of the total number of persons of concern grouped by country of origin.
The total number of persons of concern is the sum of Asylum-seekers, Internally displaced persons, Persons in IDP-like situation, Others of concern, Returned IDPs, Refugees (incl. refugee-like situations), Returnees, Persons in Refugee-like situation, and Stateless Persons. Excludes countries with relatively low counts. Data from UNHCR Population Statistics.
See also this variant that shows the data grouped by destination.
Draws from Labeled Streamgraph and Syrian Refugees by Settlement Type.
Uses d3-area-label to position labels.
xxxxxxxxxx
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="https://unpkg.com/d3@4.10.0"></script>
<script src="https://unpkg.com/d3-area-label@1.2.0"></script>
<title>Refugees Streamgraph</title>
<style>
body {
margin: 0px;
}
.area-label {
font-family: sans-serif;
fill-opacity: 0.7;
fill: white;
}
path:hover {
fill-opacity: 1;
fill:black;
}
path {
fill-opacity: 0.8;
stroke-width: 0.5;
}
text {
pointer-events: none;
}
.axis--major .tick text, .legend text, .tooltip text {
fill: #585858;
font-family: sans-serif;
font-size: 16pt;
}
.axis--minor .tick text {
display: none;
}
.axis--major .tick line{
stroke: #ddd;
stroke-width: 2px;
}
.axis--minor .tick line{
stroke: #eee;
}
.axis .domain {
display: none;
}
</style>
</head>
<body>
<svg width="960" height="500"></svg>
<script>
// Find the min and max year, then give the
// full range of years between them.
function computeYears (rawData) {
var allYearsSet = d3.set();
rawData.forEach(function (d) {
d.values.forEach(function (d) {
allYearsSet.add(d.key);
});
});
var yearsExtent = d3.extent(allYearsSet
.values()
.map(function (yearStr) {
return +yearStr;
}));
return d3
.range(yearsExtent[0], yearsExtent[1] + 1)
.map(function (year) {
return new Date(year + "");
});
}
var bisectDate = d3.bisector(function (d) {
return d.date;
}).left;
function getInterpolatedValue (values, date, value){
const i = bisectDate(values, date, 0, values.length - 1);
if (i > 0) {
const a = values[i - 1];
const b = values[i];
const t = (date - a.date) / (b.date - a.date);
return value(a) * (1 - t) + value(b) * t;
}
return value(values[i]);
}
// Interpolate values, create data structure
// for d3.stack.
function interpolateValues (years, rawData) {
var value = function (d) {
return d.value;
};
return years.map(function (date) {
// Create a new row object with the date.
var row = {
date: date
};
// Assign values to the new row object for each key.
// Value for `key` here will be country name.
rawData.forEach(function (d){
row[d.key] = getInterpolatedValue(d.values, date, value);
});
return row;
});
}
d3.json('sumByCountryByYear.json', function (rawData) {
// Parse dates, extract keys.
var keys = rawData
.filter(function (d) {
var sum = d3.sum(d.values, function (d){ return d.value; });
return sum > 1000000;
})
.map(function (d) {
d.values.forEach(function (d) {
d.date = new Date(d.key);
});
return d.key;
});
// Compute interpolated values for all years.
var data = interpolateValues(computeYears(rawData), rawData);
render(data, keys);
});
var margin = { top: 0, bottom: 30, left: 0, right: 30 };
var svg = d3.select('svg');
var width = +svg.attr('width');
var height = +svg.attr('height');
var g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
var xAxisG = g.append('g')
.attr('class', 'axis');
var xAxisMinorG = xAxisG.append('g')
.attr('class', 'axis axis--minor');
var xAxisMajorG = xAxisG.append('g')
.attr('class', 'axis axis--major');
var marksG = g.append('g');
var stack = d3.stack()
.offset(d3.stackOffsetWiggle)
.order(d3.stackOrderInsideOut)
;
var xValue = function (d) { return d.date; };
var xScale = d3.scaleTime();
var yScale = d3.scaleLinear();
var colorScale = d3.scaleOrdinal()
.range(d3.schemeCategory10);
var xAxisMajor = d3.axisBottom().scale(xScale);
var xAxisMinor = d3.axisBottom().scale(xScale).ticks(50);
var area = d3.area()
.x(d => xScale(xValue(d.data)))
.y0(d => yScale(d[0]))
.y1(d => yScale(d[1]))
.curve(d3.curveBasis);
// Render StreamGraph
function render(data, keys) {
stack.keys(keys);
var stacked = stack(data);
var innerWidth = width - margin.right - margin.left;
var innerHeight = height - margin.top - margin.bottom;
xScale
.domain(d3.extent(data, xValue))
.range([0, innerWidth]);
yScale
.domain([
d3.min(stacked, function (series) {
return d3.min(series, function (d) { return d[0]; });
}),
d3.max(stacked, function (series) {
return d3.max(series, function (d) { return d[1]; });
})
])
.range([innerHeight, 0]);
colorScale.domain(d3.range(keys.length));
var paths = marksG.selectAll('path').data(stacked);
var pathsEnter = paths
.enter().append('path');
pathsEnter.merge(paths)
.attr('fill', function (d) { return colorScale(d.index); })
.attr('stroke', function (d) { return colorScale(d.index); })
.attr('d', area);
paths.select('title')
.merge(pathsEnter.append('title'))
.text(function (d) { return d.key; })
var labels = marksG.selectAll('text').data(stacked)
labels
.enter().append('text')
.attr('class', 'area-label')
.merge(labels)
.text(function (d) { return d.key; })
.attr('transform', d3.areaLabel(area).interpolateResolution(1000));
xAxisMajor.tickSize(-innerHeight);
xAxisMinor.tickSize(-innerHeight);
xAxisG.attr('transform', `translate(0,${innerHeight})`);
xAxisMajorG.call(xAxisMajor);
xAxisMinorG.call(xAxisMinor);
}
</script>
</body>
</html>
https://unpkg.com/d3@4.10.0
https://unpkg.com/d3-area-label@1.2.0