Published
Edited
Mar 8, 2022
1 fork
Importers
Insert cell
Insert cell
viewof selectedYear = Inputs.range([1872, 2021], { step: 1, value: 2021 })
Insert cell
geo_map(0.8 * width, 0.5 * width)
Insert cell
topojson = require ("topojson-client@3")
Insert cell
d3geo = require('d3-geo-projection')
Insert cell
// Separate geographic dataset for uk countries :
//Taken from https://bost.ocks.org/mike/map/uk.json
uk = d3.json("https://raw.githubusercontent.com/onaly/storage/main/uk.json")
Insert cell
world = d3.json("https://unpkg.com/world-atlas@1/world/50m.json")
Insert cell
countries = topojson.feature(world, world.objects.countries)
Insert cell
countries_codes = d3.csv(
"https://raw.githubusercontent.com/onaly/storage/main/countries_codes_with_uk_subcountries.csv"
)
Insert cell
subunits = topojson.feature(uk, uk.objects.subunits)
Insert cell
//Remove Republic of Ireland from subunits :
new_subunit_features = subunits.features.filter(function (value, index, arr) {
return value.id != "IRL";
})
Insert cell
countries_hash = new Map(countries_codes.map(d => [d['country-code'], d.name]))
Insert cell
dataset = d3.csv(
"https://raw.githubusercontent.com/onaly/storage/main/yearly_scored_goals_by_team.csv",
(d) => {
let res = {};
res.country = d.team;
res.year = parseInt(d.date);
res.goals = parseInt(d.scored_goals);
return res;
}
)
Insert cell
geo_map = (w = 1000, h = 1000, column = "goals", g) => {
const svg =
g ||
d3
.create("svg")
.attr("class", "svg-map")
.attr("viewBox", [0, 0, w, h])
.attr("width", w)
.attr("height", h);

// Filter data by year
const filtered_data_by_year = dataset.filter(function (d) {
return d["year"] == selectedYear;
});

// Retrieve available countries in the dataset
const available_countries = Object.keys(
Object.fromEntries(d3.group(filtered_data_by_year, (d) => d.country))
);

// Function to retrieve country value for the given year :
function get_country_value(c) {
let result;
if (available_countries.indexOf(c) >= 0) {
result = filtered_data_by_year.filter(function (e) {
return e["country"] == c;
})[0][column];
} else {
result = -1;
}
return result;
}

// Define margin :
let margin = { top: 10, right: 10, bottom: 10, left: 10 };

//Define colors for the legend
let colors = [
"grey",
"#f0f9e8",
"#ccebc5",
"#a8ddb5",
"#7bccc4",
"#43a2ca",
"#0868ac"
];

//Convert value to color
let color_convertor = d3
.scaleThreshold()
.domain([0, 10, 20, 30, 40, 50])
.range(colors);

//Define width and height
let width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;

let projection = d3geo
.geoKavrayskiy7()
.fitSize([width * 1.2, ((width * 2) / 3) * 1.2], countries)
.translate([width * 0.46, ((width * 2) / 3) * 0.6]);

let path = d3.geoPath(projection);

// Function to create tooltip
const tip = d3tip()
.attr("class", "d3-tip")
.style("border", "solid 3px black")
.style("background-color", "white")
.style("border-radius", "10px")
.style("float", "left")
.style("font-family", "Tahoma")
.html(function (event, d) {
let base_str = `<div style='text-align: center'>
<span style='color:#2f7ebc'>
<strong>
${countries_hash.get(d.id)}
</strong><br>
</span>`;
let add_str;
let val = get_country_value(countries_hash.get(d.id));
if (val != -1) {
add_str = `<span style='text-align:center'>
Goals scored : <strong>${get_country_value(
countries_hash.get(d.id)
)}</strong>
</span>
</div>`;
} else {
add_str = "";
}
//Reduce the opacity of all countries ...
d3.selectAll(".country-scored").style("opacity", 0.15);
// ... except for the hovered country
d3.select(this)
.style("opacity", 1)
.attr("fill", function (d) {
return color_convertor(get_country_value(countries_hash.get(d.id)));
})
.attr("stroke-width", "1"); //We also add a stroke
return base_str + add_str;
});

// Define tooltip position (with special rules for some countries)
tip.offset(function (event, d) {
let result;
// Tooltip for US :
if (d.id == "840") {
result = [height / 10, -width / 3];
}
// Tooltip for Russia :
else if (d.id == "643") {
result = [height / 12, width / 4];
}
// Tooltip for Canada :
else if (d.id == "124") {
result = [height / 15, 0];
}
// Tooltip for the rest :
else {
result = [0, 0];
}
return result;
});

// world map
let gx = svg.append("g");

// Add country shapes + fill + behaviors on events
gx.selectAll("path")
.data(countries.features)
.enter()
.append("path")
.attr("d", path)
.attr("class", "country-scored")
.attr("fill", (d) => {
return color_convertor(get_country_value(countries_hash.get(d.id)));
})
.attr("stroke", "black")
.attr("stroke-width", "0.2")
.on("click", function (d) {
//This part is to make the connection with the scatterplot
//from https://observablehq.com/@onaly/goals-scatterplot
var id = d.srcElement.__data__.id;
var team = countries_hash.get(id);
d3.selectAll(".scatter-label-text")
.attr("opacity", 0)
.style("display", "none");
d3.selectAll(".scatter-flag")
.attr("opacity", 0.1)
.style("outline", "None");
d3.select(`#scatter-flag-${flag_data[team]}`)
.attr("opacity", 1)
.style("outline", "1px solid black");
d3.select(`#scatter-label-${flag_data[team]}`)
.transition(1000)
.attr("opacity", 1)
.style("display", "unset");
})
.on("mouseover", tip.show)
.on("mouseout", function (d) {
tip.hide(d); //Hide the tooltip
//Restore the opacity of all countries :
d3.selectAll(".country-scored")
.style("opacity", 1)
.attr("fill", function (d) {
return color_convertor(get_country_value(countries_hash.get(d.id)));
})
.attr("stroke-width", "0.2");
});

// Add uk countries separately since they are not official countries :
gx.append("path").datum(subunits).attr("d", path).attr("fill", "none");

// Same as the other (non-UK) countries :
gx.selectAll(".subunit")
.data(new_subunit_features)
.enter()
.append("path")
.attr("class", "country-scored")
.attr("d", path)
.attr("fill", function (d) {
return color_convertor(get_country_value(countries_hash.get(d.id)));
})
.attr("stroke", "black")
.attr("stroke-width", "0.1")
.on("click", function (d) {
var id = d.srcElement.__data__.id;
var team = countries_hash.get(id);
d3.selectAll(".scatter-label-text")
.attr("opacity", 0)
.style("display", "none");
d3.selectAll(".scatter-flag")
.attr("opacity", 0.1)
.style("outline", "None");
d3.select(`#scatter-flag-${flag_data[team]}`)
.attr("opacity", 1)
.style("outline", "1px solid black");
d3.select(`#scatter-label-${flag_data[team]}`)
.transition(1000)
.attr("opacity", 1)
.style("display", "unset");
})
.on("mouseover", tip.show)
.on("mouseout", function (d) {
tip.hide(d);
d3.selectAll(".country-scored")
.style("opacity", 1)
.attr("fill", function (d) {
return color_convertor(get_country_value(countries_hash.get(d.id)));
})
.attr("stroke-width", "0.2");
});

// Labels for legend :
let labels = color_convertor.domain().reverse();
let arrLength = labels.push(-1);

// Add legend shapes :
gx.selectAll("rect")
.data(labels)
.enter()
.append("rect")
.attr("x", width - 0.99 * width)
.attr("y", function (d, i) {
let result;
if (d != -1) {
result = 0.6 * height + i * (0.045 * height);
} else {
result = 0.62 * height + i * (0.045 * height);
}
return result;
})
.attr("width", 0.045 * height)
.attr("height", 0.045 * height)
.attr("stroke", "black")
.attr("stroke-width", "0.5")
.style("fill", color_convertor);

// Add legend text :
gx.selectAll("text")
.data(labels)
.enter()
.append("text")
.attr("x", 0.037 * width + width - 0.99 * width)
.attr("y", function (d, i) {
let result;
if (d != -1) {
result = 0.649 * height + i * (0.045 * height);
} else {
result = 0.648 * height + i * (0.045 * height);
}
return result;
})
.attr("font-size", 0.02 * height)
.text((d) => {
let result;
if (d != -1) {
result = Math.round(d);
} else {
result = "No data";
}
return result;
});

//-----------------------------------------------------------------------------------
//Chart title in the upper corner
svg
.append("text")
.attr("text-anchor", "end")
.text("Goals")
.attr("x", 0.995 * w)
.attr("y", 0.135 * h)
.attr("fill", "#0a4a90")
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
.attr("font-size", 0.057 * h);

svg
.append("text")
.attr("text-anchor", "end")
.text(selectedYear)
.attr("x", w)
.attr("y", 0.077 * h)
.attr("fill", "#0a4a90")
.attr("font-weight", "bold")
.attr("font-family", "sans-serif")
.attr("font-size", 0.077 * h);
//------------------------------------------------------------------------------------------------

// Function for zoom :
function zoomed({ transform }) {
gx.attr("transform", transform);
}

//To activate the tooltips:
svg.call(tip);

//To activate the zoom :
svg.call(
d3
.zoom()
.extent([
[0, 0],
[width, 100]
])
.scaleExtent([1, 8])
.on("zoom", zoomed)
);

return svg.node();
}
Insert cell
require("d3")
Insert cell
d3tip = require("d3-tip")
Insert cell
flag_array = FileAttachment("flags_dataset_flagcdn.csv").csv()
Insert cell
flag_data = flag_array[0]
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