Public
Edited
Sep 11, 2023
Insert cell
Insert cell
root = {

let population = await d3.csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2023/2023-08-22/population.csv");
let populationedFilter = population.filter(pop => pop.coa_name == "France" | pop.coa_name == "Canada" | pop.coa_name == "Germany" | pop.coa == "USA" | pop.coa == "TUR" | pop.coa == "GBR" | pop.coa == "ITA" | pop.coa == "RUS");
let populationedSimplified = d3.rollups(populationedFilter, v => d3.sum(v, d => d.refugees), d => d.coa_name, d => d.coo_name);

let groupAggregator = function(subset) {
let tempArray = [];
for (let i = 0; i < subset.length; i++) {
tempArray.push(subset[i][1]);
}
let sum = 0;
tempArray.forEach(a => {sum += a;});
return ["Other", sum];
}
let aggregatedArray = [];
for (let i = 0; i < populationedSimplified.length; i++) {
let top10 = [populationedSimplified[i][0], populationedSimplified[i][1].sort((a, b) => b[1] - a[1]).slice(0,10)];
let toBeGrouped = populationedSimplified[i][1].sort((a, b) => b[1] - a[1]).slice(10,);
aggregatedArray.push(top10);
aggregatedArray[i][1].push(groupAggregator(toBeGrouped));
}

aggregatedArray[2][0] = "United Kingdom"
aggregatedArray[7][0] = "United States"
aggregatedArray.sort((a, b) => (a[0] > b[0]) ? 1 : ((b[0] > a[0]) ? -1 : 0));

let objectChildConverter = function(child, k) {
let tempObject = {name: false,
value: false};
tempObject.name = child[k][0];
tempObject.value = child[k][1];

return tempObject;
}
let objectParentConverter = function(data, i) {
let tempObject = {name: false,
children: false};
tempObject.name = data[i][0];

let tempChildren = []

for (let k = 0; k < data[i][1].length; k++) {
tempChildren.push(objectChildConverter(data[i][1], k))
}
tempObject.children = tempChildren;
return tempObject;
};

let data = [];
for (let i = 0; i < aggregatedArray.length; i++) {
data.push(objectParentConverter(aggregatedArray,i));
}

// data.sort((a, b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0));
return d3.hierarchy({
name: "refugee_data",
children: data})
.sum(d => d.value)
.sort((a, b) => b.name - a.name)
.eachAfter(d => d.index = d.parent ? d.parent.index = d.parent.index + 1 || 0 : 0)
}
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");

//x.domain([0, d3.max(population_for_viz, d => d.refugees)])
x.domain([0, root.value])

svg.append("rect")
.attr("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.attr("class", "background")
.attr("fill","none")
.on("click", (event, d) => up(svg, d));

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

down(svg, root);

return svg.node();
}


