Public
Edited
May 12, 2024
Insert cell
Insert cell
nonClimateDisasters = ["Impact"]
Insert cell
emdat_disasters
Insert cell
emdat_disasters.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
groupedDisasters = Object.groupBy(
// Filter based on necessary items
emdat_disasters.filter(
el => {
const nonBiological = el["Disaster Subgroup"] != "Biological";
const correctMeasurement = el["Start Year"] >= 1900&& el["Start Year"] < 2024;
const isClimate = ! nonClimateDisasters.includes(el["Disaster Type"]);
return nonBiological && correctMeasurement && isClimate
}),
({ "Disaster Type" : type}) => {
if (type.includes("Mass movement")) return "Mass Movement";
if (type.includes("Glacial")) return "Flood";
return type;
}
);
Insert cell
function getColumnUniqueValues(name) {
let list = [];
for (var i in emdat_disasters) {
if (!list.includes(emdat_disasters[i][name]) && (emdat_disasters[i][name] != undefined)) {
list.push(emdat_disasters[i][name]);
}
}
return list;
}
Insert cell
countries = getColumnUniqueValues("Country")
Insert cell
function getAverageCostsPerDisaster() {
let object = new Object();
var arr = [];
for (var i in emdat_disasters) {
const x = emdat_disasters[i];
const damage = x["Total Damage ('000 US$)"];
const type = x["Disaster Type"];
if (!isNaN(damage)) {
if (!(type in object)) {
object[type] = [];
}
object[type].push(damage);
}
}
var toReturn = new Object();
for (var key in object) {
const n = object[key].length;
toReturn[key] = object[key].reduce((x, y) => (x / n) + y, 0.0);
}
return toReturn;
}
Insert cell
damages = getAverageCostsPerDisaster();
Insert cell
function getDisastersAmountPerCountryPerYear() {
let obj = new Object();
emdat_disasters.forEach(d => {
let country = d["Subregion"];
let y = parseInt(d["Start Year"]);
let disasterType = d["Disaster Type"];
if (country in obj) {
if (!(y in obj[country])) {
obj[country][y] = new Object();
}
if (!(disasterType in obj[country][y])) {
obj[country][y][disasterType] = 0;
}
obj[country][y][disasterType] += 1;
} else {
obj[country] = new Object();
obj[country][y] = new Object();
obj[country][y][disasterType] = 1;
}
});
return obj;
}


Insert cell
disastersAmountPerCountryPerYear = getDisastersAmountPerCountryPerYear();
Insert cell
function getCorrelation(firstDisasterType, secondDisasterType) {
let correlations = [];
for (let country in disastersAmountPerCountryPerYear) {
let x2 = [];
let y2 = [];
let xy = [];
let sigmaX = 0;
let sigmaY = 0;
let n = 0;
const list = disastersAmountPerCountryPerYear[country];
const years = Object.keys(list).map(x => parseInt(x));
var year = Math.min.apply(Math, years);
var xs = []
var ys = []
while (year <= Math.max.apply(Math, years)) {
n++;
var i = 0;
let x = 0;
let y = 0;
while (i < 1 && (year <= Math.max.apply(Math, years))) {
if (year in disastersAmountPerCountryPerYear[country]) {
const tempX = disastersAmountPerCountryPerYear[country][year][firstDisasterType];
const tempY = disastersAmountPerCountryPerYear[country][year][secondDisasterType];
if (!isNaN(tempX)) x += tempX;
if (!isNaN(tempY)) y += tempY;
}
i++;
year++;
}
xs.push(x);
ys.push(y);
sigmaX += x;
sigmaY += y;
x2.push(x*x);
y2.push(y*y);
xy.push(x*y);
}
let sigmaX2 = x2.reduce(((x, y) => x + y), 0);
let sigmaY2 = y2.reduce(((x, y) => x + y), 0);
let sigmaXY = xy.reduce(((x, y) => x + y), 0);

if (!(sigmaX == 0 && sigmaY == 0)) {
var correlation = ((n*sigmaXY) - (sigmaX*sigmaY))/Math.sqrt((n*sigmaX2 - (sigmaX*sigmaX)) * (n*sigmaY2 - (sigmaY*sigmaY)));
if(isNaN(correlation))
correlations.push(0);
else
correlations.push(correlation);
}
}
if (correlations.length == 0) {
return 0;
}
return correlations.reduce((x, y) => x + y, 0.0) / correlations.length;
}

