Public
Edited
Dec 11
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
readingDataRaw = FileAttachment("reading-data.csv").csv()
Insert cell
viewof readingDataTable = Inputs.table(readingDataRaw)
Insert cell
ageGroups = [
{ label: "0-18", min: 0, max: 18 },
{ label: "19-25", min: 19, max: 25 },
{ label: "26-34", min: 26, max: 34 },
{ label: "35-44", min: 35, max: 44 },
{ label: "45-54", min: 45, max: 54 },
{ label: "55-64", min: 55, max: 64 },
{ label: "65+", min: 65, max: Infinity }
];
Insert cell
ageGroups[0].label
Insert cell
readingDataMod = {

function getAgeGroup(age) {
const group = ageGroups.find(g => age >= g.min && age <= g.max);
return group ? group.label : "Unknown";
}
const readingDataClean = readingDataRaw.map((d) => {
return {
date: d.date,
time: d.time,
country: d.country,
city: d.city,
age: parseInt(d.age),
ageGroup: getAgeGroup(d.age),
title: d.book_title,
author: d.author,
isbn: d.isbn,
genres: d.genres.split(", ").map(g=>g.trim()),
year: parseInt(d.year_published)
};
})
return readingDataClean;
}
Insert cell
viewof selectedAgeGroup = Inputs.checkbox(
ageGroups.map(d=>d.label), {
multiple: true,
label: "Age Group",
value: ageGroups.map(d=>d.label)
})
Insert cell
Insert cell
bookTidesViz = {
// Filter data for a specific age group (e.g., "19-25")
const selectedAgeGroup = "35-44";
const filteredData = readingDataMod.filter(d => d.ageGroup === selectedAgeGroup);

// Count occurrences of each genre in the filtered data
const genreCounts = d3.rollups(
filteredData.flatMap(d => d.genres), // Flatten genres into a single array
v => v.length, // Count occurrences
d => d // Group by genre
).map(([key, value]) => ({ genre: key, value }));

// Prepare data for the treemap layout
const root = d3
.hierarchy({ children: genreCounts })
.sum(d => d.value)
.sort((a, b) => b.value - a.value);

// Set dimensions
const width = 800;
const height = 400;

// Create treemap layout
d3.treemap()
.size([width, height])
.padding(1)(root);

// Create SVG container
const svg = d3
.select(DOM.svg(width, height))
.attr("viewBox", `0 0 ${width} ${height}`)
.style("font-family", "helvetica neue");

// Tooltip setup
const tooltip = d3
.select("body")
.append("div")
.style("position", "absolute")
.style("background", "white")
.style("border", "1px solid #ccc")
.style("border-radius", "5px")
.style("padding", "5px")
.style("box-shadow", "0px 0px 5px rgba(0,0,0,0.3)")
.style("pointer-events", "none")
.style("opacity", 0)
.style("font-family", "helvetica neue") // Change font family
.style("font-size", "12px") // Adjust font size
.style("font-weight", "normal") // Set font weight
// Add rectangles for each genre
const node = svg
.selectAll("g")
.data(root.leaves())
.join("g")
.attr("transform", d => `translate(${d.x0},${d.y0})`);

node
.append("rect")
.attr("width", d => d.x1 - d.x0)
.attr("height", d => d.y1 - d.y0)
.attr("fill", (d, i) => d3.schemeObservable10[i % 7])
.on("mouseover", function (event, d) {
d3.select(this).attr("fill-opacity", 0.7); // Make color less opaque

// Calculate the percentage
const total = d.parent.value; // Total count for the tree
const percentage = ((d.value / total) * 100).toFixed(1); // Genre percentage

// Show tooltip
tooltip
.style("opacity", 1)
.html(
`<strong>${d.data.genre}</strong><br>${percentage}% of people reading this genre`
)
.style("left", `${event.pageX + 5}px`)
.style("top", `${event.pageY - 55}px`);
})
.on("mousemove", function (event) {
// Update tooltip position as the mouse moves
tooltip
.style("left", `${event.pageX + 5}px`)
.style("top", `${event.pageY - 55}px`);
})
.on("mouseout", function () {
d3.select(this).attr("fill-opacity", 1); // Reset color opacity

// Hide tooltip
tooltip.style("opacity", 0);
});

// Add genre labels
node
.append("text")
.selectAll("tspan")
.data(d => d.data.genre.split(" "))
.join("tspan")
.attr("x", 4)
.attr("y", (d, i) => 13 + i * 12)
.text(d => d)
.attr("fill", "white")
.attr("font-size", "11px")
.attr("font-weight", "bold");

// Add value labels
node
.append("title")
.text(d => `${d.data.genre}: ${d.value}`);

// Return the SVG
return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more