Public
Edited
Apr 29
Insert cell
Insert cell
Insert cell
d3 = require("d3")
Insert cell
data = d3.csvParse(await FileAttachment("titanic.csv").text(), d3.autoType)
Insert cell
Insert cell
cleanData = {
return data.map(d => {
let country;
if (d.Embarked === 'Q') country = 'Ireland';
else if (d.Embarked === 'C') country = 'France';
else if (d.Embarked === 'S') country = 'England';
else country = 'Bad/Missing Data';
return { ...d, Embarked: country };
});
}

Insert cell
Insert cell
passengerIdentities = {
let irishPassengers = [0, 0, 0];
let englishPassengers = [0, 0, 0];
let frenchPassengers = [0, 0, 0];
cleanData.forEach(row => {
if (row.Embarked == 'Ireland'){
irishPassengers[row.Pclass - 1] += 1;
} else if (row.Embarked == 'France'){
frenchPassengers[row.Pclass - 1] += 1;
} else if (row.Embarked == 'England'){
englishPassengers[row.Pclass - 1] += 1;
}
});

return [
{nationality: "Irish", values: irishPassengers},
{nationality: "French", values: frenchPassengers},
{nationality: "English", values: englishPassengers}
];
}

Insert cell
Insert cell
// I used ChatGPT and the provided example code on Canvas to generate the basic code structure for the graphs
// I then modified it myself to suit my purposes and get it to display how I wanted it to

{
const width = 600;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 40;
const marginLeft = 40;

const classes = ["First Class", "Second Class", "Third Class"];
const nationalities = passengerIdentities.map(d => d.nationality);

const pData = classes.map((classVariable, i) => ({
class: classVariable,
values: passengerIdentities.map(d => ({
nationality: d.nationality,
value: d.values[i]
}))
}));

const xClass = d3.scaleBand()
.domain(classes)
.range([marginLeft, width - marginRight])
.padding(0.1);

const xNationality = d3.scaleBand()
.domain(nationalities)
.range([0, xClass.bandwidth()])
.padding(0.05);

const y = d3.scaleLinear()
.domain([0, d3.max(passengerIdentities.flatMap(d => d.values))])
.range([height - marginBottom, marginTop]);

const color = d3.scaleOrdinal()
.domain(nationalities)
.range([
"#4EA204", // Irish
"#5349AE", // English
"#B2040D" // French
]);

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

svg.append("g")
.selectAll("g")
.data(pData)
.join("g")
.attr("transform", d => `translate(${xClass(d.class)},0)`)
.selectAll("rect")
.data(d => d.values)
.join("rect")
.attr("x", d => xNationality(d.nationality))
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))
.attr("width", xNationality.bandwidth())
.attr("fill", d => color(d.nationality));

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(xClass));

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).ticks(5));
const legend = svg.append("g")
.attr("transform", `translate(${marginLeft + (width / 20)}, ${marginTop})`);
nationalities.forEach((nat, i) => {
const legendRow = legend.append("g")
.attr("transform", `translate(0, ${i * 20})`);
legendRow.append("rect")
.attr("width", 15)
.attr("height", 15)
.attr("fill", color(nat));
legendRow.append("text")
.attr("x", 20)
.attr("y", 12)
.text(nat)
.style("font-size", "12px")
.attr("alignment-baseline", "middle");
});

return svg.node();
}

Insert cell
Insert cell
survivorshipData = cleanData.map(d => ({
survived: +d.Survived,
age: +d.Age,
fare: +d.Fare
})).filter(d => !isNaN(d.age) && !isNaN(d.fare));
Insert cell
Insert cell
// I used ChatGPT and the provided example code on Canvas to generate the basic code structure for the graphs
// I then modified it myself to suit my purposes and get it to display how I wanted it to.

{
const width = 600;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 40;
const marginLeft = 40;

const x = d3.scaleLinear()
.domain([0, d3.max(survivorshipData, d => d.age)])
.range([marginLeft, width - marginRight])

const y = d3.scaleLinear()
.domain([0, d3.max(survivorshipData, d => d.fare)])
.range([height - marginBottom, marginTop]);

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

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x));

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y));

svg.append("text")
.attr("x", width / 2)
.attr("y", height - 5)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Age");

svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", 10)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Fare");

var points = svg.selectAll("g")
.data(survivorshipData)
.enter()
.append("path");

var symbol = d3.symbol();
points.attr("d", symbol
.type(d => d.survived === 1 ? d3.symbolCircle : d3.symbolCross)
.size(50))
.attr("transform", d => `translate(${x(d.age)}, ${y(d.fare)})`)
.attr("fill", d => d.survived === 1 ? "steelblue" : "tomato");
const legend = svg.append("g")
.attr("transform", `translate(${marginLeft + (width / 10)},${marginTop})`);
const legendData = [
{ label: "Survived", shape: d3.symbolCircle, color: "steelblue" },
{ label: "Died", shape: d3.symbolCross, color: "tomato" }
];

