Public
Edited
May 1
Insert cell
Insert cell
tested.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
chart = {
// Pull in just the D3 helpers we need to keep names short.(taught by GPT)
const {
bin, // to compute histogram bins
max, // to find the maximum age
range, // to generate our bin boundaries
scaleBand, // for mapping categories (age-ranges) to x positions
scaleLinear, // for mapping counts to y positions
create, // for building our <svg>
axisLeft, // to draw the y-axis
axisBottom // to draw the x-axis
} = d3;

// Define the overall SVG size and margins.
const width = 928;
const height = 500;
const margin = { top: 20, right: 30, bottom: 30, left: 40 };

// Filter to only those passengers who died (Survived === 0) and have a valid Age.
const deaths = tested1.filter(d => d.Survived === 0 && d.Age != null);

// Build 5-year age bins from 0 up to the oldest age in the filtered data.
// thresholds(range(1,100,5)) gives us cut points at 1,6,11,…,96.
const ageBins = bin()
.domain([0, max(deaths, d => d.Age)])
.thresholds(range(1, 100, 5))
(deaths.map(d => d.Age));

// Convert each bin into a simple object with a label and a count.
// e.g. { bin: "10–14", count: 42 }
const histogram = ageBins.map(b => ({
bin: `${b.x0}–${b.x1 - 1}`,
count: b.length
}));

// X scale: one band per age-range label.
const x = scaleBand()
.domain(histogram.map(d => d.bin))
.range([margin.left, width - margin.right])
.padding(0.1);

// Y scale: from 0 up to the largest bin count, flipped for SVG coords.
const y = scaleLinear()
.domain([0, max(histogram, d => d.count)]).nice()
.range([height - margin.bottom, margin.top]);

// Create our SVG with a responsive viewBox.
const svg = create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height]);

// Draw one <rect> per age-bin.
svg.selectAll("rect")
.data(histogram)
.join("rect")
.attr("x", d => x(d.bin)) // left edge
.attr("y", d => y(d.count)) // top edge
.attr("width", x.bandwidth()) // bar width
.attr("height", d => y(0) - y(d.count)) // bar height
.attr("fill", "steelblue"); // bar color

// Add the y-axis on the left, plus a label.
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(axisLeft(y))
.append("text")
.attr("fill", "currentColor")
.attr("x", -margin.left)
.attr("y", margin.top - 10)
.attr("text-anchor", "start")
.text("Number of Deaths");

// Add the x-axis on the bottom, rotating labels for clarity.
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(axisBottom(x))
.selectAll("text")
.attr("text-anchor", "end")
.attr("transform", "rotate(-20)")
.attr("dx", "-0.5em")
.attr("dy", "0.1em");