Insert cell
getCorrelation("Wildfire", "Drought")
Insert cell
function getTypeCorrelations() {
var correlations = [];
getColumnUniqueValues("Disaster Type").forEach(x => {
getColumnUniqueValues("Disaster Type").forEach(y => {
const correlation = getCorrelation(x, y);
correlations.push({first : x, second : y, correlation: correlation});
})
})
return correlations;
}
Insert cell
correlations = getTypeCorrelations();
Insert cell
Plot.plot({
marginLeft: 145,
marginBottom: 100,
label: "Disaster Type",
color: { scheme: "rdylbu", pivot: 0, legend: true, label: "correlation" },
x : { tickRotate: -25 },
y : { tickRotate: -25 },
marks: [
Plot.cell(correlations, { x: "first", y: "second", fill: "correlation" }),
Plot.dot(correlations, {
x: "first",
y : "second",
stroke: "correlation",
tip : true,
channels: {"Disaster Type" : "first", "Second Disaster Type" : "second", correlation : "correlation"}
})
]
})
Insert cell
disastersPerYear = Object.entries(groupedDisasters).reduce((acc, [disasterType, disasterList]) => {
if (!selectedDisasters.includes(disasterType)) return acc;
let obj = new Object();
let miny = Number.MAX_VALUE;
let maxy = Number.MIN_VALUE;
disasterList.forEach(d => {
let y = parseInt(d["Start Year"]);
if (y in obj) {
obj[y] += 1;
} else {
obj[y] = 1;
}
if (y < miny) {
miny = y;
}
if (y > maxy) {
maxy = y;
}
});
for (let i = miny; i < maxy; i++) {
let nrOfDisasters = 0;
if (i in obj) {
nrOfDisasters = obj[i];
}
acc.push({disaster: disasterType, year : i, disasters: nrOfDisasters});
}
return acc;
}, [])
Insert cell
counts = Object.keys(groupedDisasters).reduce((acc, key) => {
acc.push({disaster: key, amount: groupedDisasters[key].length});
return acc;
}, []).sort((a, b) => b.amount - a.amount);
Insert cell
lengthOfDisastersPerYear = Object.entries(groupedDisasters).reduce((acc, [disasterType, disasterList]) => {
if (!selectedDisasters.includes(disasterType)) return acc;
let obj = new Object();
let miny = Number.MAX_VALUE;
let maxy = Number.MIN_VALUE;
disasterList.forEach(d => {
const startYear = parseInt(d["Start Year"]);
const startMonth = parseInt(d["Start Month"]);
const startDay = parseInt(d["Start Day"]);

const endYear = parseInt(d["End Year"]);
const endMonth = parseInt(d["End Month"]);
const endDay = parseInt(d["End Day"]);

const hasNan = [startYear, startMonth, startDay, endYear, endMonth, endDay].some(el => isNaN(el))
if (hasNan) return;
const startDate = new Date(startYear, startMonth, startDay);
const endDate = new Date(endYear, endMonth, endDay);

const lengthInDays = Math.round((endDate - startDate) / (1000 * 60 * 60 * 24));
//console.log(lengthInDays)
if (startYear in obj) {
const [currentAvg, n] = obj[startYear];
const newAvg = (lengthInDays + currentAvg * n) / (n + 1);
obj[startYear] = [newAvg, n + 1];
} else {
obj[startYear] = [lengthInDays, 1];
}

if (startYear < miny) {
miny = startYear;
}
if (startDate > maxy) {
maxy = startYear;
}
});
for (let i = miny; i < maxy; i++) {
let avgLength = 0;
if (i in obj) {
avgLength = obj[i][0];
}
acc.push({disaster: disasterType, year : i, avgLength: avgLength});
}
return acc;
}, [])
Insert cell
totalCount = counts.reduce((acc, dic) => acc + dic["amount"], 0)
Insert cell
Plot.plot({
height: 500,
marginLeft: 150,
x: {
label: "Number of disasters per category",
labelAnchor: "center",
},
marks: [
Plot.barX(
counts,
Plot.groupY(
{ x: "max" },
{
x: (val) => val.amount / totalCount,
y: "disaster",
sort: { y: "x", reverse: true }
}
)
)
]
})
Insert cell
Plot.plot({
y: {
label: "Amount of disasters"
},
marks: [
Plot.areaY(
disastersPerYear,
Plot.stackY({
x: "year",
y: "disasters",
fill: "disaster",
z: "disaster",
title: "disaster",
order: "max",
reverse: true,
stroke: "#ddd",
tip: true
})
),
Plot.ruleY([0])
],
style: {
pointerEvents: "all"
},
color: {
legend: true,
}
})
Insert cell
potDisasters = Object.keys(groupedDisasters)
Insert cell
viewof selectedDisasters = Inputs.checkbox(potDisasters, {label: "Choose Disasters:", value: potDisasters}, "");
Insert cell
selectedDisasters
Insert cell
Plot.plot({
style: "overflow: visible;",
y: {
label: "Amount of disasters"
},
marks: [
Plot.ruleY([0]),
Plot.lineY(disastersPerYear, {
x: "year",
y: "disasters",
stroke: "disaster",
title: "disaster",
order: "max",
reverse: true,
tip: true,
}),
],
color: {
legend: true,
}
})
Insert cell
Plot.plot({
style: "overflow: visible;",
y: {
label: "Length of disaster"
},
marks: [
Plot.ruleY([0]),
Plot.lineY(lengthOfDisastersPerYear, {
x: "year",
y: "avgLength",
stroke: "disaster",
title: "disaster",
order: "max",
reverse: true,
tip: true,
}),
],
color: {
legend: true,
}
})
Insert cell
function insertSorted(object, severityFactor, list) {
var temp;
if(list.length == 0) {
list.push(object);
}
for (var i = 0; i < list.length; i++) {
if (object[severityFactor] > list[i][severityFactor]) {
temp = list[i];
list[i] = object;
object = temp;
}
}
}
Insert cell
function getTop5Severity(disasters, severityFactor, specificDisasterType) {
var mostSevere = [];
disasters.forEach(d => {
if (!(d[severityFactor] == undefined) && specificDisasterType.includes(d["Disaster Type"])) {
if (mostSevere.length < 5) {
insertSorted(d, severityFactor, mostSevere);
} else {
for (var i = 0; i < mostSevere.length; i++) {
if (d[severityFactor] > mostSevere[i][severityFactor]) {
insertSorted(d, severityFactor, mostSevere);
break;
}
}
}
}
});
return mostSevere.map((d) => {
return {severity : d[severityFactor], info : (d["Disaster Type"] + " in " + d["Country"] + " (" + d["Start Year"] + ")"), disasters: d};
});
}