const legendItem = legend.selectAll("g")
.data(legendData)
.enter()
.append("g")
.attr("transform", (d, i) => `translate(0, ${i * 20})`);

legendItem.append("path")
.attr("d", d => d3.symbol().type(d.shape).size(50)())
.attr("fill", d => d.color);

legendItem.append("text")
.attr("x", 10)
.attr("y", 5)
.text(d => d.label)
.style("font-size", "12px")
.attr("alignment-baseline", "middle");

return svg.node();
}

Insert cell
Insert cell
Insert cell
function getSankeyData(country){
let totalPassengers = 0;
let firstClass = 0;
let seecondClass = 0;
let thirdClass = 0;
let survived_1stClass = 0;
let died_1stClass = 0;
let survived_2ndClass = 0;
let died_2ndClass = 0;
let survived_3rdClass = 0;
let died_3rdClass = 0;

cleanData.forEach(row => {
if (row.Embarked == country){
totalPassengers++;
if (row.Pclass == 1){
firstClass++;
if (row.Survived == 1){
survived_1stClass++;
} else {
died_1stClass++;
}
} else if (row.Pclass == 2){
seecondClass++;
if (row.Survived == 1){
survived_2ndClass++;
} else {
died_2ndClass++;
}
} else {
thirdClass++;
if (row.Survived == 1){
survived_3rdClass++;
} else {
died_3rdClass++;
}
}
}
})

return {
nodes: [
{ name: "Passengers" }, //0

{ name: "First Class" }, //1
{ name: "Second Class" }, //2
{ name: "Third Class" }, //3

{ name: "Survived" }, //4
{ name: "Died" } //5
],
links: [
{source: 0, target: 1, value: firstClass},
{source: 0, target: 2, value: seecondClass},
{source: 0, target: 3, value: thirdClass},

{source: 1, target: 4, value: survived_1stClass},
{source: 1, target: 5, value: died_1stClass},

{source: 2, target: 4, value: survived_2ndClass},
{source: 2, target: 5, value: died_2ndClass},

{source: 3, target: 4, value: survived_3rdClass},
{source: 3, target: 5, value: died_3rdClass},
]
}
}
Insert cell
Insert cell
sankeyModule = require("d3-sankey");
Insert cell
Insert cell
// I used ChatGPT and the provided example code on Canvas to generate the basic code structure for the graphs
// I then modified it myself to suit my purposes and get it to display how I wanted it to

function createSankeyChart(sankey_data, color, countryName) {
const { sankey: sankeyLayout, sankeyLinkHorizontal } = sankeyModule;
const width = 350;
const height = 150; // Increased height to avoid cutting off text

const orderMap = {
"First Class": 0,
"Second Class": 1,
"Third Class": 2,
"Survived": 0,
"Died": 1
};
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.style("max-width", "100%");

const sankeyGenerator = sankeyLayout()
.nodeWidth(20)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 50]])
.nodeSort((a, b) => {
const oa = orderMap[a.name] != null ? orderMap[a.name] : 0;
const ob = orderMap[b.name] != null ? orderMap[b.name] : 0;
return oa - ob;
});

const { nodes, links } = sankeyGenerator({
nodes: sankey_data.nodes.map(d => ({ ...d })),
links: sankey_data.links.map(d => ({ ...d }))
});

svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(links)
.join("path")
.attr("d", sankeyLinkHorizontal())
.attr("stroke", (d) => color)
.attr("stroke-opacity", 0.5)
.attr("stroke-width", d => Math.max(1, d.width))
.style("display", d => d.value === 0 ? "none" : null);

svg.append("g")
.selectAll("rect")
.data(nodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("fill", (d) => color)
.attr("stroke", "#000");

svg.append("g")
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("y", d => (d.y1 + d.y0) / 2 + 3)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(d => d.name)
.style("font-size", "15px");

const legend = svg.append("g")
.attr("transform", `translate(${width / 2}, ${height - 20})`)
.attr("text-anchor", "middle");
legend.append("text")
.attr("x", 0)
.attr("y", 0)
.text(countryName)
.style("font-size", "12px")
.attr("dy", "0.35em");

return svg.node();
}
Insert cell
Insert cell
irishSankey = {
const color = "#4EA204";
return createSankeyChart(getSankeyData("Ireland"), color, "Ireland");
};
Insert cell
Insert cell
englishSankey = {
const color = "#B2040D";
return createSankeyChart(getSankeyData("England"), color, "England");
};
Insert cell
Insert cell
frenchSankey = {
const color = "#5349AE";
return createSankeyChart(getSankeyData("France"), color, "France");
};
Insert cell
Insert cell
// I used ChatGPT and the provided example code on Canvas to generate the basic code structure for the graphs
// I then modified it myself to suit my purposes and get it to display how I wanted it to

allSankeys = html`
<div style="
display: flex;
flex-direction: row;
gap: 20px;
align-items: left;
">
${irishSankey}
${englishSankey}
${frenchSankey}
</div>
`;

Insert cell
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