Public
Edited
Apr 11, 2024
1 fork
Insert cell
Insert cell
catastrophesEmpty = FileAttachment("CatastrophesByYear.json").json()
Insert cell
catastrophesEmpty
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
catastrophesByYear = FileAttachment("CatastrophesByYearAlmostFilled.json").json()
Insert cell
catastrophesByYear
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
test = nest(cities, d => d.iso3, d => d.name)
Insert cell
countries = FileAttachment("Countries.json").json()
Insert cell
countriesDownload = d3.json('https://gist.githubusercontent.com/bumbeishvili/20a70b32e172c58d9ada27f88adae54f/raw/32d8cd06362a673add0d7371f91c0a9ab3ecd01b/jsonDownload.json')
Insert cell
continentsDownload = d3.json('https://gist.githubusercontent.com/bumbeishvili/d2680078bb5232d11993d36030c5770c/raw/f19267222668edd504f739bb3c82c55f00283112/continents.json')
Insert cell
function createContinents() {
let tempContinents = cloneObject(continentsDownload);
tempContinents.objects.continents.geometries.forEach(continent => {
let continentObject = {name: continent.properties.CONTINENT};
continent.properties = continentObject;
})
return tempContinents;
}
Insert cell
temp = catastrophesByYear.map(d => [d.Country, d.ISO, d.Location])
Insert cell
function checkIfCoordinatesAreUnique(coords, latitude, longitude) {
if(latitude !== null && longitude !== null && latitude !== undefined && longitude !== undefined){
for (const coord of coords) {
//if (Math.abs(coord[0] - latitude) < Number.EPSILON && Math.abs(coord[1] - longitude) < Number.EPSILON) {
if (coord[0] === latitude && coord[1] === longitude) {
return false;
}
}
} else {
return false;
}
return true;
}
Insert cell
async function fillLatitudeAndLongitude(entryMap) {
let tempEntries = cloneObject(entryMap);
tempEntries.forEach(entry => {
let locationsSplitByComma = entry.Location.normalize("NFD").replace(/[\u0300-\u036f]/g, "").split(",");
let locationsSplitBySpace = entry.Location.normalize("NFD").replace(/[\u0300-\u036f]/g, "").split(" ");
let country = entry.ISO;
let countryName = entry.Country;
let entries = findLocationEntries(entry, locationsSplitByComma, country, countryName);
let coords = entries[1];
//entry.Location = [entry.Country, entry.Location, entries]; debug!
let latitude = 0;
let longitude = 0;
coords.forEach(coord => {
latitude += parseFloat(coord[0]);
longitude += parseFloat(coord[1]);
})
entry.Latitude = coords[0][0]//parseFloat(latitude)/parseFloat(coords.length);
entry.Longitude = coords[0][1]//(longitude)/parseFloat(coords.length);
})
return tempEntries;
}
Insert cell
function findLocationEntries(entry, locations, country, countryName){
let entries = []
let coords = [];
let countryCoords = []
locations.forEach(location => {
let entriesForLocation = []
cities.forEach(tempCountry => {
if(country === "PAZ"){country = "PRT"}
if(country === tempCountry.iso3 || countryName === tempCountry.name){
//countryCoords = [parseFloat(tempCountry.latitude), parseFloat(tempCountry.longitude)];
countryCoords = [tempCountry.latitude, tempCountry.longitude];
tempCountry.states.forEach(state => {
if(location.includes(state.name.normalize("NFD").replace(/[\u0300-\u036f]/g, ""))){
entriesForLocation.push({
name: "(S) " + state.name,
stateCode: state.state_code,
stateLatitude: state.latitude,
stateLongitude: state.longitude
})
if(checkIfCoordinatesAreUnique(coords, state.latitude, state.longitude)){
//coords.push([parseFloat(state.latitude), parseFloat(state.longitude)]);
coords.push([state.latitude, state.longitude]);
}
}
state.cities.forEach(city => {
if(location.includes(city.name.normalize("NFD").replace(/[\u0300-\u036f]/g, ""))){
entriesForLocation.push({
name: "(C) " + city.name,
stateCode: state.state_code,
stateLatitude: state.latitude,
stateLongitude: state.longitude,
latitude: city.latitude,
longitude: city.longitude,
})
if(checkIfCoordinatesAreUnique(coords, state.latitude, state.longitude)){
//coords.push([parseFloat(state.latitude), parseFloat(state.longitude)]);
coords.push([state.latitude, state.longitude]);
}
}
})
})
}
})
if(entriesForLocation.length > 0){
entries.push(entriesForLocation);
}
})
if(coords.length < 1){
coords.push(countryCoords);
}
return [entries, coords];
}
Insert cell
filled = fillLatitudeAndLongitude(catastrophesByYear)
Insert cell
/*// debug! Recursive function to serialize nested arrays
function serializeArray(arr) {
if (!Array.isArray(arr)) {
// If arr is not an array, return it as is (already serialized)
return arr;
}

return arr.map(item => {
// Recursively serialize nested arrays
return serializeArray(item);
});
}*/
Insert cell
/*//debug!
function flatten(){
let tempArray = cloneObject(filled);
filled.forEach(entry => {
let location = JSON.stringify(serializeArray(entry.Location))
entry.Location = location
})
return tempArray;
}*/
Insert cell
//flattened = flatten() debug!
Insert cell
mistakes_corrected = FileAttachment("Mistakes_Corrected.json").json()
Insert cell
citiesCorrected = mistakes_corrected.map(d => d[1])
Insert cell
completed = implementCorrectedMistakes(filled, citiesCorrected)
Insert cell
completedMap = fillLatitudeAndLongitude(completed)
Insert cell
completedMap
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
missing = nest(FilledCoordinates, d => d.ISO, d => d.Country)
Insert cell
test.get("PAZ")
Insert cell
completedMap[43].Location
Insert cell
mistakes = FilledCoordinates.map(d => [d.ISO, d.Location.replace(/^"|"$/g, "", "")])
Insert cell
/*function extractFromLarge() {
let tempProvinces = cloneObject(large);
tempProvinces.features.forEach(province => {
let iso3166 = province.properties.iso_3166_2.split("-")
let provinceObject = {
countryName: province.properties.admin,
iso3: province.properties.gu_a3,
country: iso3166[0],
state: iso3166[1],
name: province.properties.gn_name,
type: province.properties.type_en,
latitude: province.properties.latitude,
longitude: province.properties.longitude,
};
province.properties = provinceObject;
})
return tempProvinces;
*/
Insert cell
tempContinents = createContinents()
Insert cell
countriesTopo = topojson.feature(countriesDownload, countriesDownload.objects.jsonDownload)
Insert cell
continentTopo = topojson.feature(tempContinents, tempContinents.objects.continents)
Insert cell
world = FileAttachment("countries-50m (1).json").json()
Insert cell
countriesChanged = FileAttachment("Countries@1.json").json()
Insert cell
chartContinents = createZoomAbleMap(900, 500, 0.5, 100, d3.geoNaturalEarth1(), continentTopo, "red", "white", "#444", true, "orange", 12)
Insert cell
chartRegions = createZoomAbleMap(900, 500, 0.5, 100, d3.geoNaturalEarth1(), regions, "red", "white", "#444", true, "orange", 12)
Insert cell
viewof chartCountries = createZoomAbleMap(900, 500, 0.5, 100, d3.geoNaturalEarth1(), countriesChanged, "red", "white", "#444")
Insert cell
function createTooltip(d){
const location = d.Location ? `Location: ${d.Location} ` : "";
const country = d.Country ? `(${d.Country.replace(" (the)", "")})` : "";
const seq = d.Seq ? `Nr. ${d.Seq}` : "";
const disasterSubgroup = d.DisasterSubgroup ? `${d.DisasterSubgroup} Catastrophe` : "";
const disasterSubtype = d.DisasterSubtype ? `(${d.DisasterSubtype})` : "";
const disasterSubsubtype = d.DisasterSubsubtype ? `-${d.DisasterSubsubtype}` : "";
const startMonth = d.StartMonth ? `Time: ${d.StartMonth}/` : "";
const startYear = d.StartYear ? `${d.StartYear}` : "";
const endMonth = d.EndMonth ? `-${d.EndMonth}/` : "";
const endYear = d.EndYear ? `${d.EndYear}` : "";
const totalAffected = d.TotalAffected ? `Total Affected: ${d.TotalAffected}` : "";

return `${location} ${country}\n${seq} ${disasterSubgroup} ${disasterSubtype} ${disasterSubsubtype}\n${startMonth}${startYear}${endMonth}${endYear}\n${totalAffected}`
}
Insert cell
function createZoomAbleMap(chosenWidth=900, chosenHeight=500, minZoom=0.5, maxZoom=100, projection=d3.geoMercator(), data=countries, fillColor="red", strokeColor="white", fillColorNone="#444", names = false, textColor = "grey", fontSize = 1, markersData=completedMap, radius=1, markersFillColor="orange") {
const width = chosenWidth;
const height = chosenHeight;

const zoom = d3.zoom()
.scaleExtent([minZoom, maxZoom])
.on("zoom", zoomed);

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: 100%;")
.on("click", reset);

const path = d3.geoPath(projection);

const g = svg.append("g");

const states = g.append("g")
.attr("fill", fillColorNone)
.attr("cursor", "pointer")
.selectAll("path")
.data(data.features)
.join("path")
.attr("d", path)
.on("click", clicked);

let textLabels = "";

if (names) {
textLabels = g.append("g")
.attr("pointer-events", "none") // Ensure text labels don't interfere with zooming and clicking
.selectAll("text")
.data(data.features)
.join("text")
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text(d => d.properties.name);
} else {
states.append("title").text(d => d.properties.name);
}

const markers = g.append("g")
.attr("fill-opacity", 0.3)
.selectAll("circle")
.attr("cursor", "pointer")
.data(markersData)
.join("circle")
.attr("cx", d => projection([d.Longitude, d.Latitude])[0])
.attr("cy", d => projection([d.Longitude, d.Latitude])[1])
.attr("r", radius)
.attr("fill", markersFillColor)
.append("title")
.text(d => createTooltip(d));

g.append("path")
.attr("fill", "none")
.attr("stroke", strokeColor)
.attr("stroke-linejoin", "round")
.attr("d", path(countrymesh.features));

svg.call(zoom);

function reset() {
states.transition().style("fill", null);
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity,
d3.zoomTransform(svg.node()).invert([width, height])
);
}