Insert cell
top5 = getTop5Severity(emdat_disasters, "Magnitude", ["Flood"]);
Insert cell
top5_reduced = top5.map((d) => {
return {severity : d["Total Affected"], info : (d["Country"] + " in " + d["Start Year"])};
});
Insert cell
Plot.plot({
height: 500,
marginLeft: 150,
marks: [
Plot.barY(top5_reduced, {x: "info", y: "severity", fill: "steelblue", sort: {x: "-y"}}),
]
});
Insert cell
confirmedAffectedPersonsPerYear= Object.entries(groupedDisasters).reduce((acc, [disasterType, disasterList]) => {

let json = {};
let minYear = Number.MAX_VALUE;
let maxYear = Number.MIN_VALUE;
disasterList.forEach(d => {

const year = parseInt(d["Start Year"]);
let deaths = parseInt(d["Total Deaths"]);
let injured = parseInt(d["No. Injured"]);
let affected = parseInt(d["No. Affected"]);
if (!deaths) deaths = 0;
if (!injured) injured = 0;
if (!affected) affected = 0;

if (year > maxYear) {
maxYear = year;
}
if (year < minYear) {
minYear = year;
}

if (year in json) {
json[year]["deaths"] += deaths;
json[year]["injured"] += injured;
json[year]["affected"] += affected;
json[year]["totalCount"] += 1;
} else {
json[year] = new Object({deaths : deaths, injured : injured, affected : affected, totalCount: 1});
}
});
for (let i = minYear; i <= maxYear; i++) {
if (i in json) {
acc.push({
disaster: disasterType,
year : i,
deaths : json[i]["deaths"],
injured : json[i]["injured"],
affected : json[i]["affected"],
amount: json[i]["totalCount"]
});
}
}
return acc;
}, []);
Insert cell
disasterCounts = confirmedAffectedPersonsPerYear.reduce((acc, disaster) => {
const disasterName = disaster["disaster"];
const nrOfDeaths = disaster["deaths"];
const nrOfInjured = disaster["injured"];
const nrOfAffected = disaster["affected"];
const count = disaster["amount"];
const foundDisaster = acc.find((el) => el["disaster"] === disasterName);
if (foundDisaster) {
foundDisaster["deaths"] += nrOfDeaths;
foundDisaster["injured"] += nrOfInjured;
foundDisaster["affected"] += nrOfAffected;
foundDisaster["numberOfDisasters"] += count;

acc = acc.filter((el) => el["disaster"] !== disasterName);
acc.push(foundDisaster);
} else {
const obj = {
disaster: disasterName,
deaths: nrOfDeaths,
injured: nrOfInjured,
affected: nrOfAffected,
numberOfDisasters: count,
};
acc.push(obj);
}
return acc;
}, []);
Insert cell
Plot.plot({
height: 500,
marginLeft: 150,
x: {
label: "Test",
labelAnchor: "center",
},
marks: [
Plot.barX(
disasterCounts,
Plot.groupY(
{ x: "max" },
{
x: (val) => val["numberOfDisasters"] / 1,
y: "disaster",
sort: { y: "x", reverse: true }
}
)
)
],
})
Insert cell
magnitudeWildfires = groupedDisasters["Flood"].filter(el => el["Magnitude"]).reduce((acc, disaster) => {
const year = disaster["Start Year"];
const magnitude = disaster["Magnitude"];

const dInAcc = acc.find(e => e["year"] === year);
if (dInAcc) {
const nrOfDisasters = dInAcc["nrOfDisasters"];
const currentMagnitudeAverage = dInAcc["magnitude"];
const newAverage = ((nrOfDisasters * currentMagnitudeAverage + magnitude) / (nrOfDisasters + 1)) | 0
const newObj = {
year: year,
magnitude: newAverage,
nrOfDisasters: nrOfDisasters + 1,
disaster: "Flood"
}
acc = acc.filter(e => e["year"] !== year);
acc.push(newObj);
} else {
const obj = {
year: year,
magnitude: magnitude,
nrOfDisasters: 1,
disaster: "Flood"
};
acc.push(obj);
}
return acc;
}, []);
Insert cell
groupedDisasters["Flood"].sort((a, b) => {
if (! a["Total Deaths"]) return 1;
if (! b["Total Deaths"]) return -1;
const deathsA = parseInt(a["Total Deaths"]);
const deathsB = parseInt(b["Total Deaths"]);
return deathsB - deathsA;
}).slice(0, 5).map(disaster => {
return {
disaster: "Flood",
year: disaster["Start Year"],
deaths: disaster["Total Deaths"],
};
});
Insert cell
function index(xs, x) {
for (var i = 0; i < xs.length; i++) {
if (xs[i].name == x) {
return i;
}
}

return -1;
}
Insert cell
function sunBurst(emdat_data) {
var data = [];
for (var i = 0; i < emdat_data.length; i++) {
const type = emdat_data[i]["Disaster Type"];
const subType = emdat_data[i]["Disaster Subtype"];

if (!data.map(x => x.name).includes(type)) {
data.push({ name: type, children: [{name : subType, children: []}] });
} else {
var children = data[index(data, type)].children;
if (!children.map(x => x.name).includes(subType)) {
children.push({name: subType, children: []});
}
}
}

data = {name : "disasters", children: data};

// return data;
// Specify the chart’s dimensions.
const width = 928;
const height = width;
const radius = width / 6;

// Create the color scale.
const color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow, data.children.length + 1));