// Return the finished SVG node so Observable can render it.
return svg.node();
}



Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// learn from https://observablehq.com/@d3/parallel-sets#chart
chart2 = {
// Pull in just the D3 helpers we’ll actually use
const {
sankey, // handles positioning of the flows
sankeyLinkHorizontal, // draws the curved link paths
create, // a small helper to set up our SVG container
rollup, // handy for grouping and summing up values
InternMap, // fast lookup from node name → its index
scaleOrdinal // simple way to map class names to colors
} = d3;

// Pick the size for our SVG—wide enough to spread things out
const width = 928;
const height = 720;

// Count how many people are in each class, and add up their fares
const classes = ["1", "2", "3"];
const totalPeople = tested1.length;
const counts = new Map(classes.map(c => [c, 0]));
const fares = new Map(classes.map(c => [c, 0]));
tested1.forEach(d => {
const cls = String(d.Pclass);
counts.set(cls, counts.get(cls) + 1);
fares .set(cls, fares .get(cls) + (d.Fare || 0));
});

// Build our list of “nodes” in the order they should appear
// Root → classes → fare totals for each class
const nodes = [];
const indexByName = new InternMap([], JSON.stringify);
function addNode(name) {
indexByName.set(name, nodes.length);
nodes.push({ name });
}
addNode("All Passengers");
classes.forEach(c => addNode(`Class ${c}`));
classes.forEach(c => addNode(`Fare: Class ${c}`));

// Now connect them: first link everyone to their class,
// then link each class to its total fare bucket
const links = [];
classes.forEach(c => {
links.push({
source: indexByName.get("All Passengers"),
target: indexByName.get(`Class ${c}`),
value: counts.get(c)
});
});
classes.forEach(c => {
links.push({
source: indexByName.get(`Class ${c}`),
target: indexByName.get(`Fare: Class ${c}`),
value: fares.get(c)
});
});

// Run the Sankey layout—this calculates x0/x1/y0/y1 for nodes & links
const sankeyGen = sankey()
.nodeWidth(10) // how thick each node rectangle is
.nodePadding(20) // vertical spacing between nodes
.extent([[1,1], [width-1, height-1]]);
const { nodes: layoutNodes, links: layoutLinks } = sankeyGen({
nodes: nodes.map(d => ({ ...d })),
links: links.map(d => ({ ...d }))
});

// Pick some colors just for our three classes
const classColor = scaleOrdinal()
.domain(classes.map(c => `Class ${c}`))
.range(["steelblue", "indianred", "seagreen"]);

// Decide node fill: gray for the “All Passengers” root,
// colored otherwise by matching class number
function nodeColor(d) {
if (d.name === "All Passengers") return "#ccc";
const m = d.name.match(/\d/)[0];
return classColor(`Class ${m}`);
}

// Color each link by whichever end is “thicker”
function linkColor(d) {
const cls = d.source.name === "All Passengers"
? d.target.name.match(/\d/)[0]
: d.source.name.match(/\d/)[0];
return classColor(`Class ${cls}`);
}

// Create our SVG element and set a basic font
const svg = create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.style("font", "10px sans-serif");

// Draw the flow paths first (so they sit behind the nodes)
svg.append("g")
.attr("fill", "none")
.selectAll("path")
.data(layoutLinks)
.join("path")
.attr("d", sankeyLinkHorizontal())
.attr("stroke", linkColor)
.attr("stroke-width", d => d.width)
.style("mix-blend-mode", "multiply")
.append("title")
.text(d => `${d.source.name} → ${d.target.name}\n${d.value.toLocaleString()}`);

// Now draw the node rectangles on top
svg.append("g")
.selectAll("rect")
.data(layoutNodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.attr("fill", nodeColor)
.attr("stroke", "#000")
.append("title")
.text(d => `${d.name}\n${d.value.toLocaleString()}`);

// Finally, label everything with counts or fare sums
svg.append("g")
.selectAll("text")
.data(layoutNodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 6 : d.x0 - 6)
.attr("y", d => (d.y0 + d.y1) / 2)
.attr("dy", "0.35em")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(d => {
if (d.name === "All Passengers") {
return `Total People: ${totalPeople}`;
}
if (/^Class \d$/.test(d.name)) {
const cls = d.name.match(/\d/)[0];
return `Class ${cls}: ${counts.get(cls)}`;
}
const cls = d.name.match(/\d/)[0];
return `Fare: Class ${cls}: $${fares.get(cls).toLocaleString(undefined,{minimumFractionDigits:2})}`;
});

// Return our finished SVG so it can be attached to the page
return svg.node();
}

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart3 = {
const {
hierarchy, // to turn our nested data into a tree structure
tree, // to calculate x/y positions for each node
linkVertical, // to draw straight vertical connectors between nodes
create, // for setting up our SVG container
rollup // for grouping and counting records
} = d3;

// Decide how big our drawing area should be, and leave some margin for labels (learned from https://observablehq.com/@ddshiyu/cluster-dendrogram#cell-1)
const width = 928;
const height = 500;
const margin = { top: 50, right: 20, bottom: 20, left: 100 };

// Turn the raw Titanic array into a nested Map:
// first by Pclass, then Sex, then Embarked, then Survived → each leaf holds a count
const dataMap = rollup(
tested1, // our dataset
v => v.length, // we just need counts, so length of each group
d => d.Pclass, // group level 1: ticket class
d => d.Sex, // level 2: gender
d => d.Embarked, // level 3: port of embarkation
d => d.Survived // level 4: did they survive?
);

// Convert the nested Map into a plain JS tree object:
// { name: key, children: […] } for internal nodes, plus value on leaves
function mapToTree(map, name) {
const children = [];
for (const [key, val] of map) {
if (val instanceof Map) {
// dive one level deeper
children.push(mapToTree(val, key));
} else {
// reached a leaf: save the count
children.push({ name: key, value: val });
}
}
return { name, children };
}
const rootData = mapToTree(dataMap, "All Passengers");

// Build a d3.hierarchy from our object, summing up child values into each parent
const root = hierarchy(rootData)
.sum(d => d.value) // let internal nodes know their subtree total
.sort((a, b) => b.value - a.value);// show bigger branches first

// Lay out the tree, top to bottom, spacing siblings tighter than cousins
tree()
.size([
width - margin.left - margin.right, // horizontal room for nodes
height - margin.top - margin.bottom // vertical room for depth levels
])
.separation((a, b) =>
a.parent === b.parent ? 1 : 1.5 // siblings vs cousins spacing
)(root);

// Kick off our SVG canvas and shift everything into the margins
const svg = create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height]);
const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

// Prepare the path generator for parent→child links
const linkGen = linkVertical()
.x(d => d.x)
.y(d => d.y);

// Draw links first so nodes sit on top
g.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.selectAll("path")
.data(root.links())
.join("path")
.attr("d", linkGen);

// Create a <g> for each node at its computed (x,y)
const node = g.append("g")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 1.5)
.selectAll("g")
.data(root.descendants())
.join("g")
.attr("transform", d => `translate(${d.x},${d.y})`);

// Draw a circle for each node: darker if it has children
node.append("circle")
.attr("r", 4)
.attr("fill", d => d.children ? "#555" : "#999");

// Label each node beside its circle: to the left for parents, to the right for leaves
node.append("text")
.attr("dy", "0.31em")
.attr("x", d => d.children ? -6 : 6)
.attr("text-anchor", d => d.children ? "end" : "start")
.style("font", "10px sans-serif")
.text(d => d.data.name);

// Hand back the completed SVG element
return svg.node();
}


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