viewof combinedViz = {
let selectedStates = [];
const width = 1100;
const barChartsHeight = 1200;
const mapHeight = 800;
const totalHeight = barChartsHeight + mapHeight;
const margin = { top: 50, right: 20, bottom: 150, left: 100 };
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);
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();
}