// Compute the layout.
const hierarchy = d3.hierarchy(data)
.sum(d => d.value)
.sort((a, b) => b.value - a.value);
const root = d3.partition()
.size([2 * Math.PI, hierarchy.height + 1])
(hierarchy);
root.each(d => d.current = d);

// Create the arc generator.
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1))

// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [-width / 2, -height / 2, width, width])
.style("font", "10px sans-serif");

// Append the arcs.
const path = svg.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", d => { while (d.depth > 1) d = d.parent; return color(d.data.name); })
.attr("fill-opacity", d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.current) ? "auto" : "none")

.attr("d", d => arc(d.current));

// Make them clickable if they have children.
path.filter(d => d.children)
.style("cursor", "pointer")
.on("click", clicked);

const format = d3.format(",d");
path.append("title")
.text(d => `${d.ancestors().map(d => d.data.name).reverse().join("/")}\n${format(d.value)}`);

const label = svg.append("g")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.selectAll("text")
.data(root.descendants().slice(1))
.join("text")
.attr("dy", "0.35em")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.text(d => d.data.name);

const parent = svg.append("circle")
.datum(root)
.attr("r", radius)
.attr("fill", "none")
.attr("pointer-events", "all")
.on("click", clicked);

// Handle zoom on click.
function clicked(event, p) {
parent.datum(p.parent || root);

root.each(d => d.target = {
x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
y0: Math.max(0, d.y0 - p.depth),
y1: Math.max(0, d.y1 - p.depth)
});

const t = svg.transition().duration(750);

// Transition the data on all arcs, even the ones that aren’t visible,
// so that if this transition is interrupted, entering arcs will start
// the next transition from the desired position.
path.transition(t)
.tween("data", d => {
const i = d3.interpolate(d.current, d.target);
return t => d.current = i(t);
})
.filter(function(d) {
return +this.getAttribute("fill-opacity") || arcVisible(d.target);
})
.attr("fill-opacity", d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
.attr("pointer-events", d => arcVisible(d.target) ? "auto" : "none")

.attrTween("d", d => () => arc(d.current));

label.filter(function(d) {
return +this.getAttribute("fill-opacity") || labelVisible(d.target);
}).transition(t)
.attr("fill-opacity", d => +labelVisible(d.target))
.attrTween("transform", d => () => labelTransform(d.current));
}
function arcVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}

function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}

function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}

return svg.node();
}
Insert cell
before2000 =
Inputs.checkbox(
["include"],
{ label: "Include disasters before year 2000", value: ["include"] },
""
);

Insert cell
before2000
Insert cell
filterBefore2000 = before2000.length == 0;
Insert cell
sunBurst(emdat_disasters);
Insert cell
Object.entries(groupedDisasters).reduce(
(acc, [type, list]) => {
acc.push({ name: type, value: list.length, children: [] });

for (var i = 0; i < list.length; i++) {
const el = list[i];
const subType = el["Disaster Subtype"];
if (subType == undefined) return;
var children = acc[index(acc, type)].children;
if (!children.map(x => x.name).includes(subType)) {
children.push({ name: subType, value: 1, children: [] });
} else {
children[index(children, subType)].value += 1;
}
}
return acc;
}, []);
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more