function clicked(event, d) {
const [[x0, y0], [x1, y1]] = path.bounds(d);
event.stopPropagation();
states.transition().style("fill", null);
d3.select(this).transition().style("fill", fillColor);
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(Math.min(100, 0.9 / Math.max((x1 - x0) / width, (y1 - y0) / height)))
.translate(-(x0 + x1) / 2, -(y0 + y1) / 2),
d3.pointer(event, svg.node()),
);
console.log(d.properties.name);
}

function zoomed(event) {
const {transform} = event;
g.attr("transform", transform);
g.attr("stroke-width", 1 / transform.k);
if (names) {
textLabels.attr("transform", d => `translate(${path.centroid(d)}) scale(${Math.min(transform.k / 10, 5)})`);
textLabels.style("font-size", fontSize); // Adjust font size based on zoom scale
textLabels.attr("fill", textColor);
}
// Update marker positions on zoom
markers.attr("x", d => projection([d.Longitude, d.Latitude])[0])
.attr("y", d => projection([d.Longitude, d.Latitude])[1]);
}

return svg.node();
}
Insert cell
function implementCorrectedMistakes(flattened, mistakesCorrected){
var tempMap = cloneObject(catastrophesByYear);
var i = 0;
let pos = 0;
flattened.forEach(object => {
if(object.Latitude === ""){
if(mistakesCorrected[i] !== undefined){
let location = mistakesCorrected[i];
tempMap[pos].Location = location;
i++;
}
}
pos++;
})
return tempMap;
}
Insert cell
countrymesh = topojson.mesh(world, world.objects.countries, (a, b) => a !== b);
Insert cell
australiaAndNewZealand = FileAttachment("Australia and New Zealand@1.geojson").json()
Insert cell
caribbeans = FileAttachment("Caribbean.geojson").json()
Insert cell
centralAmerica = FileAttachment("Central America.geojson").json()
Insert cell
centralAsia = FileAttachment("Central Asia.geojson").json()
Insert cell
easternAfrica = FileAttachment("Eastern Africa.geojson").json()
Insert cell
easternAsia = FileAttachment("Eastern Asia.geojson").json()
Insert cell
easternEurope = FileAttachment("Eastern Europe.geojson").json()
Insert cell
melanesia = FileAttachment("Melanesia.geojson").json()
Insert cell
micronesia = FileAttachment("Micronesia.geojson").json()
Insert cell
middleAfrica = FileAttachment("Middle Africa.geojson").json()
Insert cell
northernAfrica = FileAttachment("Northern Africa.geojson").json()
Insert cell
northernAmerica = FileAttachment("Northern America.geojson").json()
Insert cell
northernEurope = FileAttachment("Northern Europe.geojson").json()
Insert cell
polynesia = FileAttachment("Polynesia@2.geojson").json()
Insert cell
russianFederation = FileAttachment("Russian Federation.geojson").json()
Insert cell
southAmerica = FileAttachment("South America@2.geojson").json()
Insert cell
southEasternAsia = FileAttachment("South-Eastern Asia.geojson").json()
Insert cell
southernAfrica = FileAttachment("Southern Africa@1.geojson").json()
Insert cell
southernAsia = FileAttachment("Southern Asia.geojson").json()
Insert cell
southernEurope = FileAttachment("Southern Europe.geojson").json()
Insert cell
westernAfrica = FileAttachment("Western Africa.geojson").json()
Insert cell
westernAsia = FileAttachment("Western Asia.geojson").json()
Insert cell
westernEurope = FileAttachment("Western Europe.geojson").json()
Insert cell
regionsGeoJson = [australiaAndNewZealand, caribbeans, centralAmerica, centralAsia, easternAfrica, easternAsia, easternEurope, melanesia, micronesia, middleAfrica, northernAfrica, northernAmerica, northernEurope, polynesia, russianFederation, southAmerica, southEasternAsia, southernAfrica, southernAsia, southernEurope, westernAfrica, westernAsia, westernEurope]
Insert cell
regions = createGeoObjectFromGeoJsonArray(regionsGeoJson, "Regions")
Insert cell
function createGeoObjectFromGeoJsonArray(array, name){
let object = {
type: "FeatureCollection",
name: name,
features: []
}

array.forEach(geoJsonObject => {
let feature = geoJsonObject.features[0];
let tempObject = {
type: "Feature",
properties: {name: feature.properties.ADMIN},
geometry: feature.geometry
}
object.features.push(tempObject);
})
return object;
}
Insert cell
function cloneObject(obj) {
if (typeof obj !== 'object' || obj === null) {
// If the value is not an object, return it directly
return obj;
}

const clonedObj = Array.isArray(obj) ? [] : {}; // Determine if obj is an array or an object
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// Recursively clone nested objects
clonedObj[key] = cloneObject(obj[key]);
}
}
return clonedObj;
}
Insert cell
function nest(values, ...keys) {
return (function regroup(values, i) {
if (i >= keys.length) return values;
const map = group(values, keys[i]);
return new Map(Array.from(map, ([k, v]) => [k, regroup(v, i + 1)]));
})(values, 0);
}
Insert cell
function groupReduce(values, keyof = identity, reduce, init = noop) {
const map = new Map();
let index = -1;
for (const value of values) {
const key = keyof(value, ++index, values);
map.set(key, reduce(map.has(key) ? map.get(key) : init(key), value, index, values));
}
return map;
}
Insert cell
group = {
const reduce = (p, v) => (p.push(v), p);
const init = () => [];
return function group(values, keyof) {
return groupReduce(values, keyof, reduce, init);
};
}
Insert cell
function identity(x) { return x; }
Insert cell
//Whole section from: https://observablehq.com/@mbostock/group & https://observablehq.com/@mbostock/nested-groups
function noop() {}
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