Public
Edited
Mar 12
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof combinedViz = {
let selectedStates = [];

const width = 1100;
const barChartsHeight = 1200; // Height for both bar charts
const mapHeight = 800; // Height for the map
const totalHeight = barChartsHeight + mapHeight; // Total height for the entire visualization
const margin = { top: 50, right: 20, bottom: 150, left: 100 };

// Adjust individual chart heights
const obesityHeight = 600;
const restaurantHeight = 600;

const filteredByYear = YearData.filter(d => d.year === selectedYear);

const filteredData = maybeColor
? filteredByYear.filter(d => {
if (maybeColor === "Northeast") return northeast.includes(d.state);
if (maybeColor === "Midwest") return midwest.includes(d.state);
if (maybeColor === "South") return south.includes(d.state);
if (maybeColor === "West") return west.includes(d.state);
return true;
})
: filteredByYear;

const sortedObesityData = filteredData.slice().sort((a, b) => b.Obesity - a.Obesity);
const sortedRestaurantData = filteredData.slice().sort((a, b) => b.Restaurants - a.Restaurants);

// Scales for both charts
const xScale = d3.scaleBand()
.domain(sortedObesityData.map(d => d.state))
.range([margin.left, width - margin.right])
.padding(0.2);

const yScaleObesity = d3.scaleLinear()
.domain([0, d3.max(sortedObesityData, d => d.Obesity)])
.range([obesityHeight - margin.bottom, margin.top]);

const yScaleRestaurants = d3.scaleLinear()
.domain([0, d3.max(sortedRestaurantData, d => d.Restaurants)])
.range([restaurantHeight - margin.bottom, margin.top]);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", totalHeight); // Use totalHeight for both charts and map

// Create a tooltip div
const tooltip = d3.select("body").append("div")
.style("position", "absolute")
.style("background", "white")
.style("padding", "5px")
.style("border", "1px solid black")
.style("border-radius", "5px")
.style("visibility", "hidden")
.style("font-size", "12px");

// Map Section (now on top)
const mapGroup = svg.append("g")
.attr("transform", `translate(0, 0)`); // Position the map at the top

// Data ranges for the map
const minX = d3.min(YearData, d => d.X);
const minY = d3.min(YearData, d => d.Y) - 50;
const maxX = d3.max(YearData, d => d.X);
const maxY = d3.max(YearData, d => d.Y);
const mapMargin = 110; // For spacing

// Scales for the map
const mapXScale = d3.scaleLinear().domain([minX, maxX]).range([mapMargin, width - mapMargin]);
const mapYScale = d3.scaleLinear().domain([minY, maxY]).range([mapMargin, mapHeight - mapMargin]);

// Add a dynamic header for the map
const mapHeader = mapGroup.append("text")
.attr("x", width / 2)
.attr("y", 30) // Adjust vertical position as needed
.attr("text-anchor", "middle")
.attr("font-size", "20px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(getHeaderText()); // Set initial text

function getHeaderText() {
let regionText = maybeColor ? ` - ${maybeColor}` : "";
return `Obesity and Restaurant Distribution in ${selectedYear}${regionText}`;
}

mapHeader.text(getHeaderText()); // Update header dynamically

// Tooltip for the map
const mapTooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("background", "white")
.style("padding", "5px")
.style("border", "1px solid black")
.style("border-radius", "5px")
.style("pointer-events", "none")
.style("opacity", 0);

function updateTooltipPosition(event) {
const offset = 10;
const tooltipNode = mapTooltip.node();
const tooltipRect = tooltipNode.getBoundingClientRect();
let x = event.pageX - tooltipRect.width - offset;
let y = event.pageY - tooltipRect.height - offset;
if (x < 0) { x = event.pageX + offset; }
if (y < 0) { y = event.pageY + offset; }
mapTooltip.style("left", x + "px").style("top", y + "px");
}

// Create glyph map
filteredData.forEach(d => {
const restaurant_num = +d.Restaurants;
const obesityValue = +d.Obesity * 5;

const originalWidth = 300;
const scaleFactor = 0.2;
const svgWidthLocal = originalWidth * 0.1;
const totalHeightLocal = (restaurant_num / 100 + obesityValue) * scaleFactor;
const scaledYellowHeight = (restaurant_num / 80) * scaleFactor;
const scaledRedHeight = (obesityValue / 6) * scaleFactor;

let stateSvg = d3.create("svg")
.attr("width", svgWidthLocal)
.attr("height", totalHeightLocal)
.on("mouseover", function(event) {
mapTooltip.style("opacity", 1)
.html(`<strong>${d.state}</strong><br>Restaurants: ${d.Restaurants}<br>Obesity: ${d.Obesity}%<br>Obesity Rank: ${d.Obesity_Rank}`);
})
.on("mousemove", function(event) {
updateTooltipPosition(event);
})
.on("mouseout", function() {
mapTooltip.style("opacity", 0);
})
.on("click", function() {
const index = selectedStates.indexOf(d.state);
if (index !== -1) {
selectedStates.splice(index, 1); // Remove the state
} else if (selectedStates.length < 5) {
selectedStates.push(d.state); // Add the state
}
mapTooltip.style("opacity", 0);
console.log("Selected States:", selectedStates);
});

stateSvg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", svgWidthLocal)
.attr("height", scaledYellowHeight)
.attr("fill", "yellow");

const rank = Math.max(0, +d.Obesity_Rank);
const numLines = Math.min(rank, 51);
if (numLines > 0) {
let spacing = svgWidthLocal / numLines;
for (let i = 0; i <= numLines; i++) {
stateSvg.append("line")
.attr("x1", i * spacing)
.attr("x2", i * spacing)
.attr("y1", 0)
.attr("y2", scaledYellowHeight)
.attr("stroke", "gray")
.attr("stroke-width", scaleFactor * 2)
.attr("opacity", 0.5);
}
}

stateSvg.append("rect")
.attr("x", 0)
.attr("y", scaledYellowHeight)
.attr("width", svgWidthLocal)
.attr("height", scaledRedHeight)
.attr("fill", "red");

mapGroup.append("g")
.attr("transform", `translate(${mapXScale(d.X) - svgWidthLocal/2}, ${mapYScale(d.Y) - totalHeightLocal/2})`)
.node().append(stateSvg.node());
});

// Legend for the map
const legend = mapGroup.append("g")
.attr("transform", `translate(20, ${mapHeight - 150})`);

legend.append("text")
.attr("x", 0)
.attr("y", 0)
.text("Legend")
.attr("font-size", "14px")
.attr("font-weight", "bold");

legend.append("rect")
.attr("x", 0)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "yellow");

legend.append("rect")
.attr("x", 0)
.attr("y", 50)
.attr("width", 20)
.attr("height", 20)
.attr("fill", "red");

legend.append("text")
.attr("x", 30)
.attr("y", 65)
.text("(height) : Obesity Rate")
.attr("font-size", "12px");

legend.append("line")
.attr("x1", 10)
.attr("x2", 10)
.attr("y1", 90)
.attr("y2", 120)
.attr("stroke", "gray")
.attr("stroke-width", 2)
.attr("opacity", 0.5);

legend.append("text")
.attr("x", 30)
.attr("y", 105)
.text("# of Fries = Obesity Rank for That Year")
.attr("font-size", "12px");

// Obesity Bar Chart (now below the map)
const obesityBars = svg.append("g")
.attr("transform", `translate(0, ${mapHeight})`) // Move the obesity chart below the map
.selectAll("rect")
.data(sortedObesityData)
.join("rect")
.attr("x", d => xScale(d.state))
.attr("y", d => yScaleObesity(d.Obesity))
.attr("width", xScale.bandwidth())
.attr("height", d => yScaleObesity(0) - yScaleObesity(d.Obesity))
.attr("fill", "red")
.attr("opacity", 1)
.on("mouseover", (event, d) => {
tooltip.style("visibility", "visible")
.text(`${d.state}: ${d.Obesity}%`);
obesityBars.transition().duration(200).attr("opacity", 0.3);
d3.select(event.currentTarget).transition().duration(200).attr("opacity", 1);
})
.on("mousemove", (event) => {
tooltip.style("top", `${event.pageY - 20}px`)
.style("left", `${event.pageX + 10}px`);
})
.on("mouseout", () => {
tooltip.style("visibility", "hidden");
obesityBars.transition().duration(200).attr("opacity", 1);
});

svg.append("g")
.attr("transform", `translate(0, ${mapHeight + obesityHeight - margin.bottom})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");

svg.append("text")
.attr("x", width / 2)
.attr("y", mapHeight + obesityHeight - margin.bottom + 100)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("States");

svg.append("g")
.attr("transform", `translate(${margin.left}, ${mapHeight})`)
.call(d3.axisLeft(yScaleObesity));

// Y-Axis Label for Obesity Bar Chart
svg.append("text")
.attr("x", -obesityHeight / 2)
.attr("y", margin.left / 3)
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90)")
.attr("font-size", "14px")
.text("Obesity Rate (%)");

svg.append("text")
.attr("x", width / 2)
.attr("y", mapHeight + margin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text(`Obesity Rate per State (${selectedYear}) ${maybeColor ? `- ${maybeColor}` : ""}`);

// Restaurant Bar Chart (now below the obesity chart)
const restaurantBars = svg.append("g")
.attr("transform", `translate(0, ${mapHeight + obesityHeight})`) // Move the restaurant chart below the obesity chart
.selectAll("rect")
.data(sortedRestaurantData)
.join("rect")
.attr("x", d => xScale(d.state))
.attr("y", d => yScaleRestaurants(d.Restaurants))
.attr("width", xScale.bandwidth())
.attr("height", d => yScaleRestaurants(0) - yScaleRestaurants(d.Restaurants))
.attr("fill", "yellow")
.attr("opacity", 1)
.on("mouseover", (event, d) => {
tooltip.style("visibility", "visible")
.text(`${d.state}: ${d.Restaurants} restaurants`);
restaurantBars.transition().duration(200).attr("opacity", 0.3);
d3.select(event.currentTarget).transition().duration(200).attr("opacity", 1);
})
.on("mousemove", (event) => {
tooltip.style("top", `${event.pageY - 20}px`)
.style("left", `${event.pageX + 10}px`);
})
.on("mouseout", () => {
tooltip.style("visibility", "hidden");
restaurantBars.transition().duration(200).attr("opacity", 1);
});

svg.append("g")
.attr("transform", `translate(0, ${mapHeight + obesityHeight + restaurantHeight - margin.bottom})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");

svg.append("text")
.attr("x", width / 2)
.attr("y", mapHeight + obesityHeight + restaurantHeight - margin.bottom + 100)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("States");

svg.append("g")
.attr("transform", `translate(${margin.left}, ${mapHeight + obesityHeight})`)
.call(d3.axisLeft(yScaleRestaurants));

// Y-Axis Label for Restaurant Bar Chart
svg.append("text")
.attr("x", -restaurantHeight / 2)
.attr("y", margin.left)
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90)")
.attr("font-size", "14px")
.text("Number of Restaurants");

svg.append("text")
.attr("x", width / 2)
.attr("y", mapHeight + obesityHeight + margin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text(`Number of Restaurants per State (${selectedYear}) ${maybeColor ? `- ${maybeColor}` : ""}`);

return svg.node();
}
Insert cell
data = YearData
Insert cell
viewof selectedStates = {
const states = data.map(d => d.state);
const container = html`<div class="checkbox-container"></div>`;
states.forEach(state => {
const checkbox = html`<label>
<input type="checkbox" value="${state}" checked> ${state}
</label>`;
container.appendChild(checkbox);
});
return container;
}

Insert cell
viewof selectedMetrics = Inputs.checkbox(
new Map([
["Obesity", "Obesity Rate (%)"],
["Restaurants", "Number of Restaurants"],
["Obesity_Rank", "Obesity Rank"],
]),
{label: "Select Metrics:", value: new Set(["Obesity", "Restaurants"])}
);

Insert cell
YearData
Insert cell
filteredData = {
const states = Array.from(selectedStates.querySelectorAll("input:checked")).map(input => input.value);
const metrics = Array.from(selectedMetrics.querySelectorAll("input:checked")).map(input => input.value);
return data.filter(d => states.includes(d.state)).map(d => {
const filtered = { state: d.state };
metrics.forEach(metric => {
filtered[metric] = d[metric];
});
return filtered;
});
}
Insert cell
viewof smallMultiples = {
const width = 800;
const height = 400;
const margin = { top: 20, right: 20, bottom: 30, left: 40 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid #ccc");

const g = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

// Update visualization based on filtered data
const update = (data) => {
const states = data.map(d => d.state);
const metrics = Object.keys(data[0]).filter(key => key !== "state");

// Clear previous charts
g.selectAll(".state-chart").remove();

// Create a mini chart for each state
const stateCharts = g.selectAll(".state-chart")
.data(data, d => d.state)
.join("g")
.attr("class", "state-chart")
.attr("transform", (d, i) => `translate(${(i % 3) * (innerWidth / 3)},${Math.floor(i / 3) * (innerHeight / 2)})`);

// Draw bars for each metric
metrics.forEach((metric, i) => {
stateCharts.each(function(d) {
const chart = d3.select(this);
const barWidth = innerWidth / 3 / metrics.length;
const maxValue = d3.max(data, d => d[metric]);

const yScale = d3.scaleLinear()
.domain([0, maxValue])
.range([innerHeight / 2, 0]);

chart.append("rect")
.attr("x", i * barWidth)
.attr("y", yScale(d[metric]))
.attr("width", barWidth - 5)
.attr("height", innerHeight / 2 - yScale(d[metric]))
.attr("fill", metric === "Obesity" ? "red" : metric === "Restaurants" ? "gold" : "blue");

chart.append("text")
.attr("x", i * barWidth + barWidth / 2)
.attr("y", innerHeight / 2 + 15)
.attr("text-anchor", "middle")
.text(`${metric}: ${d[metric]}`);
});
});
};

// Initial render
update(filteredData);

// Re-render when data changes
selectedStates.addEventListener("change", () => update(filteredData));
selectedMetrics.addEventListener("change", () => update(filteredData));

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
RegionPops.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
regionpops2 = regionpops.map(d => ({
...d,
Population: +d.Population.replace(/,/g, "")
}))
Insert cell
Insert cell
//region_combined = combineRegion(YearData)
Insert cell
//region_combined[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