Insert cell
// Creates a set of bars for the given data node, at the specified index.
function bar(svg, down, d, selector) {
const g = svg.insert("g", selector)
.attr("class", "enter")
.attr("transform", `translate(0,${marginTop + barStep * barPadding})`)
.attr("text-anchor", "end")
.style("font", "11px Lato Light");

const bar = g.selectAll("g")
.data(d.children)
.join("g")
.attr("cursor", d => !d.children ? null : "pointer")
.on("click", (event, d) => down(svg, d));

bar.append("text")
.attr("x", marginLeft - 6)
.attr("y", barStep * (1 - barPadding) / 2)
.attr("dy", ".35em")
.text(d => d.data.name);

bar.append("rect")
.attr("x", x(0))
.attr("width", d => x(d.value) - x(0))
.attr("height", barStep * (1 - barPadding));

return g;
}
Insert cell
function down(svg, d) {
if (!d.children || d3.active(svg.node())) return;
if (d3.active(svg.node())) return;
// Rebind the current node to the background.
svg.select(".background").datum(d);

// Define two sequenced transitions.
const transition1 = svg.transition().duration(duration);
const transition2 = transition1.transition();

// Mark any currently-displayed bars as exiting.
const exit = svg.selectAll(".enter")
.attr("class", "exit");
// Entering nodes immediately obscure the clicked-on bar, so hide it.
exit.selectAll("rect")
.attr("fill-opacity", p => p === d ? 0 : null);

// Transition exiting bars to fade out.
exit.transition(transition1)
.attr("fill-opacity", 0)
.remove();

// Enter the new bars for the clicked-on data.
// Per above, entering bars are immediately visible.
const enter = bar(svg, down, d, ".y-axis")
.attr("fill-opacity", 0);

// Have the text fade-in, even though the bars are visible.
enter.transition(transition1)
.attr("fill-opacity", 1);

// Transition entering bars to their new y-position.
enter.selectAll("g")
.attr("transform", stack(d.index))
.transition(transition1)
.attr("transform", stagger());

// Update the x-scale domain.
x.domain([0, d3.max(d.children, d => d.value)]);

// Update the x-axis.
svg.selectAll(".x-axis").transition(transition2)
.call(xAxis);

// Transition entering bars to the new x-scale.
enter.selectAll("g").transition(transition2)
.attr("transform", (d, i) => `translate(0,${barStep * i})`);

// Color the bars as parents; they will fade to children if appropriate.
enter.selectAll("rect")
.attr("fill", color(true))
.attr("fill-opacity", 1)
.transition(transition2)
.attr("fill", d => color(!!d.children))
.attr("width", d => x(d.value) - x(0));


// fade in new backgrond
// background image
backgroundImage(svg, d.data.name);
}
Insert cell
function up(svg, d) {
if (!d.parent || !svg.selectAll(".exit").empty()) return;

// Rebind the current node to the background.
svg.select(".background").datum(d.parent);

// Define two sequenced transitions.
const transition1 = svg.transition().duration(duration);
const transition2 = transition1.transition();

// Mark any currently-displayed bars as exiting.
const exit = svg.selectAll(".enter")
.attr("class", "exit");

// Update the x-scale domain.
x.domain([0, d3.max(d.parent.children, d => d.value)]);

// Update the x-axis.
svg.selectAll(".x-axis").transition(transition1)
.call(xAxis);

//svg.selectAll("rect")
// .attr("cursor", "alias");
// .attr("class", "background")
// .attr("fill", "none");
// Transition exiting bars to the new x-scale.
exit.selectAll("g").transition(transition1)
.attr("transform", stagger());

// Transition exiting bars to the parent’s position.
exit.selectAll("g").transition(transition2)
.attr("transform", stack(d.index));

// Transition exiting rects to the new scale and fade to parent color.
exit.selectAll("rect").transition(transition1)
.attr("width", d => x(d.value) - x(0))
.attr("fill", color(true));

// Transition exiting text to fade out.
// Remove exiting nodes.
exit.transition(transition2)
.attr("fill-opacity", 0)
.remove();

// background
svg.selectAll("image").remove();

// Enter the new bars for the clicked-on data's parent.
const enter = bar(svg, down, d.parent, ".exit")
.attr("fill-opacity", 0);

enter.selectAll("g")
.attr("transform", (d, i) => `translate(0,${barStep * i})`);

// Transition entering bars to fade in over the full duration.
enter.transition(transition2)
.attr("fill-opacity", 1);

// Color the bars as appropriate.
// Exiting nodes will obscure the parent bar, so hide it.
// Transition entering rects to the new x-scale.
// When the entering parent rect is done, make it visible!
enter.selectAll("rect")
.attr("fill", d => color(!!d.children))
.attr("fill-opacity", p => p === d ? 0 : null)
.transition(transition2)
.attr("width", d => x(d.value) - x(0))
.on("end", function(p) { d3.select(this).attr("fill-opacity", 1); });
}
Insert cell
function stack(i) {
let value = 0;
return d => {
const t = `translate(${x(value) - x(0)},${barStep * i})`;
value += d.value;
return t;
};
}
Insert cell
function stagger() {
let value = 0;
return (d, i) => {
const t = `translate(${x(value) - x(0)},${barStep * i})`;
value += d.value;
return t;
};
}
Insert cell
x = d3.scaleLinear().range([marginLeft, width - marginRight])
Insert cell
xAxis = g => g
.attr("class", "x-axis")
.attr("transform", `translate(0,${marginTop})`)
.call(d3.axisTop(x).ticks(width / 80, "s"))
.call(g => (g.selection ? g.selection() : g).select(".domain").remove())
Insert cell
yAxis = g => g
.attr("class", "y-axis")
.attr("transform", `translate(${marginLeft + 0.5},0)`)
.call(g => g.append("line")
.attr("stroke", "currentColor")
.attr("y1", marginTop)
.attr("y2", height - marginBottom))
Insert cell
height = {
let max = 1;
root.each(d => d.children && (max = Math.max(max, d.children.length)));
return max * barStep + marginTop + marginBottom;
}
Insert cell
marginBottom = 0
Insert cell
marginLeft = 100;
Insert cell
marginTop = 30;
Insert cell
marginRight = 30;
Insert cell
barStep = 27
Insert cell
barPadding = 3 / barStep
Insert cell
color = d3.scaleOrdinal([true, false], ["steelblue", "#aaa"])
Insert cell
duration = 750
Insert cell
function backgroundImage(svg, name) {

if (name != "refugee_data") {

svg.append("defs")
.append("linearGradient").attr("id", "Gradient1")
.append("stop").attr("offset", "0").attr("stop-color", "black");
svg.selectAll("#Gradient1")
.append("stop").attr("offset", ".5").attr("stop-color", "white");
svg.selectAll("#Gradient1")
.append("stop").attr("offset", ".92").attr("stop-color", "black");
svg.selectAll("defs")
.append("mask")
.attr("id", "Mask1")
.append("rect")
.attr("x",marginTop*1.1)
.attr("y",marginLeft)
.attr('width', width)
.attr('height', height)
.attr("fill","url(#Gradient1)");
svg.selectAll("defs")
.append("linearGradient")
.attr("id", "Gradient2")
.attr("x1", "0")
.attr("x2", "0")
.attr("y1", "0")
.attr("y2", "1")
.append("stop").attr("offset", "0").attr("stop-color", "black")
svg.selectAll("#Gradient2")
.append("stop").attr("offset", "1").attr("stop-color", "white");
svg.selectAll("defs")
.append("mask")
.attr("id", "Mask2")
.append("rect")
.attr("mask","url(#Mask1)")
.attr("x",marginTop*1.1)
.attr("y",marginLeft)
.attr('width', width)
.attr('height', height)
.attr("fill","url(#Gradient2)");
let imageLink = countryImageMap.filter(a => a.nameToMap == name)[0]["link"];
svg.insert("svg", ":first-child")
.append("image")
.attr("id","background_image")
.attr('xlink:href', imageLink)
.attr("width", width-marginLeft)
.attr("height", height)
.attr("y", marginTop*1.1)
.attr("x", marginLeft)
.attr("preserveAspectRatio", "xMidYMid slice")
.attr("opacity", "0")
.attr("mask", "url(#Mask1)")
.attr("mask", "url(#Mask2)")
.transition();

const transition3 = svg.transition()
.duration(1500);
svg.selectAll("#background_image")
.transition(transition3)
.attr("opacity", ".95");
}
}
Insert cell
await countryImageMap.filter(a => a.nameToMap == "France")[0]["link"]
Insert cell
countryImageMap = [
{nameToMap: "Canada",
link: "https://github.com/taylorrodgers/public_datasets/blob/26ff5e3c64f16bab69b45dc50aaf51107cfafba3/images/data_story_canada.jpg?raw=true&fit=crop&w=200&h=200"},
{nameToMap: "France",
link: "https://github.com/taylorrodgers/public_datasets/blob/7646676719fb0c3ab144cbf9c07672b38af25aa0/images/data_story_paris_skyline.jpg?raw=true&fit=crop&w=200&h=200"}, // https://www.pexels.com/@alecdoua/
{nameToMap: "Germany",
link: "https://github.com/taylorrodgers/public_datasets/blob/3ae52e90bbe68ec8f2d6500e430349d92ceb9449/images/data_story_germany.jpg?raw=true&fit=crop&w=200&h=200"}, //https://www.pexels.com/@lander-lai-64457665/
{nameToMap: "Italy",
link: "https://github.com/taylorrodgers/public_datasets/blob/440c8cd4a13cc756ba7e097f6c711bc7bd111e8e/images/data_story_italy.jpg?raw=true&fit=crop&w=200&h=200"}, // https://www.pexels.com/@yankrukov/f
{nameToMap: "Russian Federation",
link: "https://github.com/taylorrodgers/public_datasets/blob/64a9dd32b29345c06fbcd49e5e0f6d0c646b0ac7/images/data_story_kremlin.jpg?raw=true&fit=crop&w=200&h=200"}, // https://www.pexels.com/@67117688/
{nameToMap: "Türkiye",
link: "https://github.com/taylorrodgers/public_datasets/blob/47910c01e1adff3f0750a641ff96e2d8ae368c5e/images/turkey_photo2.JPG?raw=true&fit=crop&w=200&h=200"},
{nameToMap: "United Kingdom",
link: "https://github.com/taylorrodgers/public_datasets/blob/5125e4ff20d6efce9915ab1335f9e2c503523ef1/images/data_story_london_skyline.jpg?raw=true&fit=crop&w=200&h=200"}, // https://www.pexels.com/@skitterphoto/
{nameToMap: "United States",
link: "https://github.com/taylorrodgers/public_datasets/blob/main/images/data_story_statue_of_liberty.jpg?raw=true&fit=crop&w=200&h=200"} // https://www.pexels.com/@domenico-solimeno-6463499/
];
Insert cell
root.data.name
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